﻿/* ==========================================================================
   OmniEsol NSM 개발현황 대시보드 — 프로젝트 전용 추가 스타일
   Inspinia(app.min.css) 위에 얹는 최소 보강만 담는다.
   Inspinia 가 이미 제공하는 클래스를 재정의하지 않는다.
   ========================================================================== */

/* 한글 폰트 폴백 — Inspinia 기본 라틴 폰트 뒤에 한글 폰트를 이어 붙인다.
   외부 CDN 을 쓰지 않으므로(사내망/오프라인 대응) 시스템 설치 폰트만 사용. */
:root {
    --theme-font-sans-serif: "Open Sans", "Pretendard", "Malgun Gothic", "맑은 고딕",
        "Apple SD Gothic Neo", sans-serif;
}

/* ==========================================================================
   브랜드 색 / 테마
   ⚠ 색 토큰은 여기 두지 않는다 — **css/themes.css 가 유일한 정본**이다.
     예전에 이 파일에 html[data-skin='default'] 블록으로 브랜드 색을 넣어 뒀는데,
     themes.css 의 html[data-theme=...] 과 구체도가 같고(0,1,1) 이 파일이 뒤에
     로드되므로 **테마 선택이 전부 무력화**됐다(실측: slate 로 바꿔도 primary 가
     signature 값 유지). 그래서 색 토큰은 themes.css 의 signature 테마로 이관했다.
   테마 추가/변경은 css/themes.css + js/themes.js 두 곳만 본다.
   ========================================================================== */

/* --------------------------------------------------------------------------
   OmniEsol 로고 락업
   PPT 양식(D:\ai\PPT 양식\Session1_*.html)에 base64 로 들어 있던 alt="OmniEsol"
   심볼(585x529)을 assets/images/omniesol-symbol.png 로 추출해 쓴다.
   ⚠ 가로형 워드마크 이미지는 없다(심볼만). 그래서 큰 로고는
     "심볼 + 텍스트" 로 조합한다. 텍스트라서 라이트/다크에서 색이 자동으로 맞고
     Inspinia 의 logo-light / logo-dark 를 위한 별도 이미지가 필요 없다.
   심볼은 투명 배경 + 브랜드 블루(rgb(41,138,255))라 밝은 상단바와
   어두운 사이드바 양쪽에서 모두 보인다(실측 확인).
   -------------------------------------------------------------------------- */
/* ⚠ 선택자 주의 — Inspinia 가 아래처럼 더 구체적으로 지정한다(실측):
     `.logo span.logo-lg { display:block }`  (0,2,1)
     `.logo-lg img { height: var(--theme-logo-lg-height) }`
   그래서 `.oe-logo`(0,1,0) 로는 이기지 못해 심볼과 텍스트가 세로로 겹쳤다.
   같은 형태로 구체도를 맞춘다. */
.logo span.logo-lg.oe-logo,
.oe-logo {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    white-space: nowrap;
    line-height: 1;
}
/* display:block 이 중요 — inline 이면 baseline 정렬이라 텍스트와 위아래로 어긋난다.
   높이는 원본을 2배로 미리 축소해 둔 값(64px)의 절반. */
.logo-lg img.oe-logo-sym,
.oe-logo-sym {
    display: block;
    height: 28px;
    width: auto;
    flex: none;
}
.logo-sm img.oe-logo-sym {
    display: block;
    height: 26px;
    width: auto;
    margin: 0 auto;
}
/* 워드마크 글자 — "ERP 개발1센터".
   ⚠ 예전 문구는 "OmniEsol"(라틴)이었고 값도 거기에 맞춰 둔 것이었다.
     한글은 같은 px 에서도 글자 잉크가 훨씬 크고(실측: 18px/800 기준 잉크 높이
     라틴 14px vs 한글 17px), 라틴에 없던 **디센더(2px)** 가 생겨
     아래로 처져 보인다. 그래서 값을 다시 잡았다.
       · 크기  1.125rem/800 → 1.0625rem(17px)/700
               잉크 17px → 16px. 심볼 28px 대비 0.57 로,
               예전 라틴 조합(14/28 = 0.50)과 비슷한 균형이 된다.
       · 보정  `padding-top: 1px`(대문자 O 용) 제거 → 디센터만큼 위로 당긴다.
     ⚠ 값을 바꿀 때는 눈대중 말고 **심볼 중심 y 와 글자 잉크 중심 y 를 실측**해
       맞춘다(그냥 align-items:center 로는 디센더 때문에 어긋난다). */
.oe-logo-txt {
    display: block;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1;
    /* 디센더(2px)로 잉크 중심이 아래로 내려간 만큼 되올린다 — 실측으로 고른 값 */
    padding-bottom: 2px;
}
/* 상단바(밝은 배경)에서는 본문색, 사이드바(어두운 배경)에서는 흰색 */
.app-topbar .oe-logo-txt {
    color: var(--theme-body-color);
}
.sidenav-menu .oe-logo-txt {
    color: #fff;
}

/* ==========================================================================
   페이지 설명 — 제목 줄에서 이 화면이 무엇을 보여주는지 한 줄로 말한다

   ⚠ 화면 이름(제목)은 **상단바**가 담당한다(layout.js 의 #pageHeadTitle).
     제목을 여기 또 적으면 같은 말이 두 번 나온다 — 그래서 이 자리는 **설명 전용**이다.
   ⚠ 집계 화면에서 "이 숫자가 무슨 숫자냐" 를 없애는 것이 목적이다 →
     대상 데이터·기준·시안 여부를 한 줄에 담는다(참고 문서의 스코프 배너와 같은 역할).
   ========================================================================== */
.page-desc {
    font-size: 0.8125rem;
    line-height: 1.6;
    color: var(--bs-secondary-color);
    /* 넓은 화면에서 한 줄이 너무 길어지면 읽기 어렵다 */
    max-width: 92ch;
}
.page-desc > i:first-child {
    color: var(--theme-primary);
    margin-right: 0.4rem;
}
.page-desc b {
    color: var(--theme-emphasis-color);
    font-weight: 600;
}

/* ==========================================================================
   전체화면 버튼 — 아이콘 두 개가 같이 보이던 결함

   ⚠ 벤더(Inspinia) CSS 에는 **켤 때 보이게 하는 규칙만** 있다(실측):
       body.fullscreen-enable .ti-minimize { display: block !important; }
       body.fullscreen-enable .ti-maximize { display: none; }
     즉 **기본 상태에서 .ti-minimize 를 숨기는 규칙이 없다.** 벤더 템플릿은 자기 마크업에서
     따로 숨겨 두고 있었던 것으로 보이는데, 우리 layout.js 마크업에는 그게 없어서
     `전체화면`(maximize) + `전체화면 취소`(minimize) 아이콘이 **동시에** 찍혔다.
   → 기본값을 숨김으로 정한다. 켜질 때는 위 벤더 규칙이 `!important` 로 이기므로
     이쪽에 !important 를 쓰지 않는다(쓰면 전체화면에서 아이콘이 안 바뀐다).
   ========================================================================== */
.app-topbar [data-toggle='fullscreen'] .ti-minimize {
    display: none;
}

/* ==========================================================================
   KPI 처리구분 탭 바 (전체 / 유지보수 / 신규)
   KPI 행 바로 위에 얇게 얹는다 — 카드가 아니라 조회조건과 KPI 사이의 띠다.
   ========================================================================== */
.kpi-fgbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 0.25rem 0 0.5rem;
}
.kpi-fgbar-lb {
    font-size: 0.8125rem;
    font-weight: 600;
    white-space: nowrap;
}
/* nav-tabs 는 기본으로 아래 테두리를 전폭으로 그린다 → 띠 안에서는 항목만큼만 */
.kpi-fgbar .nav-tabs {
    border-bottom: 0;
}
.kpi-fgbar .nav-link {
    padding: 0.35rem 0.85rem;
    font-size: 0.8125rem;
}
.kpi-fgbar-note {
    white-space: nowrap;
}
@media (max-width: 767.98px) {
    /* 좁은 폭에서는 안내문이 탭을 밀어내므로 숨긴다(탭 자체가 더 중요하다) */
    .kpi-fgbar-note {
        display: none;
    }
}

/* ==========================================================================
   단위업무 위젯 헤더 — 아이콘 + 제목 + 큰 수치를 한 줄에
   ========================================================================== */
.ut-w-ico {
    display: inline-flex;
    align-items: center;
    color: var(--theme-primary);
    font-size: 1rem;
    line-height: 1;
}
.ut-w-total {
    font-family: var(--dash-num-font, inherit);
    font-weight: var(--dash-num-weight, 700);
    letter-spacing: var(--dash-num-ls, -0.02em);
    font-size: 1.375rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--theme-emphasis-color);
}

/* 숫자 정렬 — KPI·표의 수치가 자리수마다 흔들리지 않게 */
.num,
.card h3,
.table td,
.table th {
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   행 간격 보정 — .row-equal
   Inspinia 는 행(.row)에 margin 이 없고, 행 사이 간격을 카드의
   margin-bottom(20px)으로만 만든다. 그런데 카드에 .h-100 을 주면 카드 높이가
   컬럼 높이와 같아져서 그 margin 이 행 박스 밖으로 빠지고, 결과적으로 다음 행과의
   간격이 0px 이 된다(실측 확인).
   → .h-100 카드를 쓰는 행에는 .row-equal 을 함께 주고, 간격을 행 자체가 갖게 한다.
   .h-100 은 카드 높이를 서로 맞춰 3열 배치에서 짧은 카드 밑에 큰 빈 공간이
   생기는 것도 막아준다(실측 196px 공백 → 0).
   -------------------------------------------------------------------------- */
.row-equal {
    margin-bottom: 20px; /* 다른 행이 카드 margin 으로 만드는 간격과 같은 값 */
}
.row-equal > [class*='col'] > .card.h-100 {
    margin-bottom: 0; /* 행이 간격을 담당하므로 카드 margin 은 제거 */
}

/* --------------------------------------------------------------------------
   위젯 그리드 (#dashGrid)
   드래그로 순서를 바꾸려면 위젯이 모두 **한 컨테이너의 형제**여야 한다.
   그래서 여러 .row 로 나누지 않고 하나의 .row 에 col-* 을 나열하고
   줄바꿈은 Bootstrap 이 알아서 하게 둔다.
   행 간격은 위 .row-equal 방식(카드 margin)이 아니라 **거터(g-3)** 가 담당한다
   → 카드 자체 margin-bottom(20px)은 거터와 이중으로 더해지므로 제거한다.
   -------------------------------------------------------------------------- */
#dashGrid > [class*='col'] > .card {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   KPI 7열 그리드
   Bootstrap 의 row-cols-* 는 6까지만 제공한다. KPI 가 7종이라 커스텀 추가.
   xxl(1400px+) 에서 7열, 그 아래로는 4열 → 2열 → 1열로 접힌다.
   -------------------------------------------------------------------------- */
@media (min-width: 1400px) {
    .row-cols-xxl-7 > * {
        flex: 0 0 auto;
        width: 14.2857142857%;
    }
}

/* KPI 카드: 7열일 때 폭이 좁아지므로 내부 여백·글자를 한 단 줄인다 */
.kpi-card .card-body {
    padding: 1rem 1.1rem;
}
.kpi-card .kpi-label {
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1.35;
    min-height: 2.1em; /* 라벨 1줄/2줄이 섞여도 숫자 baseline 을 맞춘다 */
    margin-bottom: 0.5rem;
}
.kpi-card h3 {
    font-size: 1.5rem;
    letter-spacing: -0.02em;
}
.kpi-card .kpi-foot {
    font-size: 0.75rem;
}

/* KPI 숫자 → 상태별 상세 목록 링크.
   기본은 링크처럼 보이지 않게 두고(숫자 가독성 우선) hover 때만 링크임을 드러낸다. */
.kpi-link {
    color: inherit;
    text-decoration: none;
}
.kpi-link:hover,
.kpi-link:focus-visible {
    color: var(--theme-primary);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* --------------------------------------------------------------------------
   조회조건(필터) 바
   -------------------------------------------------------------------------- */
.filter-card .filter-label {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    white-space: nowrap;
}
/* 프리셋 버튼이 많아 줄바꿈될 때 btn-group 의 라운드가 깨지지 않도록 개별 버튼 처리 */
.preset-group {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
}
.preset-group .btn {
    border-radius: var(--theme-border-radius, 0.375rem);
}
/* flatpickr 입력은 읽기전용처럼 보이지 않게 커서 유지 */
input[data-provider="flatpickr"] {
    background-color: var(--theme-secondary-bg, #fff);
    cursor: pointer;
}
.date-sep {
    padding: 0 0.15rem;
    color: var(--theme-secondary-color);
}

/* --------------------------------------------------------------------------
   멀티 선택 드롭다운 (.ms)
   네이티브 select 는 단일 선택뿐이라 체크박스 드롭다운으로 대체했다.
   버튼 모양은 .form-select-sm 과 맞춰 다른 필터와 이질감이 없게 한다.
   -------------------------------------------------------------------------- */
.ms-wrap {
    display: inline-block;
}
.ms-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 118px;
    max-width: 190px;
    /* 조회조건 바에서 form-control-sm 과 나란히 서므로 높이 메트릭을 맞춘다.
       .btn-sm 기본값(line-height:normal)이면 3px 낮아 한 줄이 어긋나 보인다. */
    padding: 0.25rem 0.6rem;
    line-height: 1.5;
    background: var(--theme-secondary-bg);
    border: 1px solid var(--theme-border-color);
    color: var(--theme-body-color);
    font-size: 0.7875rem;
    text-align: left;
}
.ms-btn:hover {
    border-color: var(--theme-secondary-color);
}
.ms-btn-tx {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ms-cnt {
    flex: none;
    font-size: 0.625rem;
}
.ms-menu {
    min-width: 232px;
    max-width: 300px;
    padding: 6px;
}
.ms-search {
    position: relative;
    margin-bottom: 4px;
}
.ms-search .ti {
    position: absolute;
    left: 8px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 13px;
    color: var(--theme-secondary-color);
    pointer-events: none;
}
.ms-search .ms-q {
    padding-left: 26px;
}
/* 목록이 길어도 드롭다운이 화면을 넘지 않게 */
.ms-list {
    max-height: 264px;
    overflow-y: auto;
    scrollbar-width: thin;
}
.ms-list::-webkit-scrollbar {
    width: 7px;
}
.ms-list::-webkit-scrollbar-thumb {
    background: var(--theme-border-color);
    border-radius: 4px;
}
.ms-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 8px;
    border-radius: 5px;
    font-size: 0.8125rem;
    cursor: pointer;
    margin: 0;
}
.ms-row:hover {
    background: var(--theme-tertiary-bg, rgba(0, 0, 0, 0.04));
}
.ms-row input {
    flex: none;
    margin: 0;
    cursor: pointer;
}
.ms-row span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* '전체' 는 목록과 한 칸 띄워 구분 */
.ms-row-all {
    font-weight: 600;
    border-bottom: 1px solid var(--theme-border-color);
    border-radius: 5px 5px 0 0;
    margin-bottom: 4px;
}
/* Unit(상위) 행 — 하위 Cell 은 들여쓴다 */
.ms-row-unit {
    font-weight: 600;
    font-size: 0.78rem;
}
.ms-group .ms-row:not(.ms-row-unit) {
    padding-left: 26px;
}
.ms-group + .ms-group {
    border-top: 1px dashed var(--theme-border-color);
    margin-top: 3px;
    padding-top: 3px;
}
.ms-foot {
    border-top: 1px solid var(--theme-border-color);
    margin-top: 4px;
    padding-top: 2px;
    text-align: right;
}
.ms-clear {
    font-size: 0.75rem;
    padding: 2px 6px;
    text-decoration: none;
}

/* flatpickr 월 선택 콤보박스 색 맞추기.
   원인: vendors.min.css 가 `.flatpickr-monthDropdown-month{background-color:transparent}`
   로 option 배경을 투명하게 지정하고 select 자체에도 배경이 없다. 네이티브 드롭다운
   목록은 select/option 의 computed 색을 그대로 쓰기 때문에, 투명이면 브라우저 기본색
   (흰 배경·검은 글씨)으로 떠서 같은 화면의 .form-select 목록과 색이 달라진다.
   → .form-select 와 동일한 테마 토큰으로 고정한다(다크모드까지 함께 따라간다).

   ⚠ 선택자를 `.flatpickr-current-month` 부터 쓴 이유: vendors.min.css 가
     `.flatpickr-current-month .flatpickr-monthDropdown-months{background:transparent}`
     (specificity 0,2,0 + background 단축속성)로 지정해서, 클래스 하나(0,1,0)로는
     덮어쓰지 못한다. 같은 형태로 맞춰야 이긴다. */
.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
    background-color: var(--theme-secondary-bg);
    color: var(--theme-body-color);
}
/* hover 시 다시 투명으로 돌아가지 않게(vendors·app 양쪽에 hover 규칙이 있다) */
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
    background-color: var(--theme-tertiary-bg, var(--theme-secondary-bg));
    color: var(--theme-body-color);
}

/* 주말 색 — 일요일 붉은 계열, 토요일 파란 계열(국내 달력 관례).
   flatpickr 의 날짜 칸에는 요일 클래스가 없고 7열로 나열되기만 한다.
   firstDayOfWeek:0(일요일 시작)으로 고정했으므로 nth-child 로 짚을 수 있다.
   (flatpickr-ko.js 에서 firstDayOfWeek 를 바꾸면 이 규칙도 함께 고쳐야 한다.) */
.flatpickr-weekdaycontainer .flatpickr-weekday:first-child,
.dayContainer .flatpickr-day:nth-child(7n + 1) {
    color: #e0405c; /* 일요일 */
}
.flatpickr-weekdaycontainer .flatpickr-weekday:last-child,
.dayContainer .flatpickr-day:nth-child(7n) {
    color: #1273d4; /* 토요일 */
}
/* 이전/다음 달 날짜는 주말이라도 흐리게 (원래 규칙 유지) */
.dayContainer .flatpickr-day.prevMonthDay,
.dayContainer .flatpickr-day.nextMonthDay {
    color: var(--theme-secondary-color);
    opacity: 0.45;
}
/* 선택된 날짜·오늘은 배경이 칠해지므로 글자색을 되돌린다 */
.dayContainer .flatpickr-day.selected,
.dayContainer .flatpickr-day.selected:nth-child(7n),
.dayContainer .flatpickr-day.selected:nth-child(7n + 1) {
    color: #fff;
}

/* 달력 헤더를 "연도 → 월" 순서로.
   flatpickr 기본 DOM 은 [월 셀렉트][연도 입력] 순서(영문 관례)인데,
   국문은 연도를 먼저 고르고 월을 고른다. flex order 로 순서만 뒤집는다.
   (DOM 을 건드리면 flatpickr 내부 참조가 깨질 수 있어 CSS 로만 처리) */
.flatpickr-current-month {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.flatpickr-current-month .numInputWrapper {
    order: 1; /* 연도 먼저 */
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
    order: 2; /* 월 나중 (단일 달력: 셀렉트) */
}
/* ⚠ showMonths:2(범위 달력)에서는 flatpickr 이 월 셀렉트 대신 정적 텍스트
   `.cur-month` 를 쓴다(월 드롭다운은 다중 월과 함께 지원되지 않는다).
   그 구조에도 같은 순서를 적용한다. */
.flatpickr-current-month .cur-month {
    order: 2;
    margin: 0;
}

/* --------------------------------------------------------------------------
   개발자별 처리현황 — 스택 바 행
   -------------------------------------------------------------------------- */
.dev-row {
    display: grid;
    /* 이름 칸에는 훈장(16px)+여백이 함께 들어가고 모듈명이 길다('회계법인 품질위험관리')
       → 84px 이던 것을 넓혔다. 좁으면 훈장이 이름에 밀려 잘려 보인다. */
    grid-template-columns: 148px 1fr 116px;
    gap: 0.75rem;
    align-items: center;
    padding: 0.3rem 0;
}
.dev-row + .dev-row {
    border-top: 1px dashed var(--theme-border-color);
}
.dev-row .dev-name {
    font-size: 0.8125rem;
    font-weight: 600;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* 부서명(최말단 조직명)처럼 라벨이 긴 목록은 이름 칸을 넓힌다.
   전체 부서명은 매우 길어(전체 경로) 툴팁으로만 보여준다 — mock-data.js 의 depts 참고. */
.rows-wide-name .dev-row {
    grid-template-columns: 148px 1fr 132px;
}

/* --------------------------------------------------------------------------
   목록 스크롤 영역
   개발자·부서·모듈 목록은 실제 데이터에서 수십 건이 될 수 있다. 카드가 끝없이
   길어지지 않게 기본 높이만 보여주고 나머지는 내부 스크롤로 넘긴다.

   SimpleBar(테마 내장) 대신 순수 CSS overflow 를 쓴 이유:
   SimpleBar 는 초기화 시점에 요소 크기를 재는데, 숨은 탭(display:none)에서는
   높이가 0 이라 스크롤 영역이 깨진다. 탭 전환마다 recalculate() 를 걸어야 해서
   실패 지점이 늘어난다. overflow 는 숨은 탭에서도 그대로 동작한다.
   -------------------------------------------------------------------------- */
.rows-scroll {
    /* .dev-row 29px 기준 10행 + 다음 행이 반쯤 보이는 높이.
       일부러 절반을 남긴다 — 딱 맞게 자르면 목록이 거기서 끝난 것처럼 보인다. */
    max-height: 306px;
    overflow-y: auto;
    scrollbar-gutter: stable; /* 스크롤바 유무로 내용 폭이 흔들리지 않게 */
    padding-right: 4px;
}
/* 옆 도넛(320px)과 높이를 맞추는 변형 */
.rows-scroll-tall {
    max-height: 276px;
}

/* 스크롤바를 테마 색에 맞춰 얇게 (Chromium/Edge 계열) */
.rows-scroll::-webkit-scrollbar {
    width: 8px;
}
.rows-scroll::-webkit-scrollbar-track {
    background: transparent;
}
.rows-scroll::-webkit-scrollbar-thumb {
    background: var(--theme-border-color);
    border-radius: 4px;
}
.rows-scroll::-webkit-scrollbar-thumb:hover {
    background: var(--theme-secondary-color);
}
/* Firefox */
.rows-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--theme-border-color) transparent;
}
.dev-row .dev-nums {
    font-size: 0.75rem;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* 이름 왼쪽의 소속 부서 — 흐린 작은 글씨로 보조 정보임을 드러낸다.
   한 줄에 [훈장][부서][이름] 으로 나열하므로 부서 칸은 고정폭 + 말줄임. */
.dev-dept {
    flex: 0 0 auto;
    width: 98px;
    /* 사원명과 같은 크기. 구분은 색(흐리게)으로만 준다 */
    font-size: 0.8125rem;
    font-weight: 400;
    line-height: 1.3;
    color: var(--theme-secondary-color);
    opacity: 0.85;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* 부서가 앞에 붙으면 이름은 왼쪽 정렬이 읽기 좋다 */
.rows-with-dept .dev-name {
    text-align: left;
    flex: 1 1 auto;
}
.rows-with-dept .dev-row {
    grid-template-columns: 186px 1fr 100px;
}

/* --------------------------------------------------------------------------
   메뉴별 현황 — 한 줄 행
   [훈장][모듈 · 메뉴명] [막대] [접수 N] 을 한 줄에 넣는다.
   ⚠ 절반 폭 카드(약 595px)에서는 폭 배분이 빡빡하다. 이름 칸을 15자(196px)까지
     주면 막대에 100px 밖에 남지 않아 7색 조각이 2px 슬라이버가 된다(실측).
     → 이름 칸을 208px 로 묶어 막대에 약 250px 를 남긴다(접수구분별 카드와 동일 수준).
     넘치는 메뉴명은 말줄임이며 전체 이름은 툴팁에 나온다.
   -------------------------------------------------------------------------- */
.rows-menu .dev-row {
    grid-template-columns: 208px 1fr 76px;
}
.rows-menu .dev-name-wrap {
    gap: 4px;
}
.rows-menu .dev-dept {
    width: auto;
    max-width: 84px;
}
.rows-menu .dev-name {
    text-align: left;
    flex: 1 1 auto;
}
/* 모듈과 메뉴명 사이 구분점 */
.rows-menu .dev-dept::after {
    content: '·';
    margin-left: 4px;
    opacity: 0.6;
}

/* --------------------------------------------------------------------------
   카드 범례 (제목줄 우측 고정)
   처리현황(3종)과 구분별 접수현황(7종)이 같은 위치에 오게 한다.
   7종은 폭이 모자라 줄바꿈되지만, 부모 헤더가 flex-wrap 이면 범례 블록 전체가
   아래 줄로 떨어져 좌측 정렬돼 버린다(실제로 그렇게 보였다).
   → 헤더는 줄바꿈시키지 않고, 범례 '안에서만' 우측 정렬로 줄바꿈시킨다.
   -------------------------------------------------------------------------- */
/* Inspinia 의 .card-header 는 flex-wrap:wrap 이라, 범례가 남은 폭보다 넓으면
   범례 블록 전체가 다음 줄로 떨어져 좌측 정렬돼 버린다(7종에서 실제로 발생).
   헤더를 nowrap 으로 고정하면 범례 박스가 남은 폭으로 줄어들고,
   줄바꿈은 범례 '안에서' 일어나 항상 제목줄 우측에 머문다.

   ⚠ !important 가 필요한 이유: app.min.css 가
     `.avatar-group,.card-header,.flex-wrap{flex-wrap:wrap!important}` 로
     선언해 두었다(실측 확인). !important 는 같은 우선순위로만 덮을 수 있다. */
.card-header.header-legend {
    flex-wrap: nowrap !important;
}
.card-legend {
    display: flex;
    /* 항상 한 줄 — 7종도 접히지 않게 폰트·간격을 압축했다 */
    flex-wrap: nowrap;
    justify-content: flex-end;
    align-items: center;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.625rem;
    line-height: 1.4;
    text-align: right;
}
.card-legend > span {
    white-space: nowrap;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 3px;
}
.card-legend .ti {
    font-size: 11px;
}
/* 범례가 자리를 확보할 수 있도록 제목 블록이 먼저 줄어들게 한다 */
.card-header.header-legend > :first-child {
    min-width: 0;
}
.card-header.header-legend .card-title,
.card-header.header-legend > :first-child p {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* --------------------------------------------------------------------------
   순위 훈장 (1·2·3위)
   Tabler 의 award-filled 아이콘에 금·은·동 색을 준다.
   순위는 툴팁 문구에도 들어가므로 색만으로 등수를 전달하지 않는다.
   -------------------------------------------------------------------------- */
.rank {
    flex: 0 0 auto;
    width: 16px;
    font-size: 15px;
    line-height: 1;
    text-align: center;
}
.rank-1 {
    color: #e8ac16; /* 금 */
    filter: drop-shadow(0 1px 1px rgba(184, 134, 11, 0.35));
}
.rank-2 {
    color: #9fadbc; /* 은 */
    filter: drop-shadow(0 1px 1px rgba(120, 132, 148, 0.3));
}
.rank-3 {
    color: #c87c44; /* 동 */
    filter: drop-shadow(0 1px 1px rgba(150, 90, 45, 0.3));
}
/* 4위 이하는 아이콘 없이 같은 폭의 빈 자리만 둬서 이름 시작선을 맞춘다 */
.rank-empty {
    display: inline-block;
    width: 16px;
}

/* 이름 칸 = [메달][이름/부서] */
.dev-name-wrap {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    /* 훈장이 이름에 밀려 사라지지 않게 — 이름만 줄어들게 한다 */
    overflow: hidden;
}
.dev-name-wrap .dev-name {
    min-width: 0;
    flex: 0 1 auto;
}

/* --------------------------------------------------------------------------
   위젯 드래그 이동 (SortableJS)
   -------------------------------------------------------------------------- */
/* 이동 손잡이 — 사방향 화살표(ti-arrows-move)로 "옮길 수 있다"를 명확히 알린다.
   기존 grip(점 6개)은 의미가 약하고 눈에 띄지 않았다. */
.drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 28px;
    height: 28px;
    margin-right: 2px;
    cursor: grab;
    color: var(--theme-secondary-color);
    background: var(--theme-tertiary-bg, rgba(0, 0, 0, 0.04));
    border: 1px solid var(--theme-border-color);
    border-radius: 6px;
    font-size: 17px;
    line-height: 1;
    transition: color 0.15s, background-color 0.15s, border-color 0.15s;
}
.drag-handle:hover {
    color: #fff;
    background: var(--theme-primary);
    border-color: var(--theme-primary);
}
.drag-handle:active {
    cursor: grabbing;
}
/* 드래그 중 원래 자리에 남는 자리표시 */
.widget-ghost > .card {
    outline: 2px dashed var(--theme-primary);
    outline-offset: -2px;
    opacity: 0.45;
}
/* 실제로 끌려다니는 요소 */
.widget-drag > .card {
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
}
/* 드래그 중에는 카드 안 텍스트가 선택되지 않게 */
body.widget-dragging {
    user-select: none;
}
.stack-bar {
    display: flex;
    height: 16px;
    gap: 2px; /* 세그먼트 사이 배경색 간격 — 색이 붙어 보이지 않게 */
}
.stack-bar > span {
    display: block;
    height: 100%;
    border-radius: 3px;
    /* 극소값 가시성 — 2px 로는 실질적으로 안 보였다(실측: '배포' 1건 = 3px).
       범례에 색을 걸어둔 항목이 화면에서 사라지면 "없는 항목" 으로 읽히므로
       비율을 약간 과장하더라도 최소한 보이게 한다. 정확한 값은 항상 옆에
       숫자로 찍히므로 판독은 숫자가 담당한다. */
    min-width: 4px;
}
/* 등장 애니메이션 — ApexCharts 차트들과 느낌을 맞춘다.
   dashboard.js 의 animateBars() 가 폭 0 → 목표값으로 바꾼다. */
.bars-animate .stack-bar > span {
    transition: width 0.75s cubic-bezier(0.2, 0.75, 0.25, 1);
}
@media (prefers-reduced-motion: reduce) {
    .bars-animate .stack-bar > span {
        transition: none;
    }
}
/* 리드타임 바도 같은 방식으로 늘어나게 */
.lt-bar.bars-animate > span,
.lt-bar > span {
    transition: flex-grow 0.75s cubic-bezier(0.2, 0.75, 0.25, 1);
}

/* --------------------------------------------------------------------------
   단계별 리드타임 — 한 줄 스택
   -------------------------------------------------------------------------- */
.lt-bar {
    display: flex;
    height: 34px;
    gap: 2px;
    margin-bottom: 0.85rem;
}
.lt-bar > span {
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 700;
    color: #fff;
    min-width: 28px;
}

/* --------------------------------------------------------------------------
   AI 분석 요약 배너
   -------------------------------------------------------------------------- */
.ai-summary {
    border: 1px solid var(--theme-border-color);
    border-left: 3px solid var(--theme-primary);
    background: rgba(var(--theme-primary-rgb), 0.04);
}

/* ==========================================================================
   나의 업무 칸반
   Inspinia 의 .kanban-* 구조를 쓰되, 단계가 10개라 컬럼 폭을 줄인다
   (테마 기본값은 `.kanban-board{min-width:340px;width:320px!important}` →
    10개면 3,400px 이라 가로 스크롤만 길어진다).
   ========================================================================== */
.kb-content {
    padding: 0.5rem 0.25rem;
    /* 화면 높이에서 상단바·제목·조회조건·카드헤더를 뺀 만큼 */
    min-height: 420px;
}
/* ⚠ 선택자 주의 — 테마가 `.kanban-app .kanban-content .kanban-board`(0,3,0)에
   `width:320px!important; min-width:340px!important` 로 지정한다(실측).
   !important 끼리는 구체도로 갈리므로 같은 깊이 이상으로 맞춰야 이긴다. */
.kanban-app .kanban-content .kanban-board.kb-col {
    min-width: 236px !important;
    width: 236px !important;
}
/* ⚠ flex column + 아래 .kb-list 의 flex:1 이 핵심이다.
   ul 이 카드 높이만큼만 차지하면 **마지막 카드 아래 빈 공간이 드롭 대상 밖**이라
   컬럼 끝에 놓을 때 위치 표시도 안 뜨고 이동도 안 된다(실제 증상).
   리스트가 컬럼 높이를 끝까지 채워야 그 빈 공간도 드롭 영역이 된다. */
.kb-col .kanban-board-group {
    display: flex;
    flex-direction: column;
    height: calc(100vh - 400px);
    min-height: 320px;
    overflow-y: auto;
    scrollbar-width: thin;
}
.kb-col .kanban-board-group::-webkit-scrollbar {
    width: 6px;
}
.kb-col .kanban-board-group::-webkit-scrollbar-thumb {
    background: var(--theme-border-color);
    border-radius: 3px;
}
.kb-list {
    list-style: none;
    margin: 0;
    /* 남은 높이를 전부 차지해 '마지막 카드 아래'도 드롭 영역이 되게 한다.
       flex-basis:auto + grow:1 → 카드가 많으면 내용만큼 커지고(스크롤),
       적으면 컬럼 바닥까지 늘어난다. */
    flex: 1 0 auto;
    /* 바닥에 여유를 둬서 끝부분 드롭이 쉬워진다 */
    padding: 0 0 48px;
    min-height: 80px; /* 빈 컬럼용 최소 영역 */
}
/* 단계 표시 점 */
.kb-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
}
/* 요청구분 표시 점 */
.kb-fg {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    flex: none;
}
.kb-card {
    cursor: grab;
    border: 1px solid var(--theme-border-color);
    box-shadow: none;
    transition: box-shadow 0.15s, border-color 0.15s;
}
.kb-card:hover {
    border-color: var(--theme-primary);
    box-shadow: 0 2px 8px rgba(var(--theme-primary-rgb), 0.18);
}
.kb-card:active {
    cursor: grabbing;
}
.kb-title {
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1.35;
    /* 제목이 길어도 카드 높이가 들쭉날쭉하지 않게 2줄로 제한 */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* 드래그 중 원래 자리 */
.kb-ghost > .card {
    outline: 2px dashed var(--theme-primary);
    outline-offset: -2px;
    opacity: 0.4;
}
/* 드래그 중인 동안 각 컬럼의 드롭 가능 영역을 옅게 보여준다.
   특히 '마지막 카드 아래' 가 드롭 가능하다는 걸 알 수 있게. */
body.kb-dragging .kb-list {
    background: rgba(var(--theme-primary-rgb), 0.04);
    border: 1px dashed var(--theme-border-color);
    border-radius: 6px;
}
body.kb-dragging .kb-list.kb-over {
    background: rgba(var(--theme-primary-rgb), 0.1);
    border-color: var(--theme-primary);
}
.kb-drag > .card {
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.22);
}
body.kb-dragging {
    user-select: none;
}

/* --------------------------------------------------------------------------
   카드 상세 팝업
   -------------------------------------------------------------------------- */
.cm-table th.cm-th {
    width: 150px;
    font-weight: 600;
    font-size: 0.8125rem;
    color: var(--theme-secondary-color);
    background: var(--theme-tertiary-bg, rgba(0, 0, 0, 0.02));
    white-space: nowrap;
}
.cm-table td {
    font-size: 0.8125rem;
}
/* CLOB 내용 — 줄바꿈·공백을 그대로 살린다 */
.cm-pre {
    font-family: inherit;
    font-size: 0.8125rem;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
    background: var(--theme-tertiary-bg, rgba(0, 0, 0, 0.02));
    border: 1px solid var(--theme-border-color);
    border-radius: 6px;
    padding: 0.7rem 0.85rem;
    margin: 0;
}

/* --------------------------------------------------------------------------
   진행 흐름 (팝업 상단)
   노드(단계) 사이에 연결선을 두고, 지난 구간은 채우고 **현재 진행 중인 구간에는
   흐르는 애니메이션**을 준다(>>> 이동 느낌). 연결선 위에 구간 소요일수를 적어
   "어디서 시간이 걸렸는지" 가 바로 보이게 한다.
   -------------------------------------------------------------------------- */
.cm-flow {
    display: flex;
    align-items: flex-start;
    padding: 0.25rem 0 0;
    overflow-x: auto;
    scrollbar-width: thin;
}
.cm-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    flex: 0 0 auto;
    width: 62px;
    text-align: center;
    font-size: 0.6875rem;
    color: var(--theme-secondary-color);
}
.cm-node i {
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--theme-border-color);
    flex: none;
}
.cm-node em {
    font-style: normal;
    line-height: 1.2;
    word-break: keep-all;
}
.cm-node small {
    font-size: 0.625rem;
    opacity: 0.7;
    font-variant-numeric: tabular-nums;
    min-height: 0.9em; /* 날짜가 없는 단계도 높이를 유지해 줄이 흔들리지 않게 */
}
.cm-node.done i {
    background: var(--theme-primary);
    opacity: 0.5;
}
.cm-node.done em {
    color: var(--theme-body-color);
}
.cm-node.cur i {
    background: var(--theme-primary);
    box-shadow: 0 0 0 4px rgba(var(--theme-primary-rgb), 0.22);
    animation: cmPulse 1.8s ease-in-out infinite;
}
.cm-node.cur em {
    font-weight: 700;
    color: var(--theme-primary);
}

/* 연결선 — 노드의 점 높이에 맞춰 위쪽에 배치 */
.cm-link {
    position: relative;
    flex: 1 1 auto;
    min-width: 22px;
    height: 11px;
    margin: 0 -6px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: visible;
}
.cm-link::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 5px;
    height: 2px;
    background: var(--theme-border-color);
    border-radius: 1px;
}
.cm-link.done::before {
    background: rgba(var(--theme-primary-rgb), 0.5);
}
/* 현재 단계에서 다음 단계로 진행 중인 구간 — 빛이 흐르는 효과 */
.cm-link.active::before {
    background: linear-gradient(
        90deg,
        var(--theme-border-color) 0%,
        var(--theme-primary) 45%,
        var(--theme-primary) 55%,
        var(--theme-border-color) 100%
    );
    background-size: 220% 100%;
    animation: cmFlow 1.6s linear infinite;
}
/* 구간 소요일수 라벨 */
.cm-days {
    position: relative;
    top: 13px;
    font-size: 0.5625rem;
    line-height: 1;
    padding: 1px 4px;
    border-radius: 3px;
    background: var(--theme-secondary-bg);
    color: var(--theme-secondary-color);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

@keyframes cmFlow {
    from {
        background-position: 100% 0;
    }
    to {
        background-position: -100% 0;
    }
}
@keyframes cmPulse {
    0%,
    100% {
        box-shadow: 0 0 0 4px rgba(var(--theme-primary-rgb), 0.22);
    }
    50% {
        box-shadow: 0 0 0 7px rgba(var(--theme-primary-rgb), 0.08);
    }
}
/* 움직임을 줄이도록 설정한 사용자에게는 애니메이션을 끈다(정보는 그대로 남는다) */
@media (prefers-reduced-motion: reduce) {
    .cm-link.active::before,
    .cm-node.cur i {
        animation: none;
    }
    .cm-link.active::before {
        background: var(--theme-primary);
    }
}

/* 흐름 아래 요약 — 총 경과 · 납기 · 최장 구간 */
.cm-flow-sum {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    margin-top: 0.9rem;
    padding-top: 0.6rem;
    border-top: 1px dashed var(--theme-border-color);
    font-size: 0.75rem;
    color: var(--theme-secondary-color);
}

/* 이동 결과 알림 */
.kb-toast {
    position: fixed;
    left: 50%;
    bottom: 28px;
    transform: translate(-50%, 16px);
    z-index: 1080;
    display: flex;
    align-items: center;
    padding: 0.55rem 1rem;
    border-radius: 8px;
    background: var(--theme-secondary-bg);
    border: 1px solid var(--theme-border-color);
    border-left: 3px solid var(--theme-primary);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
    font-size: 0.8125rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s, transform 0.2s;
}
.kb-toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
}
@media (prefers-reduced-motion: reduce) {
    .kb-toast {
        transition: none;
    }
}

/* 시안 표시 배지 — 실데이터 연결 시 제거 */
.mock-badge {
    letter-spacing: 0.04em;
}


/* ==========================================================================
   상태 배지 — 테마 3계열 상태색 따라가기   [BADGE-THEME-V1]

   Inspinia 의 .badge-soft-* 는 자기 팔레트(#0acf97 / #f8ac59 / #ed5565)를 박아
   쓰므로 테마를 바꿔도 KPI 배지·목록 상태 배지만 색이 그대로다. 대시보드의
   나머지가 --chart-* 를 쓰는데 배지만 안 바뀌면 그게 더 어색하다.
   → success/warning/danger 만 --chart-done/alt/delay 로 맞춘다.
     info·secondary·dark 는 대응하는 테마 변수가 없어 그대로 둔다.

   ⚠ 첫 줄의 rgba() 는 color-mix 미지원 브라우저용 폴백이다(순서 유지).
     Inspinia 가 !important 를 쓰므로 여기서도 써야 이긴다.
   ========================================================================== */
.badge-soft-success {
    background-color: rgba(10, 207, 151, 0.18) !important;
    background-color: color-mix(in srgb, var(--chart-done) 18%, transparent) !important;
    color: var(--chart-done) !important;
}
.badge-soft-warning {
    background-color: rgba(248, 172, 89, 0.18) !important;
    background-color: color-mix(in srgb, var(--chart-alt) 18%, transparent) !important;
    color: var(--chart-alt) !important;
}
.badge-soft-danger {
    background-color: rgba(237, 85, 101, 0.18) !important;
    background-color: color-mix(in srgb, var(--chart-delay) 18%, transparent) !important;
    color: var(--chart-delay) !important;
}


/* ==========================================================================
   조회기간 범위 달력 드롭다운 (.dr-pop)   [DATERANGE-POP-V1]

   flatpickr 인라인 인스턴스 2개를 나란히 담는 자체 패널.
   showMonths:2 를 쓰지 않는 이유는 filters.js 주석 참고 — 화살표가 한 벌만
   생겨 두 달이 함께 움직이기 때문이다. 인스턴스를 분리하니 각 달력이
   자기 화살표를 갖는다.

   position:fixed + body 직속 — 조회조건 카드 안에 두면 카드의 overflow 에
   잘릴 수 있다. 위치는 filters.js 의 drPosition() 이 입력칸 기준으로 잡는다.
   ========================================================================== */
.dr-pop {
    position: fixed;
    z-index: 1060; /* dropdown(1000) 보다 위, modal(1055) 보다 위 */
    display: none;
    padding: 8px;
    background: var(--theme-secondary-bg);
    border: 1px solid var(--theme-border-color);
    border-radius: var(--dash-radius, 6px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
}
.dr-pop.show {
    display: block;
}
.dr-cals {
    display: flex;
    gap: 10px;
}
/* ⚠ flatpickr(inline) 은 달력을 호스트 **안**이 아니라 **형제**로 끼워 넣는다.
     그래서 호스트(.dr-cal)는 빈 자리만 차지하므로 숨기고, 달력은 .dr-pane 안에서 잡는다. */
.dr-cal {
    display: none;
}
/* 인라인 flatpickr 은 기본으로 그림자·테두리를 갖는다 → 패널 안에서는 뺀다 */
.dr-pane .flatpickr-calendar.inline {
    margin: 0;
    box-shadow: none;
    border: 0;
}
/* 두 달력 사이 구분선 — 각자 독립으로 움직인다는 것을 시각적으로 알린다 */
.dr-cals .dr-pane + .dr-pane {
    border-left: 1px solid var(--theme-border-color);
    padding-left: 10px;
}
.dr-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 4px;
    padding-top: 6px;
    border-top: 1px solid var(--theme-border-color);
}
.dr-hint {
    margin-right: auto;
    font-size: 0.75rem;
    color: var(--theme-primary);
}
.dr-foot .dr-done {
    padding: 0 4px;
    font-size: 0.75rem;
}
/* 달력 아이콘을 열기 버튼으로 쓴다 */
.input-group-text.dr-open {
    cursor: pointer;
}
.input-group-text.dr-open:hover {
    color: var(--theme-primary);
}
/* 직접 입력 가능해졌으므로 커서를 텍스트로 (readonly 시절의 pointer 규칙 무효화).
   ⚠ 고정폭 글꼴이 필요하다 — 마스크(`____-__-__`)의 밑줄과 숫자의 폭이 다르면
     타이핑할 때마다 글자가 좌우로 밀려 보인다. */
#dateRange {
    cursor: text;
    font-family: 'Cascadia Mono', Consolas, 'D2Coding', monospace;
    font-size: 0.78rem;
    letter-spacing: 0;
}


/* 발생 원인 분류 — 정상 배포분 요약 줄   [CAUSE-NORMAL-V1]
   막대에서 떼어낸 '개발(신규·개선 배포)' 를 숨기지 않고 카드 상단에 적는다.
   76% 를 막대에 그리면 나머지 6개가 눌려 안 보이므로 분리했지만,
   비중을 감추면 오독되기 때문에 값은 그대로 노출한다. */
.cause-normal {
    padding: 6px 10px;
    background: var(--theme-tertiary-bg);
    border-radius: var(--dash-radius, 6px);
    font-size: 0.8125rem;
}
.cause-normal b {
    font-variant-numeric: tabular-nums;
}
