﻿/*
 * 밀리AI 컴포넌트 스타일.
 * 1) Bootstrap 컴포넌트 재테마: 컴파일된 bootstrap.css가 컴포넌트별로 하드코딩한
 *    로컬 커스텀 프로퍼티(--bs-btn-bg 등)를 --mili-* 토큰으로 다시 선언한다.
 * 2) 밀리AI 전용 신규 컴포넌트: 히어로 배너, 섹션 헤더, 코스 카드, 인증 카드 등.
 * 자세한 사용법은 개인 디자인 시스템 문서 참고.
 */

/* ===== 0) 수치 서체 유틸리티 =====
   시안은 대시보드에서 **홀로 읽히는 숫자**(레벨·점수·진행률·연속 학습일)에만 Orbitron 을 쓴다.
   날짜·차시·"3일차" 처럼 한글 문맥 안에서 읽는 숫자에는 쓰지 않는다(DESIGN.md 규약).
   굵기는 이 클래스가 정하지 않고 붙이는 컴포넌트가 정한다.

   ⚠️ 의도적 이탈 — 참고 775fcfa 와 다르다.
      참고 478db27 이 .mili-metric 에서 Orbitron 을 떼어 본문 서체 + tabular-nums 로 바꾸고,
      Orbitron 은 .mili-numeric-title(2.5rem/700 고정) 이라는 새 클래스로 옮겼다.
      그런데 **참고 TSX 어디에도 .mili-numeric-title 을 붙인 곳이 없다**(사용처 0건) —
      즉 참고의 렌더 결과에서는 Orbitron 이 통째로 사라진 상태이고, 이는 전환 중간 단계로 보인다.
      그 중간 상태를 따라가면 우리 5개 화면(PBL·대시보드·온보딩·검색·수료증)의 수치 서체가
      한 번 빠졌다가 참고가 .mili-numeric-title 을 붙이는 순간 되돌아와야 한다.
      참고가 어디에 붙이는지 확인될 때까지 이 한 줄로 Orbitron 을 유지한다.
      → 되돌릴 때는 이 규칙만 지우면 theme/reference.css 가 그대로 정본이 된다. */
.mili-metric {
    font-family: var(--mili-font-metric);
}

/* ===== 1) Bootstrap 컴포넌트 재테마 ===== */

/* ---------- 버튼 — 정본은 theme/figma-classes.css 다 ----------
   2026-08-27 버튼 표준 일괄적용 1단계. 이 자리에 있던 부트스트랩 버튼 재테마
   (.btn / .btn-sm / .btn-lg / .btn-primary / .btn-danger / .btn-link, 그리고 아래 §"Jalynne
   공통 시각 보정"에 있던 .mili-theme-dark|.mili-light .btn-primary 오버라이드)를 전부 걷어냈다.

   근거 — .btn-primary 의 실측값이 theme/figma-classes.css `.btn.default` 와 **픽셀 단위로
   동일**했다(다크 #FFFFFF/#F0F0F0/#000000 = neutral-50/300/950, 라이트 #000000/#1C1F23/#FFFFFF
   = neutral-950/850/50). 같은 모양의 버튼이 클래스명만 두 갈래로 남아 있던 상태라, 사용자
   확인(2026-08-27)에 따라 Figma 카탈로그 쪽으로 통일하고 이쪽을 삭제한다. 크기도 .btn-sm/.btn-lg
   대신 `.btn.small`(28px)/`.btn.medium`(36px)/`.btn.large`(52px) 세 칸만 쓴다.

   ⚠️ 여기서 --bs-btn-* 를 다시 선언하지 말 것 — `.btn` 하나만 붙은 버튼은 이제 없다(모든
   사용처가 크기+변형 클래스를 함께 붙인다). 부트스트랩 원본 .btn-primary(파랑) 노출 위험도
   사용처 0건이라 없다. 새 변형이 필요하면 figma-classes.css 에 Figma 실측으로 추가한다.

   .btn-close(부트스트랩 alert 닫기)만 예외로 남아 있다 — 버튼 변형이 아니라 아이콘 컨트롤이라
   Figma 버튼 카탈로그에 대응이 없다(아래 §"Jalynne 공통 시각 보정"의 .btn-close 규칙 참고). */

/*
 * 폼 컨트롤 모서리 반경 (2026-08-27 사용자 지시 "전반적인 폼 요소에 테두리 라운드 적용").
 *
 * 입력 컨트롤 슬롯은 docs/design-tokens.md 가 --mili-radius-control(10px)로 이미 예약해 둔
 * 칸이라 새 값을 만들지 않고 그것을 쓴다. Figma 실측은 4px(borderRadiusSM)이지만 그 값은
 * 우리 토큰표에 칸이 없고, 리터럴을 박으면 화면마다 입력 반경이 갈라지는 원인이 된다.
 *
 * --bs-border-radius(전역 0)를 되살리지 않는 이유: 그 변수는 .alert/.modal/.dropdown/.card 등
 * Bootstrap 컴포넌트 전부가 함께 읽어서, 폼 하나 둥글리려다 앱 전 화면의 판·경고·모달 반경이
 * 동시에 바뀐다. 그래서 폼 컨트롤 클래스만 짚어서 준다.
 *
 * .form-check-input(체크박스·라디오)은 제외한다 — 라디오는 원형이어야 하고 체크박스는 이미 4px 다.
 */
:is(.form-control, .form-select, textarea.form-control, .mili-selector__input-wrap, .mili-selector__list) {
    border-radius: var(--mili-radius-control);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--mili-primary-border);
    box-shadow: 0 0 0 0.25rem rgba(var(--mili-focus-rgb), 0.25);
}

/* QA — 라이트 모드에서 체크 전 상태가 --mili-border(연한 회색)만 물려받아 시인성이 떨어짐.
   .mili-light 스코프에서만 --mili-border-strong 으로 테두리를 한 단계 진하게 잡는다. */
.mili-light .form-check-input {
    border-color: var(--mili-border-strong);
}

.form-check-input:checked {
    background-color: var(--mili-primary);
    border-color: var(--mili-primary);
}

.form-check-input:focus {
    border-color: var(--mili-primary-border);
    box-shadow: 0 0 0 0.25rem rgba(var(--mili-focus-rgb), 0.25);
}

.pagination {
    --bs-pagination-active-bg: var(--mili-primary);
    --bs-pagination-active-border-color: var(--mili-primary);
    --bs-pagination-active-color: var(--mili-primary-on);
}

.page-link {
    border-radius: var(--mili-radius-button);
    margin: 0 0.125rem;
}

.progress {
    --bs-progress-bar-bg: var(--mili-primary);
    border-radius: var(--mili-radius-pill);
}

/* 화면별 고정 높이 — 예전엔 style="height:…" 인라인으로 박았다(CSP style-src 를
   'unsafe-inline' 없이 굳히려고 2026-09-17 클래스로 옮김, 값은 각 화면 Figma 스펙 그대로). */
.progress--h-2px { height: 2px; }
.progress--h-6 { height: 0.375rem; }
.progress--h-8 { height: 0.5rem; }

.card {
    border-color: var(--mili-border);
    box-shadow: var(--mili-shadow-sm);
}

/* ===== 4) 섹션 헤더 ===== */

.section-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: var(--mili-space-5);
}

.section-header__title {
    font-weight: 800;
    letter-spacing: -0.01em;
    margin: 0;
}

/* ===== 5) 코스 카드 ===== */

/*
 * 과정·프로젝트에는 개별 대표 이미지가 없어 공용 배경 40장(images/course-bg/1~40.webp)을 돌려 쓴다.
 * 어느 장을 쓸지는 서버(user.catalog.CardThumbnail)가 과정번호 % 40 으로 정해 --N 클래스로 내려주므로,
 * 같은 과정은 어느 화면에서도 늘 같은 배경이 나온다. 카드 컨테이너 자체는 catalog-card/pbl-card
 * (화면별 css)로 대체되어 여기 남은 건 배경 변수 지정자(--card-bg)뿐이다.
 */
.course-card__thumb--1 { --card-bg: url("../../images/course-bg/1.webp"); }
.course-card__thumb--2 { --card-bg: url("../../images/course-bg/2.webp"); }
.course-card__thumb--3 { --card-bg: url("../../images/course-bg/3.webp"); }
.course-card__thumb--4 { --card-bg: url("../../images/course-bg/4.webp"); }
.course-card__thumb--5 { --card-bg: url("../../images/course-bg/5.webp"); }
.course-card__thumb--6 { --card-bg: url("../../images/course-bg/6.webp"); }
.course-card__thumb--7 { --card-bg: url("../../images/course-bg/7.webp"); }
.course-card__thumb--8 { --card-bg: url("../../images/course-bg/8.webp"); }
.course-card__thumb--9 { --card-bg: url("../../images/course-bg/9.webp"); }
.course-card__thumb--10 { --card-bg: url("../../images/course-bg/10.webp"); }
.course-card__thumb--11 { --card-bg: url("../../images/course-bg/11.webp"); }
.course-card__thumb--12 { --card-bg: url("../../images/course-bg/12.webp"); }
.course-card__thumb--13 { --card-bg: url("../../images/course-bg/13.webp"); }
.course-card__thumb--14 { --card-bg: url("../../images/course-bg/14.webp"); }
.course-card__thumb--15 { --card-bg: url("../../images/course-bg/15.webp"); }
.course-card__thumb--16 { --card-bg: url("../../images/course-bg/16.webp"); }
.course-card__thumb--17 { --card-bg: url("../../images/course-bg/17.webp"); }
.course-card__thumb--18 { --card-bg: url("../../images/course-bg/18.webp"); }
.course-card__thumb--19 { --card-bg: url("../../images/course-bg/19.webp"); }
.course-card__thumb--20 { --card-bg: url("../../images/course-bg/20.webp"); }
.course-card__thumb--21 { --card-bg: url("../../images/course-bg/21.webp"); }
.course-card__thumb--22 { --card-bg: url("../../images/course-bg/22.webp"); }
.course-card__thumb--23 { --card-bg: url("../../images/course-bg/23.webp"); }
.course-card__thumb--24 { --card-bg: url("../../images/course-bg/24.webp"); }
.course-card__thumb--25 { --card-bg: url("../../images/course-bg/25.webp"); }
.course-card__thumb--26 { --card-bg: url("../../images/course-bg/26.webp"); }
.course-card__thumb--27 { --card-bg: url("../../images/course-bg/27.webp"); }
.course-card__thumb--28 { --card-bg: url("../../images/course-bg/28.webp"); }
.course-card__thumb--29 { --card-bg: url("../../images/course-bg/29.webp"); }
.course-card__thumb--30 { --card-bg: url("../../images/course-bg/30.webp"); }
.course-card__thumb--31 { --card-bg: url("../../images/course-bg/31.webp"); }
.course-card__thumb--32 { --card-bg: url("../../images/course-bg/32.webp"); }
.course-card__thumb--33 { --card-bg: url("../../images/course-bg/33.webp"); }
.course-card__thumb--34 { --card-bg: url("../../images/course-bg/34.webp"); }
.course-card__thumb--35 { --card-bg: url("../../images/course-bg/35.webp"); }
.course-card__thumb--36 { --card-bg: url("../../images/course-bg/36.webp"); }
.course-card__thumb--37 { --card-bg: url("../../images/course-bg/37.webp"); }
.course-card__thumb--38 { --card-bg: url("../../images/course-bg/38.webp"); }
.course-card__thumb--39 { --card-bg: url("../../images/course-bg/39.webp"); }
.course-card__thumb--40 { --card-bg: url("../../images/course-bg/40.webp"); }

/* 도메인별 전용 배경 풀(2026-08-28) — 강의/프로젝트/로드맵이 더 이상 위 공용 40장을
 * 공유하지 않고 각자 폴더(thumb-vod/thumb-project/thumb-roadmap)에서 고른다.
 * 인덱스 산출은 user.catalog.CardThumbnail.indexOf(Domain, seed) — 도메인별 장수로
 * 나눈 나머지라 같은 PK는 항상 같은 사진이 나온다. 위 40장 클래스는 당장은 유지한다. */
.course-card__thumb--vod-1 { --card-bg: url("../../images/thumb-vod/1.webp"); }
.course-card__thumb--vod-2 { --card-bg: url("../../images/thumb-vod/2.webp"); }
.course-card__thumb--vod-3 { --card-bg: url("../../images/thumb-vod/3.webp"); }
.course-card__thumb--vod-4 { --card-bg: url("../../images/thumb-vod/4.webp"); }
.course-card__thumb--vod-5 { --card-bg: url("../../images/thumb-vod/5.webp"); }
.course-card__thumb--vod-6 { --card-bg: url("../../images/thumb-vod/6.webp"); }
.course-card__thumb--vod-7 { --card-bg: url("../../images/thumb-vod/7.webp"); }
.course-card__thumb--vod-8 { --card-bg: url("../../images/thumb-vod/8.webp"); }
.course-card__thumb--vod-9 { --card-bg: url("../../images/thumb-vod/9.webp"); }
.course-card__thumb--vod-10 { --card-bg: url("../../images/thumb-vod/10.webp"); }
.course-card__thumb--vod-11 { --card-bg: url("../../images/thumb-vod/11.webp"); }
.course-card__thumb--vod-12 { --card-bg: url("../../images/thumb-vod/12.webp"); }
.course-card__thumb--vod-13 { --card-bg: url("../../images/thumb-vod/13.webp"); }
.course-card__thumb--vod-14 { --card-bg: url("../../images/thumb-vod/14.webp"); }
.course-card__thumb--vod-15 { --card-bg: url("../../images/thumb-vod/15.webp"); }
.course-card__thumb--vod-16 { --card-bg: url("../../images/thumb-vod/16.webp"); }
.course-card__thumb--vod-17 { --card-bg: url("../../images/thumb-vod/17.webp"); }
.course-card__thumb--vod-18 { --card-bg: url("../../images/thumb-vod/18.webp"); }
.course-card__thumb--vod-19 { --card-bg: url("../../images/thumb-vod/19.webp"); }
.course-card__thumb--vod-20 { --card-bg: url("../../images/thumb-vod/20.webp"); }
.course-card__thumb--vod-21 { --card-bg: url("../../images/thumb-vod/21.webp"); }
.course-card__thumb--vod-22 { --card-bg: url("../../images/thumb-vod/22.webp"); }
.course-card__thumb--vod-23 { --card-bg: url("../../images/thumb-vod/23.webp"); }
.course-card__thumb--vod-24 { --card-bg: url("../../images/thumb-vod/24.webp"); }
.course-card__thumb--vod-25 { --card-bg: url("../../images/thumb-vod/25.webp"); }
.course-card__thumb--vod-26 { --card-bg: url("../../images/thumb-vod/26.webp"); }
.course-card__thumb--vod-27 { --card-bg: url("../../images/thumb-vod/27.webp"); }
.course-card__thumb--vod-28 { --card-bg: url("../../images/thumb-vod/28.webp"); }
.course-card__thumb--vod-29 { --card-bg: url("../../images/thumb-vod/29.webp"); }
.course-card__thumb--vod-30 { --card-bg: url("../../images/thumb-vod/30.webp"); }
.course-card__thumb--vod-31 { --card-bg: url("../../images/thumb-vod/31.webp"); }
.course-card__thumb--vod-32 { --card-bg: url("../../images/thumb-vod/32.webp"); }
.course-card__thumb--vod-33 { --card-bg: url("../../images/thumb-vod/33.webp"); }
.course-card__thumb--vod-34 { --card-bg: url("../../images/thumb-vod/34.webp"); }
.course-card__thumb--vod-35 { --card-bg: url("../../images/thumb-vod/35.webp"); }
.course-card__thumb--vod-36 { --card-bg: url("../../images/thumb-vod/36.webp"); }
.course-card__thumb--vod-37 { --card-bg: url("../../images/thumb-vod/37.webp"); }
.course-card__thumb--vod-38 { --card-bg: url("../../images/thumb-vod/38.webp"); }
.course-card__thumb--vod-39 { --card-bg: url("../../images/thumb-vod/39.webp"); }
.course-card__thumb--vod-40 { --card-bg: url("../../images/thumb-vod/40.webp"); }
.course-card__thumb--vod-41 { --card-bg: url("../../images/thumb-vod/41.webp"); }
.course-card__thumb--vod-42 { --card-bg: url("../../images/thumb-vod/42.webp"); }
.course-card__thumb--vod-43 { --card-bg: url("../../images/thumb-vod/43.webp"); }
.course-card__thumb--vod-44 { --card-bg: url("../../images/thumb-vod/44.webp"); }
.course-card__thumb--vod-45 { --card-bg: url("../../images/thumb-vod/45.webp"); }
.course-card__thumb--vod-46 { --card-bg: url("../../images/thumb-vod/46.webp"); }
.course-card__thumb--vod-47 { --card-bg: url("../../images/thumb-vod/47.webp"); }
.course-card__thumb--vod-48 { --card-bg: url("../../images/thumb-vod/48.webp"); }
.course-card__thumb--vod-49 { --card-bg: url("../../images/thumb-vod/49.webp"); }
.course-card__thumb--vod-50 { --card-bg: url("../../images/thumb-vod/50.webp"); }
.course-card__thumb--vod-51 { --card-bg: url("../../images/thumb-vod/51.webp"); }
.course-card__thumb--vod-52 { --card-bg: url("../../images/thumb-vod/52.webp"); }
.course-card__thumb--vod-53 { --card-bg: url("../../images/thumb-vod/53.webp"); }
.course-card__thumb--vod-54 { --card-bg: url("../../images/thumb-vod/54.webp"); }
.course-card__thumb--vod-55 { --card-bg: url("../../images/thumb-vod/55.webp"); }
.course-card__thumb--vod-56 { --card-bg: url("../../images/thumb-vod/56.webp"); }
.course-card__thumb--vod-57 { --card-bg: url("../../images/thumb-vod/57.webp"); }
.course-card__thumb--vod-58 { --card-bg: url("../../images/thumb-vod/58.webp"); }

.course-card__thumb--pbl-1 { --card-bg: url("../../images/thumb-project/1.webp"); }
.course-card__thumb--pbl-2 { --card-bg: url("../../images/thumb-project/2.webp"); }
.course-card__thumb--pbl-3 { --card-bg: url("../../images/thumb-project/3.webp"); }
.course-card__thumb--pbl-4 { --card-bg: url("../../images/thumb-project/4.webp"); }
.course-card__thumb--pbl-5 { --card-bg: url("../../images/thumb-project/5.webp"); }
.course-card__thumb--pbl-6 { --card-bg: url("../../images/thumb-project/6.webp"); }
.course-card__thumb--pbl-7 { --card-bg: url("../../images/thumb-project/7.webp"); }
.course-card__thumb--pbl-8 { --card-bg: url("../../images/thumb-project/8.webp"); }
.course-card__thumb--pbl-9 { --card-bg: url("../../images/thumb-project/9.webp"); }
.course-card__thumb--pbl-10 { --card-bg: url("../../images/thumb-project/10.webp"); }
.course-card__thumb--pbl-11 { --card-bg: url("../../images/thumb-project/11.webp"); }
.course-card__thumb--pbl-12 { --card-bg: url("../../images/thumb-project/12.webp"); }
.course-card__thumb--pbl-13 { --card-bg: url("../../images/thumb-project/13.webp"); }
.course-card__thumb--pbl-14 { --card-bg: url("../../images/thumb-project/14.webp"); }
.course-card__thumb--pbl-15 { --card-bg: url("../../images/thumb-project/15.webp"); }
.course-card__thumb--pbl-16 { --card-bg: url("../../images/thumb-project/16.webp"); }
.course-card__thumb--pbl-17 { --card-bg: url("../../images/thumb-project/17.webp"); }
.course-card__thumb--pbl-18 { --card-bg: url("../../images/thumb-project/18.webp"); }
.course-card__thumb--pbl-19 { --card-bg: url("../../images/thumb-project/19.webp"); }
.course-card__thumb--pbl-20 { --card-bg: url("../../images/thumb-project/20.webp"); }
.course-card__thumb--pbl-21 { --card-bg: url("../../images/thumb-project/21.webp"); }
.course-card__thumb--pbl-22 { --card-bg: url("../../images/thumb-project/22.webp"); }
.course-card__thumb--pbl-23 { --card-bg: url("../../images/thumb-project/23.webp"); }
.course-card__thumb--pbl-24 { --card-bg: url("../../images/thumb-project/24.webp"); }
.course-card__thumb--pbl-25 { --card-bg: url("../../images/thumb-project/25.webp"); }
.course-card__thumb--pbl-26 { --card-bg: url("../../images/thumb-project/26.webp"); }
.course-card__thumb--pbl-27 { --card-bg: url("../../images/thumb-project/27.webp"); }
.course-card__thumb--pbl-28 { --card-bg: url("../../images/thumb-project/28.webp"); }
.course-card__thumb--pbl-29 { --card-bg: url("../../images/thumb-project/29.webp"); }
.course-card__thumb--pbl-30 { --card-bg: url("../../images/thumb-project/30.webp"); }
.course-card__thumb--pbl-31 { --card-bg: url("../../images/thumb-project/31.webp"); }
.course-card__thumb--pbl-32 { --card-bg: url("../../images/thumb-project/32.webp"); }
.course-card__thumb--pbl-33 { --card-bg: url("../../images/thumb-project/33.webp"); }
.course-card__thumb--pbl-34 { --card-bg: url("../../images/thumb-project/34.webp"); }
.course-card__thumb--pbl-35 { --card-bg: url("../../images/thumb-project/35.webp"); }
.course-card__thumb--pbl-36 { --card-bg: url("../../images/thumb-project/36.webp"); }
.course-card__thumb--pbl-37 { --card-bg: url("../../images/thumb-project/37.webp"); }
.course-card__thumb--pbl-38 { --card-bg: url("../../images/thumb-project/38.webp"); }
.course-card__thumb--pbl-39 { --card-bg: url("../../images/thumb-project/39.webp"); }
.course-card__thumb--pbl-40 { --card-bg: url("../../images/thumb-project/40.webp"); }
.course-card__thumb--pbl-41 { --card-bg: url("../../images/thumb-project/41.webp"); }
.course-card__thumb--pbl-42 { --card-bg: url("../../images/thumb-project/42.webp"); }
.course-card__thumb--pbl-43 { --card-bg: url("../../images/thumb-project/43.webp"); }
.course-card__thumb--pbl-44 { --card-bg: url("../../images/thumb-project/44.webp"); }
.course-card__thumb--pbl-45 { --card-bg: url("../../images/thumb-project/45.webp"); }
.course-card__thumb--pbl-46 { --card-bg: url("../../images/thumb-project/46.webp"); }
.course-card__thumb--pbl-47 { --card-bg: url("../../images/thumb-project/47.webp"); }
.course-card__thumb--pbl-48 { --card-bg: url("../../images/thumb-project/48.webp"); }
.course-card__thumb--pbl-49 { --card-bg: url("../../images/thumb-project/49.webp"); }
.course-card__thumb--pbl-50 { --card-bg: url("../../images/thumb-project/50.webp"); }
.course-card__thumb--pbl-51 { --card-bg: url("../../images/thumb-project/51.webp"); }
.course-card__thumb--pbl-52 { --card-bg: url("../../images/thumb-project/52.webp"); }
.course-card__thumb--pbl-53 { --card-bg: url("../../images/thumb-project/53.webp"); }
.course-card__thumb--pbl-54 { --card-bg: url("../../images/thumb-project/54.webp"); }
.course-card__thumb--pbl-55 { --card-bg: url("../../images/thumb-project/55.webp"); }
.course-card__thumb--pbl-56 { --card-bg: url("../../images/thumb-project/56.webp"); }

.course-card__thumb--roadmap-1 { --card-bg: url("../../images/thumb-roadmap/1.webp"); }
.course-card__thumb--roadmap-2 { --card-bg: url("../../images/thumb-roadmap/2.webp"); }
.course-card__thumb--roadmap-3 { --card-bg: url("../../images/thumb-roadmap/3.webp"); }
.course-card__thumb--roadmap-4 { --card-bg: url("../../images/thumb-roadmap/4.webp"); }
.course-card__thumb--roadmap-5 { --card-bg: url("../../images/thumb-roadmap/5.webp"); }
.course-card__thumb--roadmap-6 { --card-bg: url("../../images/thumb-roadmap/6.webp"); }
.course-card__thumb--roadmap-7 { --card-bg: url("../../images/thumb-roadmap/7.webp"); }
.course-card__thumb--roadmap-8 { --card-bg: url("../../images/thumb-roadmap/8.webp"); }
.course-card__thumb--roadmap-9 { --card-bg: url("../../images/thumb-roadmap/9.webp"); }
.course-card__thumb--roadmap-10 { --card-bg: url("../../images/thumb-roadmap/10.webp"); }
.course-card__thumb--roadmap-11 { --card-bg: url("../../images/thumb-roadmap/11.webp"); }
.course-card__thumb--roadmap-12 { --card-bg: url("../../images/thumb-roadmap/12.webp"); }
.course-card__thumb--roadmap-13 { --card-bg: url("../../images/thumb-roadmap/13.webp"); }
.course-card__thumb--roadmap-14 { --card-bg: url("../../images/thumb-roadmap/14.webp"); }
.course-card__thumb--roadmap-15 { --card-bg: url("../../images/thumb-roadmap/15.webp"); }
.course-card__thumb--roadmap-16 { --card-bg: url("../../images/thumb-roadmap/16.webp"); }
.course-card__thumb--roadmap-17 { --card-bg: url("../../images/thumb-roadmap/17.webp"); }
.course-card__thumb--roadmap-18 { --card-bg: url("../../images/thumb-roadmap/18.webp"); }
.course-card__thumb--roadmap-19 { --card-bg: url("../../images/thumb-roadmap/19.webp"); }
.course-card__thumb--roadmap-20 { --card-bg: url("../../images/thumb-roadmap/20.webp"); }
.course-card__thumb--roadmap-21 { --card-bg: url("../../images/thumb-roadmap/21.webp"); }
.course-card__thumb--roadmap-22 { --card-bg: url("../../images/thumb-roadmap/22.webp"); }
.course-card__thumb--roadmap-23 { --card-bg: url("../../images/thumb-roadmap/23.webp"); }
.course-card__thumb--roadmap-24 { --card-bg: url("../../images/thumb-roadmap/24.webp"); }
.course-card__thumb--roadmap-25 { --card-bg: url("../../images/thumb-roadmap/25.webp"); }
.course-card__thumb--roadmap-26 { --card-bg: url("../../images/thumb-roadmap/26.webp"); }
.course-card__thumb--roadmap-27 { --card-bg: url("../../images/thumb-roadmap/27.webp"); }
.course-card__thumb--roadmap-28 { --card-bg: url("../../images/thumb-roadmap/28.webp"); }
.course-card__thumb--roadmap-29 { --card-bg: url("../../images/thumb-roadmap/29.webp"); }
.course-card__thumb--roadmap-30 { --card-bg: url("../../images/thumb-roadmap/30.webp"); }
.course-card__thumb--roadmap-31 { --card-bg: url("../../images/thumb-roadmap/31.webp"); }
.course-card__thumb--roadmap-32 { --card-bg: url("../../images/thumb-roadmap/32.webp"); }
.course-card__thumb--roadmap-33 { --card-bg: url("../../images/thumb-roadmap/33.webp"); }
.course-card__thumb--roadmap-34 { --card-bg: url("../../images/thumb-roadmap/34.webp"); }
.course-card__thumb--roadmap-35 { --card-bg: url("../../images/thumb-roadmap/35.webp"); }
.course-card__thumb--roadmap-36 { --card-bg: url("../../images/thumb-roadmap/36.webp"); }
.course-card__thumb--roadmap-37 { --card-bg: url("../../images/thumb-roadmap/37.webp"); }
.course-card__thumb--roadmap-38 { --card-bg: url("../../images/thumb-roadmap/38.webp"); }
.course-card__thumb--roadmap-39 { --card-bg: url("../../images/thumb-roadmap/39.webp"); }
.course-card__thumb--roadmap-40 { --card-bg: url("../../images/thumb-roadmap/40.webp"); }
.course-card__thumb--roadmap-41 { --card-bg: url("../../images/thumb-roadmap/41.webp"); }
.course-card__thumb--roadmap-42 { --card-bg: url("../../images/thumb-roadmap/42.webp"); }
.course-card__thumb--roadmap-43 { --card-bg: url("../../images/thumb-roadmap/43.webp"); }
.course-card__thumb--roadmap-44 { --card-bg: url("../../images/thumb-roadmap/44.webp"); }
.course-card__thumb--roadmap-45 { --card-bg: url("../../images/thumb-roadmap/45.webp"); }
.course-card__thumb--roadmap-46 { --card-bg: url("../../images/thumb-roadmap/46.webp"); }
.course-card__thumb--roadmap-47 { --card-bg: url("../../images/thumb-roadmap/47.webp"); }
.course-card__thumb--roadmap-48 { --card-bg: url("../../images/thumb-roadmap/48.webp"); }
.course-card__thumb--roadmap-49 { --card-bg: url("../../images/thumb-roadmap/49.webp"); }
.course-card__thumb--roadmap-50 { --card-bg: url("../../images/thumb-roadmap/50.webp"); }
.course-card__thumb--roadmap-51 { --card-bg: url("../../images/thumb-roadmap/51.webp"); }
.course-card__thumb--roadmap-52 { --card-bg: url("../../images/thumb-roadmap/52.webp"); }
.course-card__thumb--roadmap-53 { --card-bg: url("../../images/thumb-roadmap/53.webp"); }
.course-card__thumb--roadmap-54 { --card-bg: url("../../images/thumb-roadmap/54.webp"); }
.course-card__thumb--roadmap-55 { --card-bg: url("../../images/thumb-roadmap/55.webp"); }
.course-card__thumb--roadmap-56 { --card-bg: url("../../images/thumb-roadmap/56.webp"); }
.course-card__thumb--roadmap-57 { --card-bg: url("../../images/thumb-roadmap/57.webp"); }

/* 썸네일 위 뱃지 줄(좌: 분류/NEW, 우: 수강 인원). */
.course-card__flags {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.375rem;
    flex-wrap: wrap;
}

.course-card__badge {
    background: rgba(0, 0, 0, 0.45);
    color: var(--mili-neutral-50);
    font-size: var(--mili-caption-size);
    font-weight: var(--mili-font-weight-semibold);
    padding: 0.25rem 0.625rem;
    border-radius: var(--mili-radius-pill);
    backdrop-filter: blur(2px);
    white-space: nowrap;
}

.course-card__badge--enrolled {
    background: var(--mili-primary);
    color: var(--mili-neutral-50);
}

.course-card__badge--done {
    background: var(--mili-success);
    color: var(--mili-neutral-50);
}

/* 찜 하트 — 정사각형 아이콘 버튼. */
.wish-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.375rem;
    height: 2.375rem;
    padding: 0;
    border: 1px solid var(--mili-border);
    border-radius: 0;
    background: var(--mili-surface);
    color: var(--mili-muted);
    font-size: var(--mili-body-b2-size);
    line-height: 1;
    cursor: pointer;
}

.wish-btn:hover {
    border-color: var(--mili-danger);
    color: var(--mili-danger);
}

.wish-btn--on {
    border-color: var(--mili-danger);
    color: var(--mili-danger);
}

/* 학습레벨 게이지 — 전체 레벨 개수만큼 눈금을 찍고 과정의 레벨 구간만 채운다. */
.level-gauge {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.1875rem;
}

.level-gauge__step {
    width: 0.875rem;
    height: 0.3125rem;
    border-radius: var(--mili-radius-pill);
    background: var(--mili-heat-0);
}

.level-gauge__step--on {
    background: var(--mili-primary);
}

/* ===== 5-1) 페이지 헤더 (제목) ===== */

.page-header {
    margin-bottom: var(--mili-space-5);
}

.page-header__title {
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--mili-ink);
    margin-bottom: var(--mili-space-1);
}

.page-header__lead {
    margin: 0;
    color: var(--mili-muted);
}

/* ===== 5-2') 공통 페이지 헤더(hero) — 목록형 화면 정본 =====

   Figma "PC 공통_타이틀/설명/검색"(node 1354-232) + 커뮤니티 실화면(1497-3009) 이식본.
   가운데 정렬 제목 + 설명 + (선택) 검색 필드 한 벌이다. 마크업은 views/common/_page-hero.jsp
   하나가 만든다 — 화면마다 복붙하지 않는다.

   위 .page-header(왼쪽 정렬 + 브레드크럼)와 역할이 다르다:
     .page-hero    목록형(카탈로그·게시판) — 가운데 정렬, 브레드크럼 없음
     .page-header  폼·정책·안내처럼 문서형으로 읽는 화면 — 왼쪽 정렬, 브레드크럼 있음
   섞어 쓰지 말 것. 목록형 판별과 적용 대상은 CLAUDE.md 에 표로 있다.

   시안에 브레드크럼이 없는 이유: 목록형 화면은 전역 헤더에도 제목·브레드크럼을 두지 않고
   .page-hero 하나로 위치를 알린다(Figma 실측, 2026-08-11 확인 — _app-topbar.jsp 주석 참고). */
.page-hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: var(--mili-hero-top-gap) 0 var(--mili-hero-block-gap);
    text-align: center;
}

/* 회원가입·아이디/비밀번호 찾기 전용(variables.css 의 --mili-hero-block-gap-lg 주석 참고) —
   목록형 화면과 같은 .page-hero 를 쓰되 히어로→본문 간격만 60px 로 넓힌다. */
.page-hero--form-intro {
    margin-bottom: var(--mili-hero-block-gap-lg);
}

.page-hero__title {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--mili-space-2);
    margin: 0;
    color: var(--mili-ink);
    font-size: var(--mili-hero-title-size);
    font-weight: var(--mili-font-weight-bold);
    line-height: var(--mili-hero-title-line);
    /* 2026-08-25 정정 — Figma Typography 시트(H1 행)는 전 Heading/Body 공통으로 -2% 를 쓰는데
       여기만 그 절반인 -1% 였다(오탈자로 추정, docs/design/figma-common/02-typography.md 질문4). */
    letter-spacing: -0.02em;
}

/* 2026-08-12 추가(node 1653:2496 라이트 · 1653:4184 다크): 제목 앞 아이콘. heroIcon 을 세팅한
   화면(VOD·PBL)에만 나온다 — 마크업은 _page-hero.jsp, SVG 는 화면별 JSP 가 넘긴다. 40px 고정,
   stroke 는 currentColor 라 .page-hero__title 의 색(다크 --mili-ink)을 그대로 물려받는다. */
.page-hero__icon {
    display: inline-flex;
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    color: var(--mili-brand);
}

.page-hero__icon svg {
    width: 100%;
    height: 100%;
}

.mili-light .page-hero__icon {
    color: var(--mili-brand);
}

/* "전체 강의 1,234개" 의 수치 부분. */
.page-hero__count {
    color: var(--mili-brand);
}

/* "VOD 강의" 처럼 제목 앞에 붙는 브랜드색 단어(Figma node 1496:354). */
.page-hero__title-prefix {
    color: var(--mili-brand);
}

/* ===== 카탈로그 개수 라벨 =====
   히어로 제목과 별개로 카드 그리드 바로 위에 한 번 더 나오는 "… N개"
   (VOD node 1496:366 "전체 강의 N개", PBL node 1476:1443 "PBL 프로젝트 N개").
   두 목록이 같이 쓰므로 화면 CSS 가 아니라 여기에 둔다.

   검색창~이 텍스트 간격은 시안 실측 32px 인데 .page-hero 가 이미
   --mili-hero-block-gap(32px) 을 자기 하단 마진으로 갖고 있다. 형제 블록 마진은
   겹치므로(collapse) 여기서 margin-top 을 더 주면 32+α 로 벌어진다 — 0 으로 둬야 32px 이 유지된다. */
/* 2026-08-28 재실측(node 2723:88109, get_variable_defs) — Heading/H3/Semibold(24px·600·1.4)다.
   기존 18px(Body/B1)는 다른 화면 값을 옮겨적은 것이라 시안보다 작았다. VOD·PBL 목록이 공유하는
   컴포넌트라 두 화면 다 이 크기로 맞는다. */
.catalog-count-label {
    margin: 0;
    font-size: 1.5rem;
    line-height: 1.4;
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-fg);
}

/* 같은 재실측 — 숫자만 굵기가 다르지 않다(둘 다 SemiBold 600). font-weight 는 부모
   .catalog-count-label 값을 그대로 물려받게 두고 여기서 다시 올리지 않는다. */
.catalog-count-label__num {
    color: var(--mili-brand);
}

/* 시안은 설명도 본문색(#F8F9FA)이다 — 여기서만 --mili-muted 를 쓰지 않는다.
   시안의 설명은 한 줄짜리라 폭 제한이 없지만, 쇼케이스·로드맵처럼 두 문장인 화면은
   제한이 없으면 1200px 한 줄로 늘어진다. 이 저장소에서 이미 읽기 폭으로 쓰는 44rem 을 쓴다. */
.page-hero__lead {
    max-width: 44rem;
    margin: var(--mili-hero-lead-gap) 0 0;
    color: var(--mili-ink);
    font-size: var(--mili-text-sm);
    line-height: var(--mili-line-tight);
}

/* 제목 아래 보조 표기(진단일·아이디·수료증 발급일 등). 마이페이지 하위 화면이 쓰던
   .mypage-head__meta 를 2026-08-12 중앙정렬 개편 때 이리로 옮겼다 — 예전에는 제목 오른쪽에
   붙었는데 가운데 정렬에서는 제목 폭이 글자 수마다 달라져 옆자리가 성립하지 않는다. */
.page-hero__meta {
    margin: 0.625rem 0 0;
    padding: 0.1875rem 0.625rem;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.1);
    border-radius: var(--mili-radius-pill);
    background: rgba(var(--mili-fg-rgb), 0.04);
    color: var(--mili-fg-muted);
    font-size: 0.6875rem;
    font-weight: var(--mili-font-weight-semibold);
}

/* 알약형 검색 필드 자체는 theme/figma-classes.css 의 .search-box* (공용 클래스)로 이전했다
   (2026-08-26, Phase 4 — 예전 .page-hero__search* 삭제). 여기 남기는 것은 .page-hero 안에서만
   필요한 위쪽 여백(제목/설명과의 간격)뿐이다 — _page-hero.jsp 가 "search-box page-hero__search"
   두 클래스를 함께 붙인다. */
.page-hero__search {
    margin-top: var(--mili-hero-search-gap);
}

/* 모바일에서 36px 제목은 두 줄로 넘치고 상단 72px 여백도 과하다.
   제목은 t8 로 내리고 여백을 절반으로 줄인다. t8 값 자체는 2026-08-28 Figma(node 2963:69316
   커뮤니티 상세·2804:27549 VOD 목록, 둘 다 마스트헤드 "Heading 1" 컴포넌트 공용) 실측
   24px/1.4(33.6px)로 갱신 — 기존 28px/36px는 모바일 시안이 없던 시절의 추정값이었다.
   t8 은 이 미디어쿼리 밖에서 쓰이는 곳이 없어(reference.css 의 .mili-screen-title 은 미사용
   데드 클래스) 전역 토큰을 바꿔도 실질적으로 모바일 히어로 제목에만 영향이 간다.

   2026-09-03 간격 재실측(Figma "모바일 공통_타이틀/설명/검색", node 2859:104694 다크·
   2859:109520 라이트, get_design_context) — GNB 하단→제목 20px(= .app-page 모바일 pt 8px +
   여기 12px), 제목→설명 4px, 설명→검색창 20px. 기존 40px/12px(desktop 값 그대로 물려받던
   것)/32px 는 모바일 시안이 없던 시절의 값이라 이 실측으로 교체한다(사용자 확인).
   설명 글자색도 같은 노드에서 본문색(--mili-ink)이 아니라 Neutral/600(다크 #989a9e)·
   Neutral/800(라이트 #2b2e33)로 확인돼 모바일에서만 흐리게 뺀다(데스크톱은 .page-hero__lead
   기본 규칙의 본문색 그대로 — 시안 자체가 데스크톱/모바일 톤을 다르게 준 것으로 보고 사용자
   확인함). --mili-neutral-600/800 은 primitives.css 의 고정 스케일이라 테마와 무관하게 각각
   한 값이라, 다크/라이트를 explicit 하게 나눠 건다. */
@media (max-width: 767.98px) {
    .page-hero {
        margin-top: 12px;
    }

    .page-hero__title {
        font-size: var(--mili-font-size-t8);
        line-height: var(--mili-line-height-t8);
    }

    .page-hero__lead {
        margin-top: 4px;
        color: var(--mili-neutral-600);
    }

    .mili-light .page-hero__lead {
        color: var(--mili-neutral-800);
    }

    .page-hero__search {
        margin-top: 20px;
    }
}

/* ===== 5-3) 필터 칩 → 표준 알약 탭(.tab-pill) 으로 흡수 =====
   2026-08-27 탭 표준화. 여기 있던 .filter-chips / .filter-chip / .filter-chip--active /
   .filter-chips--sub / .filter-chip--sm 정의는 theme/figma-classes.css 의
   .tab-pill / .tab-pill__link / --active / .tab-pill--sub / .tab-pill__link--sm 으로
   옮겼다(Figma node 2042:215373 Type 2). 주석에도 적혀 있었듯 이 칩은 애초에
   "Tab Type2(알약형) 실측" 그대로였고, 개정 시안에서 활성 배색만 브랜드 틴트 →
   명암 반전으로 바뀌었다. 마크업도 전부 .tab-pill* 로 교체했다.
   2026-08-27 사용자 재지적으로 되돌림(Figma node 2224:95762 실측) — 분야 칩은 한 줄로 두고
   넘치는 만큼 가로 스크롤한다(2026-08-12 엔 "스크롤 힌트가 없어 잘려 보인다"는 이유로
   flex-wrap 으로 바꿨었는데, 시안 자체가 한 줄+가로 스크롤이라 뒤집는다). 클래스명은
   .filter-chips 가 아니라 .tab-pill 로 표준화됐으므로 이 동작은 theme/figma-classes.css 의
   .tab-pill--scroll 수정자로 반영했다(다른 .tab-pill 사용처까지 전부 nowrap 이 되지 않도록
   분야 필터에만 스코프). */

/* ===== 5-4) 결과 바 ===== */

.result-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
    margin-bottom: var(--mili-space-4);
}

/* Figma(node 2058:221452 다크 / 2058:221378 라이트, "전체 N건") 실측 — 접두사·강조 둘 다
   --mili-muted/--mili-ink(회색·본문 잉크)가 아니라 거의 흰색(다크)/검정(라이트)에 가까운
   값이다. 대응하는 시맨틱 토큰이 없어(라이트 쪽은 Figma 변수 바인딩조차 없는 리터럴
   rgba(0,0,0,.8)) primitives.css 스케일에서 가장 가까운 칸(다크 neutral-200/100,
   라이트 neutral-950)에 배정했다(2026-09-15, 사용자 확인). */
.result-bar__count {
    margin: 0;
    color: var(--mili-neutral-200);
    font-size: var(--mili-body-b3-size);
}

.result-bar__count strong {
    color: var(--mili-neutral-100);
    font-weight: var(--mili-font-weight-bold);
}

.mili-light .result-bar__count {
    color: var(--mili-neutral-950);
}

.mili-light .result-bar__count strong {
    color: var(--mili-neutral-950);
}

/* .result-bar 오른쪽의 정렬 칩 + 액션 묶음. 원래 community.css 에만 있었으나(QA38,
   2026-08-15) 2026-09-10 마이페이지(작성한 게시글)에도 같은 조합이 필요해져 공용으로 옮긴다
   (community.css 의 동일 규칙은 지운다 — 값이 갈리는 사본을 남기지 않는다). */
.community-toolbar__actions {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    flex: 0 0 auto;
}

/* 정렬 기능은 아직 없다(항상 최신순 고정) — 장식 칩만 둔다. VOD 목록의 정렬 드롭다운
   (course-list.css .catalog-sort-dropdown__toggle)이 실 기능을 붙이기 전 썼던 것과 같은
   처리(cursor:default, 클릭 무반응). 실 정렬이 필요해지면 그 화면처럼 Bootstrap dropdown +
   쿼리파라미터로 바꾼다. */
.community-toolbar__sort {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.15);
    border-radius: var(--mili-radius-filter);
    color: rgba(var(--mili-fg-rgb), 0.55);
    font-size: var(--mili-caption-size);
    line-height: var(--mili-body-b3-line);
    font-weight: var(--mili-font-weight-semibold);
    cursor: default;
    user-select: none;
}

.mili-light .community-toolbar__sort {
    color: #3E4449;
}

/* ===== 5-5) 과정 상세 ===== */

/* 본문 + 우측 액션 카드. 모바일은 1열(액션 카드가 위), lg 이상은 2열 + sticky. */
.detail-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--mili-space-5);
    align-items: start;
}

@media (min-width: 992px) {
    .detail-shell {
        grid-template-columns: minmax(0, 1fr) 20rem;
        gap: 1.75rem;
    }

    .detail-shell__aside {
        position: sticky;
        top: calc(var(--mili-navbar-height) + 1rem);
    }
}

.detail-shell__main {
    min-width: 0;
}

/* 하단 "목록으로" 링크. 실측(Figma node 1543:950/951): 배경 없이 테두리만, 화살표 없음, 좁은 여백.
   VOD·PBL 상세가 함께 쓴다(2026-08-12 추가 — 예전엔 .btn.btn-outline-secondary + 화살표 아이콘이라
   시안과 여백·아이콘 둘 다 달랐다). */
.detail-shell__back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4375rem 0.75rem;
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-none);
    color: var(--mili-muted);
    font-size: var(--mili-body-b2-size);
    font-weight: var(--mili-font-weight-semibold);
    text-decoration: none;
    margin-top: var(--mili-space-8);
}

.detail-shell__back:hover,
.detail-shell__back:focus {
    color: var(--mili-ink);
    border-color: var(--mili-border-strong);
}

/* 과정명을 얹는 표지. 목록 카드 썸네일과 같은 배경(course-card__thumb--N)을 함께 붙여 쓴다.
   2026-08-13 QA7 재지적 — 카드/패널은 --mili-radius-card(12px)를 써야 하는데 0으로 박혀 있었다. */
.detail-hero {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--mili-space-3);
    min-height: 11rem;
    padding: 1.25rem;
    margin-bottom: var(--mili-space-5);
    border-radius: var(--mili-radius-card);
    color: var(--mili-neutral-50);
    overflow: hidden;
    isolation: isolate;
    /* ⚠️ --mili-ink 를 쓰면 안 된다 — 다크 테마에서 흰색으로 뒤집혀 폴백이 "흰 판"이 된다.
       여기서 필요한 건 "테마와 무관하게 어두운 바탕"이라 원시 팔레트 값을 직접 가리킨다.
       2026-09-10 수정 — "--card-bg: none;" 을 여기서 직접 선언하면, 같은 요소에 함께 붙는
       course-card__thumb--pbl-N 같은 배경 클래스(components.css 안에서 이 규칙보다 먼저
       나온다)가 설정한 --card-bg 를 캐스케이드 순서상 항상 덮어써 버려 이미지가 사라진다
       (pbl/_project-preview.jsp, showcase/detail.jsp 실측). var() 의 두 번째 인자를 폴백으로
       써서 "이 요소에 --card-bg 가 전혀 없을 때만" none 이 되게 한다. */
    background: var(--mili-slate-900) var(--card-bg, none) no-repeat center / cover;
}

.detail-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.05) 30%, rgba(0, 0, 0, 0.45) 100%);
    z-index: -1;
}

.detail-hero__badges {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
}

.detail-hero__title {
    margin: 0;
    font-size: 1.375rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.35;
}

.detail-hero__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.875rem;
    font-size: var(--mili-font-size-t3);
    opacity: 0.95;
}

/* 레벨·이수시간·기관 등 사실 정보 표. 모바일 1열, sm 이상 2열(데스크톱 기본형 — 아래
   ≤767px 재정의가 없는 폭에서는 그대로 쓴다). */
.detail-facts {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.75rem 1.5rem;
    margin: 0;
    /* 2026-09-22 Figma get_design_context 실측(node 4839:195068 "Definition List") — 상하좌우
       12px 균일 패딩(p-[12px]), radius 12px. */
    padding: 0.75rem;
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
}

@media (min-width: 576px) {
    .detail-facts {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.detail-facts__row {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    min-width: 0;
}

.detail-facts__label {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    width: 7rem;
    flex: 0 0 auto;
    color: var(--mili-muted);
    font-size: var(--mili-body-b3-size);
}

.detail-facts__label > i {
    width: 1rem;
    height: 1rem;
    font-size: 1rem;
    line-height: 1;
    flex-shrink: 0;
}

/* Figma QA(node 4038:312702 "Light/강의/상세/학습전" 실측, 2026-09-22) — 라이트 라벨 색을
   muted(회색)에서 Neutral/900으로 올린다(다크는 손대지 않음, 기존 muted가 이미 읽힌다). */
.mili-light .detail-facts__label {
    color: var(--mili-neutral-900);
}

.detail-facts__value {
    min-width: 0;
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-semibold);
    font-size: 0.9375rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 2026-09-08 Figma QA(node 3374:112602 항목2, "모바일 QA" 보드) — 모바일 폭 전용 재작성.
   실측: 다크 node 3617:101469 "Definition List"(박스 카드), 라이트 node 3798:110656(박스
   없는 순수 목록) — 다크/라이트가 서로 다른 형상이라 아래 .mili-light 규칙이 박스 자체를
   없앤다(패딩·배경·보더 전부 초기화), 근사하지 않는다. 시안이 모바일 프레임뿐이라 데스크톱
   (위 기본형, sm 이상 2열 카드)은 그대로 두고 이 폭에서만 겹쳐 쓴다. */
@media (max-width: 767px) {
    .detail-facts {
        display: flex;
        flex-direction: column;
        width: 100%;
        margin: 0;
        gap: var(--mili-space-3);
        padding: var(--mili-space-3);
        border-radius: var(--mili-radius-card);
        background: var(--mili-neutral-870);
        border: 1px solid rgba(255, 255, 255, 0.06);
    }

    .mili-light .detail-facts {
        gap: 0;
        padding: 0;
        border-radius: 0;
        background: none;
        border: 0;
    }

    .detail-facts__row {
        align-items: flex-start;
        justify-content: space-between;
        padding-bottom: var(--mili-space-2);
        border-bottom: 1px solid var(--mili-neutral-800);
    }

    .mili-light .detail-facts__row {
        padding: var(--mili-space-3) 0;
        border-bottom-color: var(--mili-neutral-300);
    }

    .detail-facts__label {
        width: 100px;
        color: rgba(248, 249, 250, 0.7);
        font-weight: var(--mili-font-weight-regular);
    }

    .mili-light .detail-facts__label {
        color: var(--mili-neutral-700);
    }

    .detail-facts__value {
        flex: 1 1 auto;
        color: var(--mili-neutral-50);
        font-size: var(--mili-body-b3-size);
        font-weight: var(--mili-font-weight-regular);
        text-align: right;
        overflow: visible;
        text-overflow: clip;
    }

    .mili-light .detail-facts__value {
        color: var(--mili-neutral-800);
        font-weight: var(--mili-font-weight-medium);
        letter-spacing: -0.02em;
    }
}

.mili-light .detail-facts__value {
    color: var(--mili-neutral-800);
    font-weight: var(--mili-font-weight-medium);
    letter-spacing: -0.02em;
}

/* 우측(모바일에서는 상단) 액션 카드. 라운드 12(=--mili-radius-card)는 Figma QA 44 1948:7501 실측 지적사항. */
.detail-cta {
    padding: 1.25rem;
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    box-shadow: var(--mili-shadow);
}

/* 모바일에서 액션 카드가 표지 위로 올라올 때만 노출되는 과정명. */
.detail-cta__course {
    margin-bottom: var(--mili-space-3);
    padding-bottom: var(--mili-space-3);
    border-bottom: 1px solid var(--mili-border);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

.detail-cta__headline {
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
    margin-bottom: var(--mili-space-1);
}

.detail-cta__learners {
    font-size: var(--mili-heading-h4-size);
    font-weight: 800;
    color: var(--mili-ink);
    margin-bottom: var(--mili-space-4);
}

/* 정보표(라벨 — 값 한 줄). 실측(Figma node 1543:2138 계열): 구분선으로 나뉜 행,
   라벨은 muted 왼쪽, 값은 굵게 오른쪽. 마지막 행만 구분선을 뺀다.
   2026-08-18 에 css/vod/course-detail.css 에서 이리로 옮겼다 — 로드맵 상세 2곳
   (roadmap/detail.jsp, mypage/roadmap-detail.jsp)이 같은 표를 쓰게 되면서 공용이 됐다. */
.detail-cta__facts {
    margin: 0 0 1.25rem;
    font-size: 0.875rem;
}

/* 강의 상세는 정보표가 버튼 위라 간격이 필요 없지만, 로드맵 상세는 버튼이 먼저 온다 —
   그 경우에만 사이를 띄운다(안 띄우면 버튼과 첫 행이 맞붙는다). */
.detail-cta__actions + .detail-cta__facts {
    margin-top: var(--mili-space-4);
}

.detail-cta__facts-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--mili-space-3);
    padding-bottom: var(--mili-space-3);
    margin-bottom: var(--mili-space-3);
    border-bottom: 1px solid var(--mili-border);
}

.detail-cta__facts-row:last-child {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: 0;
}

.detail-cta__facts-row dt {
    color: var(--mili-muted);
}

/* Figma QA(node 4038:312702 "Light/강의/상세/학습전" 실측, 2026-09-22) — 라이트 라벨 색을
   muted(회색)에서 Neutral/900으로 올린다(다크는 손대지 않음).
   font-weight — dt 는 부트스트랩 기본(font-weight:700)을 그대로 물려받아 라벨이 값(dd)만큼
   굵게 보였다. 실측(node 4839:195407, 2026-09-22 재확인)은 라벨이 Regular 다. */
.mili-light .detail-cta__facts-row dt {
    color: var(--mili-neutral-900);
    font-weight: var(--mili-font-weight-regular);
}

.detail-cta__facts-row dd {
    margin: 0;
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-bold);
    text-align: right;
}

.detail-cta__actions {
    display: flex;
    gap: var(--mili-space-2);
}

/* 실측(Figma node 1652:5147/5150/5158): 사이드바 버튼은 --mili-radius-button(5px) — 나머지 화면의
   각진 버튼(--bs-border-radius:0)과 달리 이 카드 안에서만 예외적으로 둥글다. */
.detail-cta__actions .btn {
    flex: 1 1 auto;
    border-radius: var(--mili-radius-button);
}

/* ----------------------------------------------------------------------
   과정·PBL 상세 전용 헤더 행(.detail-cta__header-row) — 로드맵 담기 + 주요 액션 버튼.
   실측(Figma 8qAqeXw50F8GKDGkeW0Pua node 4038:312685/312707, 2026-09-11 재실측 —
   구 node 3522:* 기준 구현을 뒤집었다). .detail-cta__actions(위, 로드맵 상세와 공유하는
   다른 컴포넌트)와는 별개다 — 이 화면들은 로드맵 담기 트리거가 주요 버튼과 "같은 줄에서
   함께" peek 에 노출돼야 해서 새 클래스로 둔다.

   데스크톱(≥992px, 시트 아님)에서는 각 버튼이 카드 폭을 꽉 채우는 세로 스택이고, 주요
   액션 버튼이 로드맵 담기보다 위에 온다(모바일 시트는 반대로 왼쪽에 로드맵 담기를 둔다 —
   DOM 순서는 모바일 규약을 따르고, 데스크톱은 아래 order 로 시각만 뒤집는다).
   ---------------------------------------------------------------------- */
.detail-cta__header-row {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
}

.detail-cta__header-row .btn {
    width: 100%;
}

@media (min-width: 992px) {
    /* DOM 순서는 로드맵 담기가 먼저다(모바일 시트가 왼쪽에 로드맵 담기를 두는 규약과
       공유) — 데스크톱 세로 스택에서만 주요 버튼이 위로 오도록 시각 순서만 뒤집는다. */
    .detail-cta__header-row .roadmap-add,
    .detail-cta__header-row > .roadmap-add__trigger {
        order: 1;
    }
}

/* 정보 카드(이수시간/교육일수·진도) — 실측(node 4038:312685/312707): 버튼 위, 신청 전에도
   항상 노출. 모바일 시트(펼침)에서는 기존처럼 테두리 있는 카드로 감싸지만, 데스크톱은
   시안에 카드 테두리가 없어 배경·테두리·패딩을 걷어내고 값만 세로로 나열한다. */
.detail-cta__info-card {
    margin-top: 0;
    padding: var(--mili-space-5);
    border-radius: var(--mili-radius-button);
    /* 기본은 시맨틱 표면(라이트/다크 자동 반전) — Neutral/870 은 원시 팔레트값이라
       테마와 무관하게 항상 어둡다(2026-09-02 라이트 스크린샷에서 카드만 검게 뜨는
       버그로 발견). 다크에서만 아래 .mili-theme-dark 규칙으로 시안 실측값(Neutral/870)을
       덮어쓴다 — 라이트는 시안이 없어 이웃 카드(.detail-facts 등)와 같은 --mili-surface 로 근사. */
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
}

.mili-theme-dark .detail-cta__info-card {
    background: var(--mili-neutral-870);
    border-color: rgba(255, 255, 255, 0.08);
}

.detail-cta__info-card + .detail-cta__header-row {
    /* Figma 4839:199426 — 버튼그룹↔상세정보 간격 12px */
    margin-top: var(--mili-space-3);
}

@media (min-width: 992px) {
    .detail-cta__info-card {
        padding: 0;
        background: none;
        border: 0;
    }

    .mili-theme-dark .detail-cta__info-card {
        background: none;
        border-color: transparent;
    }
}

.detail-cta__cancel-row {
    margin-top: var(--mili-space-2);
}

/* 수강신청 취소 — VOD·PBL 상세가 함께 쓰는 재사용 컴포넌트다. 시각은 표준 `.btn.medium.error`
   (theme/figma-classes.css, 평상시에도 빨강 아웃라인 · hover 시 채움)가 정본이다 — 여기엔 폭만
   남긴다. 2026-09-02: 이전엔 `.btn.medium.neutral` + hover 전용 경고색이었다("파괴적 액션을
   항상 빨갛게 두면 학습 진입 버튼보다 눈에 띈다"는 2026-08-27 결정) — Figma node 3522:41048/
   40938("수강신청 취소" 버튼, 시안 실측 `Status/Error` 단색 아웃라인) 재확인으로 뒤집었다. */
.detail-cta__cancel {
    width: 100%;
}

.detail-cta__note {
    margin-top: 0.875rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--mili-border);
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

/* 내 진도(수강 중일 때만) */
.detail-progress {
    margin-bottom: var(--mili-space-4);
}

.detail-progress__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
    margin-bottom: 0.375rem;
}

/* margin-left:auto 로 오른쪽에 붙인다 — VOD 상세는 라벨 없이 퍼센트만 두므로(2026-08-12)
   space-between 만으로는 왼쪽으로 붙는다. 라벨이 남아 있는 PBL 상세에서는 결과가 같다. */
.detail-progress__value {
    margin-left: auto;
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-primary-strong);
}

/*
 * 상세 사이드바 "같은 스킬 기준 추천 컨텐츠" 카드. .detail-cta 바로 아래 형제 요소로
 * course-detail.jsp·project-detail.jsp 가 공용으로 include 한다(_related-content.jsp).
 *
 * .reco-list(_recommend-section.jsp 용)는 768px 이상에서 뷰포트 기준 2열로 벌어져
 * 20rem 고정폭 사이드바 안에서 깨지므로 재사용하지 않고, .detail-cta 와 같은 톤의
 * 새 카드로 둔다 — 컨테이너 폭과 무관하게 항상 1열이다.
 */
.detail-related {
    margin-top: var(--mili-space-4);
    padding: 1.25rem;
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    box-shadow: var(--mili-shadow);
}

/* 라이트 배경 — 실측(Figma 8qAqeXw50F8GKDGkeW0Pua node 4839:195422, "Light/강의/상세/학습전",
   2026-09-22) 결과 --mili-surface(라이트 #FFFFFF, 페이지 배경과 동색)가 아니라 Neutral/100
   이다 — 카드(.detail-related__link)만 흰색으로 그 위에 얹혀야 구분이 생긴다. */
.mili-light .detail-related {
    background: var(--mili-neutral-100);
}

.detail-related__title {
    margin: 0 0 var(--mili-space-3);
    font-size: var(--mili-body-b2-size);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-text);
}

.detail-related__list {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* 카드 배경·라운드·그림자는 Figma 1948:7768 실측(카드 bg #202328 ≈ --mili-surface-raised, radius 12,
   shadow 0 14px 34px rgba(0,0,0,.22) ≈ --mili-shadow). */
.detail-related__link {
    display: flex;
    flex-direction: column;
    /* min-width:0 없으면 flex 컬럼 자식(특히 white-space:nowrap 인 __item-title)이 텍스트 원래
       폭만큼 카드를 밀어 모바일에서 가로 스크롤이 생긴다(2026-08-19 실측 확인). */
    min-width: 0;
    gap: var(--mili-space-1);
    padding: var(--mili-space-3) var(--mili-space-4);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface-raised);
    box-shadow: var(--mili-shadow);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.15s ease;
}

.detail-related__link:hover,
.detail-related__link:focus-visible {
    border-color: var(--mili-brand);
}

.detail-related__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-2);
}

/* 2026-08-26 — 위 규칙은 삭제하고 .tag .vod/.pbl(theme/figma-classes.css)로 대체했다(사용자
   확인). 값은 그대로(2026-08-25 확정, node 2042:215476 실측과 정확히 일치) — 클래스명만
   바뀌었고, 테두리·font-weight(medium)는 .tag 공통값을 새로 받는다. */

/* 실측(get_design_context, node I4038:312696;1:5383 다크 / I4038:312702;1:5575 라이트,
   2026-09-22) — 다크·라이트가 배경·글자색 모두 다른데 지금까지 --mili-text-muted(양쪽 다 #868E96)
   하나로 통일해 둘 다 시안과 어긋나 있었다. */
.detail-related__reason {
    flex: 0 0 auto;
    padding: 0.125rem 0.5rem;
    border-radius: var(--mili-radius-pill);
    font-size: var(--mili-caption-size);
    color: var(--mili-neutral-50);
    background: var(--mili-neutral-800);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 60%;
}

.mili-light .detail-related__reason {
    color: var(--mili-neutral-900);
    background: var(--mili-neutral-300);
}

/* 시안(1948:7768)은 한 줄 말줄임 — 카드 폭이 좁아 2줄 클램프보다 시안을 따르기로 확정(2026-08-19). */
.detail-related__item-title {
    color: var(--mili-text);
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.4;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 2026-09-22 사용자 요청 — 이 메타(이수시간·제공기관 값)는 목록 카드 .catalog-card__facts-row
   dd(같은 역할: 사실 값 텍스트)와 색을 맞춘다. 위 주석의 실측값(다크 #989a9e/라이트 #66686c)은
   이 사이드바 노드 단독 측정이라 카드보다 어둡게 나왔었는데, 사용자가 명시적으로 목록 카드
   기준 통일을 요청해 그 값 대신 카드와 같은 neutral-50/900 을 쓴다. */
.detail-related__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--mili-space-2);
    color: var(--mili-neutral-50);
    font-size: var(--mili-caption-size);
}

.mili-light .detail-related__meta {
    color: var(--mili-neutral-900);
}

/* -------------------------------------------------------------------
   5-5-1) 액션 카드 바텀시트 (좁은 화면 전용)
   -------------------------------------------------------------------
   예전에는 화면 CSS 가 .detail-shell__aside { order: -1 } 로 사이드바를 통째로 표지 위에
   끌어올렸다. 신청 버튼은 그렇게 살렸지만 같은 aside 에 들어 있는 "추천 컨텐츠"까지 표지보다
   먼저 나와, 아직 아무것도 안 본 사람에게 다른 콘텐츠를 먼저 들이미는 순서가 됐다.
   지금은 aside 를 본문 아래 제자리에 두고, 액션 카드만 하단 고정 시트로 띄운다.

   시트 골격은 js/common/detail-cta-sheet.js 가 만든다. 아래 규칙이 전부 .detail-cta--sheet
   아래에 있는 것은 의도다 — 그 클래스는 스크립트만 붙이므로, JS 가 죽으면 예전처럼 문서 흐름
   안의 평범한 카드로 남는다(@media 만으로 fixed 를 걸면 영원히 접힌 CTA 가 된다).

       div.detail-cta.detail-cta--sheet
       ├ button.detail-cta__handle       가운데 셰브론 (스크립트 생성, 그립바 없음)
       ├ div.detail-cta__header-row      로드맵 담기 + 주요 버튼 — 접힌 상태(peek)에도 항상 노출
       └ div.detail-cta__sheet-body      나머지(정보카드·찜·취소, 스크립트가 옮겨 담는다)

   전환점 991.98px 은 .detail-shell 이 1열로 떨어지는 경계(위 992px)와 같은 값이고,
   detail-cta-sheet.js 의 MEDIA 상수와도 반드시 일치해야 한다.
   =================================================================== */

/* 핸들은 스크립트가 만든 뒤 DOM 에 남아 있다 — 넓은 화면에서는 항상 숨긴다. */
.detail-cta__handle {
    display: none;
}

@media (max-width: 991.98px) {
    .detail-cta--sheet {
        position: fixed;
        right: 0;
        bottom: 0;
        left: 0;
        /* 사이드바 드로어(스크림 1040 / 본체 1045)보다 낮게 둬서, 드로어를 열면 그 아래로 깔린다. */
        z-index: 1035;
        display: flex;
        flex-direction: column;
        max-height: 85vh;
        /* 좌우 20px(px-20)·아래 24px 는 실측(node 3522:41074, 버튼 행 하단→GNB 상단 23px)에
           가장 가까운 토큰(space-5/space-6)에 배정한 값이다. */
        padding: 0 var(--mili-space-5) max(var(--mili-space-6), env(safe-area-inset-bottom));
        border: 0;
        border-top: 1px solid var(--mili-border);
        border-radius: var(--mili-radius-card) var(--mili-radius-card) 0 0;
        box-shadow: var(--mili-shadow-lg);
    }

    /*
     * 비로그인 등 펼칠 내용이 없으면(detail-cta-sheet.js 참고) 화살표(핸들) 자체를 DOM 에
     * 넣지 않는다 — 눌러도 빈 화면만 열리는 버튼을 두지 않기 위해서다. 그 경우 핸들의
     * pt(위 §"가운데 셰브론")가 없어져 버튼 행이 테두리에 바로 붙으므로 여기서 대신 준다.
     * 시안에 없는 상태라 값은 AI 판단(다른 peek 여백과 같은 스케일인 space-3).
     */
    .detail-cta--sheet.detail-cta--no-expand {
        padding-top: var(--mili-space-3);
    }

    /*
     * 가운데 셰브론 하나만 둔다(Figma 시안엔 그립바가 없다).
     * 실측(node 3522:41075, 3522:41077): 시트 위쪽 테두리에서 셰브론까지 4px, 셰브론 바로
     * 아래(간격 0)에 헤더 행이 붙는다 — pb 를 두면 시안보다 위아래 여백이 벌어진다.
     */
    .detail-cta__handle {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        padding: var(--mili-space-1) 0 0;
        border: 0;
        background: transparent;
        color: var(--mili-muted);
        cursor: pointer;
    }

    /* 펼치면 셰브론이 아래를 향한다(.mission-accordion 아코디언과 같은 방식). */
    .detail-cta__handle-chevron {
        transition: transform 0.2s ease;
    }

    .detail-cta--sheet.is-open .detail-cta__handle-chevron {
        transform: rotate(180deg);
    }

    /*
     * 접힘 → 펼침. height:auto 는 전환이 안 되므로 max-height 를 쓴다.
     * 62vh + peek 이 시트 상한(85vh) 안에 들어간다.
     */
    .detail-cta--sheet .detail-cta__sheet-body {
        max-height: 0;
        opacity: 0;
        overflow: hidden;
        transition: max-height 0.28s ease, opacity 0.18s ease;
    }

    .detail-cta--sheet.is-open .detail-cta__sheet-body {
        max-height: 62vh;
        opacity: 1;
        overflow-y: auto;
    }

    /* 표지가 페이지 맨 위에 그대로 있으므로 시트 안의 과정명은 중복이다. */
    .detail-cta--sheet .detail-cta__course {
        display: none;
    }

    /*
     * 헤더 행 — 접힌 상태(peek)에도 항상 보여야 하므로 데스크톱의 세로 스택 대신 한 줄로
     * 눕힌다(실측: node 3522:41074 — 로드맵 담기 왼쪽, 주요 버튼 오른쪽, 둘 다 내용 크기).
     */
    .detail-cta--sheet .detail-cta__header-row {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }

    .detail-cta--sheet .detail-cta__header-row .btn {
        width: auto;
    }

    /* 비로그인 등 자식이 하나뿐이면 space-between 이 왼쪽 정렬로 무너진다 — 오른쪽으로 민다. */
    .detail-cta--sheet .detail-cta__header-row > *:only-child {
        margin-left: auto;
    }

    /*
     * 로드맵 담기 서브패널(node 3522:40991) — 열리면 같은 시트 안에서 헤더 행의 나머지·
     * 정보카드·찜·취소를 전부 감추고 패널이 시트를 통째로 채운다("같은 시트의 다른 화면").
     * 트리거 옆에 뜨는 데스크톱 팝오버(아래 ≥992px 규칙)와 달리 여기서는 항상 숨김 상태다가
     * .detail-cta--roadmap-open 일 때만 나타난다.
     *
     * 그립 핸들(가운데 셰브론)은 이 상태에서도 그대로 둔다 — 실측(node 3522:40991)에 셰브론이
     * (아래를 향한 채) 그대로 있고, 그 바로 아래 뒤로가기 행이 이어진다. 핸들을 숨기면 위쪽
     * 여백(핸들의 pt-4)까지 같이 사라져 패널이 시트 맨 위에 붙어 보인다(2026-09-03 발견).
     */
    .detail-cta--sheet.detail-cta--roadmap-open .detail-cta__header-row > *:not(.roadmap-add),
    .detail-cta--sheet.detail-cta--roadmap-open .roadmap-add > [data-role="roadmap-panel-toggle"],
    .detail-cta--sheet.detail-cta--roadmap-open .detail-cta__sheet-body {
        display: none;
    }

    .detail-cta--sheet.detail-cta--roadmap-open .detail-cta__header-row {
        display: block;
    }

    /* 패널이 열려 있는 동안은 is-open 여부와 무관하게 셰브론이 아래를 향한다(실측대로). */
    .detail-cta--sheet.detail-cta--roadmap-open .detail-cta__handle-chevron {
        transform: rotate(180deg);
    }

    /* 2026-09-04 사용자 지적 — 목록 끝 행이 하단 탭바(.app-bottom-nav)에 가려 잘려 보이고,
       스크롤이 이 패널이 아니라 뒤 상세 페이지에서 일어날 때가 있었다. 원인은 두 가지가 겹친
       것이다: ① max-height: 85vh 는 시트 전체(핸들 포함) 기준이 아니라 패널 자신의 상한이라,
       핸들(.detail-cta__handle, 실측 25px)만큼 패널이 화면 아래로 밀려나면서 85vh 를 다 쓰면
       하단 탭바 영역까지 넘친다(실측: getBoundingClientRect 로 패널 bottom 이 탭바 top 보다
       26px 더 아래였다). ② 패널 자신도 overflow-y:auto 였고 안쪽 .roadmap-panel__card 도
       overflow-y:auto 라 스크롤 컨테이너가 두 겹이었다 — 패널 쪽은 넘칠 내용이 없어 스크롤이
       안 먹고, 제스처가 그대로 상위(문서)로 전파돼 상세 페이지가 스크롤됐다.
       핸들 높이만큼 상한을 줄이고, 스크롤은 .roadmap-panel__card 하나에서만 일어나게
       overflow 를 hidden 으로 바꾼다(카드가 flex:1 로 남는 공간을 채우고 자체 스크롤한다). */
    .detail-cta--sheet .roadmap-panel:not([hidden]) {
        display: flex;
        flex-direction: column;
        max-height: calc(85vh - var(--mili-space-8));
        overflow: hidden;
    }

    .detail-cta__scrim {
        position: fixed;
        inset: 0;
        z-index: 1030;
        background: var(--mili-overlay);
    }

    /*
     * 시트가 페이지 끝(목록으로 링크·개인정보처리방침 푸터)을 가리지 않게 자리를 비운다.
     * 푸터는 </main> 밖(common/_shell-end.jsp)이라 .app-page 가 아니라 셸 본문에 건다.
     * peek 실측값은 스크립트가 --mili-cta-sheet-peek 로 넘긴다(버튼 문구가 줄바꿈되면 달라진다).
     */
    body.has-cta-sheet .app-frame__body {
        padding-bottom: calc(var(--mili-cta-sheet-peek, 7.5rem) + var(--mili-space-5));
    }
}

/*
 * ≤767px 에서는 공용 하단 탭바(.app-bottom-nav, §"모바일 하단 탭바")도 동시에 fixed 로 떠 있다.
 * 위 991.98px 규칙이 이 시트를 bottom:0 에 고정해 버려서, 같은 자리의 .app-bottom-nav(z-index 1030)
 * 를 이 시트(z-index 1035)가 완전히 덮어 GNB 가 통째로 안 보이는 상태였다(2026-08-28 발견 —
 * Figma node 2804:28504 실측: 액션바 바로 아래에 GNB 가 이어지는 2단 구성이지 겹치는 게 아니다).
 * 시트를 탭바 높이만큼 위로 밀어 두 바가 세로로 쌓이게 한다 — 탭바 실측 높이는 90px
 * (.app-bottom-nav min-height, 위 §"모바일 하단 탭바") 인데 여기 값이 80px로 남아 있어
 * 시트 아래쪽 10px 가 탭바에 가려 겹쳐 있었다(2026-09-03 발견, 실측으로 정정).
 * .app-page 의 padding-bottom(아래 §"모바일 하단 탭바")이 이미 탭바 높이분을 확보해 두므로
 * 본문 여백은 손대지 않는다 — 이 규칙은 시트의 위치만 바꾼다.
 */
@media (max-width: 767px) {
    .detail-cta--sheet {
        bottom: calc(90px + max(0px, env(safe-area-inset-bottom)) - 10px);
        /* 위 bottom 을 10px 낮춘 만큼 padding-bottom 을 10px 늘려 실제 높이를 키운다 —
           보이는 콘텐츠(위쪽) 위치는 그대로고, 늘어난 여백(시트 배경)이 그만큼 더 아래로
           깔려 하단 GNB 의 둥근 모서리 틈(2026-09-04 사용자 지적)을 덮는다. */
        padding-bottom: calc(max(var(--mili-space-6), env(safe-area-inset-bottom)) + 10px);
    }
}

/*
 * ≤767px 도 991.98px 이하 공용 시트 골격(위)을 그대로 쓴다 — 2026-09-02 Figma 재시안
 * (node 3522:41074 등)으로 "≤767px 만 그립 없는 정적 한 줄 바"였던 이전 결정(2804:28408/28505
 * 근거, 2026-08-30/31 사용자 재확인 3회)을 뒤집었다. 새 시안은 767px 이하와 768~991.98px
 * 사이를 구분하지 않는다 — 그립·헤더 행·정보카드 전부 같은 구조로 접고 펼친다.
 *
 */
/* ===== 5-6) 커리큘럼 아코디언 ===== */

.curriculum {
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface);
    overflow: hidden;
}

.curriculum__section + .curriculum__section {
    border-top: 1px solid var(--mili-border);
}

/* 실측(get_design_context, node 2058:232595/230069·2058:230058/232584 "Syllabus" 강의·프로젝트
   커리큘럼 실제 케이스, 2026-08-27) — 행마다 배경이 있다. 라이트는 홀/짝 지브라(Neutral/200·100),
   다크는 균일(Neutral/870). 지금 마크업은 펼침 없이 최상위만 보여주는 정적 헤더뿐이라(아래
   --static 참고) 시안의 "펼친 행 강조색"은 반영 대상이 없다. */
.curriculum__section:nth-child(odd) {
    background: var(--mili-neutral-200);
}

.curriculum__section:nth-child(even) {
    background: var(--mili-neutral-100);
}

.mili-theme-dark .curriculum__section {
    background: var(--mili-neutral-870);
}

/* "여정" 변형 — 순번 배지를 잇는 세로선(node 2058:232595/230069 의 Vector, 프로젝트 미션
   여정 전용). 행마다 세로선 세그먼트를 그려 이어 붙이면 컨테이너 하나가 통짜로 이어진
   선처럼 보인다.
   - 가운데 정렬: 시안 원본은 행 패딩 20px 기준 배지 중심이 32px(20+12)이지만, 이 화면의
     실제 패딩은 24px(.curriculum__head, 1.5rem)라 배지 중심은 36px(24+12)다 — 32px를
     그대로 쓰면 배지 중심에서 4px 왼쪽으로 어긋난다.
   - 숫자 가림 방지: `.step-seq` 배지가 기본 `position: static`이라 스태킹 순서상 이
     `position: absolute` 세로선이 항상 배지 위에 그려져 숫자를 가리고 있었다 — 배지를
     `position: relative`로 올려 자기 자신의 스태킹 레벨을 갖게 하면, DOM 순서상 뒤에 오는
     배지가 앞선 `::before` 위에 그려진다. */
.curriculum--journey .curriculum__section {
    position: relative;
}

.curriculum--journey .curriculum__section::before {
    content: "";
    position: absolute;
    left: 36px;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--mili-border);
}

.curriculum--journey .step-seq {
    position: relative;
    z-index: 1;
}

.curriculum__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
    width: 100%;
    /* 2026-08-26 Figma "Syllabus" 재실측(node 1543:827) — 시안 패딩이 사방 24px
       균일이라 기존 14/18px에서 맞춘다. */
    padding: 1.5rem;
    border: 0;
    background: transparent;
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-bold);
    font-size: 0.9375rem;
    text-align: left;
    cursor: pointer;
}

.curriculum__head:hover {
    background: var(--mili-bg);
}

/* 펼침 없이 최상위 목차만 보여 줄 때 — 클릭 대상이 아니므로 커서·호버 배경을 되돌린다. */
.curriculum__head--static,
.curriculum__head--static:hover {
    cursor: default;
    background: transparent;
}

.curriculum__count {
    flex: 0 0 auto;
    font-size: var(--mili-body-b3-size);
    /* 2026-08-26 Figma "Syllabus" 재실측(node 1543:827) — "N개" 텍스트가
       Regular(400)다(기존 medium). 색상(#adb5bd)은 우리 토큰과 안 맞아
       현행(--mili-muted) 유지(사용자 확인). */
    font-weight: var(--mili-font-weight-regular);
    color: var(--mili-muted);
}

.curriculum__items {
    list-style: none;
    margin: 0;
    padding: 0 1.5rem 1.5rem;
}

/* 실측(Figma node 1543:827 Syllabus): 아이콘 없이 [타입 라벨][제목] 두 칸을 좌측 정렬로 나열한다
   (타입 라벨이 오른쪽 뱃지가 아니라 왼쪽 고정폭 텍스트다). */
.curriculum__item {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    padding: 0.4375rem 0;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-body);
    border-top: 1px dashed var(--mili-border);
}

.curriculum__item-type {
    flex: 0 0 auto;
    width: 3.25rem;
    color: var(--mili-muted);
}

.curriculum__item-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ===== 5-7) 개설 차수 목록 ===== */

.offering-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.offering-list__item {
    padding: 0.875rem 0;
    border-bottom: 1px solid var(--mili-border);
}

.offering-list__item:last-child {
    border-bottom: none;
}

.offering-list__title {
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-ink);
}

/* ===== 6) 인증(로그인/회원가입) ===== */

/* 브랜드 로고 — 사이드바(_app-sidebar.jsp)·상단바(_app-topbar.jsp)와 같은 SVG(images/home/logo-mili-ai.svg). */
.auth-split__brand {
    display: inline-block;
}

/*
 * 로그인 전용 2단 레이아웃. 좌측은 비주얼, 우측은 폼.
 * 좌측 배경은 --mili-login-visual 로 갈아 끼운다(사진을 넣으려면 variables.css 의 값만 바꾼다).
 * 모바일에서는 비주얼을 감추고 폼만 세로로 채운다.
 */
.auth-split {
    display: grid;
    grid-template-columns: 1fr;
    min-height: 100vh;
}

/* Figma 8qAqeXw50F8GKDGkeW0Pua node 2803:72110(모바일, 2026-08-28) — 모바일 프레임엔
   비주얼 배너가 아예 없다. md 미만에서는 숨기고 768px 이상(기존 데스크톱 2단 레이아웃)에서만 보인다. */
.auth-split__visual {
    display: none;
    position: relative;
    overflow: hidden;
    min-height: 11rem;
    background: var(--mili-login-visual);
    background-size: cover;
    background-position: center;
}

/* 네온그린 방사형 글로우 — PBL 히어로 배너(pbl/project-list.css .pbl-hero::before)와 같은 조합. */
.auth-split__visual::before {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0.16;
    background-image: radial-gradient(circle at 80% 20%, var(--mili-brand) 0%, transparent 60%);
}

/* 실제로는 js/login.js#applyRandomVisual 이 접속마다 images/login/bg-N.webp(어두운 군사 사진)를
   덮어씌운다 — --mili-login-visual 그라디언트는 JS 실행 전/이미지 로드 실패 시 폴백일 뿐이다.
   사진이 테마와 무관하게 항상 어두우므로 흰 글자+그림자는 라이트 모드에서도 그대로 둔다. */
.auth-split__visual-caption {
    position: absolute;
    left: 1.5rem;
    bottom: 1.25rem;
    right: 1.5rem;
    color: var(--mili-neutral-50);
    text-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
}

.auth-split__visual-caption h2 {
    font-size: var(--mili-body-b1-size);
    font-weight: 800;
    letter-spacing: -0.02em;
    margin-bottom: 0.375rem;
}

/* 모바일은 비주얼 배너가 없어 입력칸과 구분할 필요가 없다 — 시안 실측 배경(--mili-app-bg)을
   그대로 쓰고, 로고~푸터 전체를 화면 세로 중앙에 놓는다(node 2803:72110 실측: 위/아래 여백 동일). */
.auth-split__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100vh;
    padding: 2rem 1.5rem 2.5rem;
    background: var(--mili-app-bg);
}

.auth-split__form {
    width: 100%;
    max-width: 24rem;
    margin: 0 auto;
}

/* 모바일 실측(node 2803:72117) Body/B1/Semibold 18px·line-height 1.4. 데스크톱은 아래
   768px 이상 블록에서 기존 h4/800/1.45 로 되돌린다(이번 시안은 모바일 프레임뿐이라 유지). */
.auth-split__welcome {
    font-size: var(--mili-body-b1-size);
    font-weight: var(--mili-font-weight-semibold);
    line-height: 1.4;
    letter-spacing: -0.02em;
    color: var(--mili-ink);
    margin: 1.25rem 0 2rem;
}

/* --mili-ink 라이트값(#1E2227)은 사이트 전역 잉크 톤이라 이 화면 실측(4038:312531, neutral/900
   #0e1013)과 미세하게 다르다 — 전역 토큰을 바꾸면 다른 모든 화면에 번지므로 로그인에서만
   리터럴로 못박는다. 다크는 --mili-ink(#F8F9FA)가 neutral/100 실측과 정확히 같아 그대로 둔다. */
.mili-light .auth-split__welcome {
    color: var(--mili-neutral-900);
}

@media (min-width: 768px) {
    .auth-split {
        grid-template-columns: 1fr 1fr;
    }

    .auth-split__visual {
        display: block;
        min-height: 0;
    }

    .auth-split__visual-caption {
        left: 2.5rem;
        bottom: 2.5rem;
        right: 2.5rem;
    }

    .auth-split__visual-caption h2 {
        font-size: var(--mili-heading-h3-size);
    }

    .auth-split__panel {
        justify-content: center;
        min-height: 0;
        padding: 3rem;
        background: var(--mili-surface);
    }

    .auth-split__welcome {
        font-size: var(--mili-heading-h4-size);
        font-weight: 800;
        line-height: 1.45;
    }
}

/* 로그인 입력창 — Figma 8qAqeXw50F8GKDGkeW0Pua node 2224:106242/106245 실측(사용자 확인, 2026-08-26)
   PC 는 --mili-radius-button(사각에 가까운 반경)이 정본이다.
   2026-09-11 모바일 QA(node 4054:210737) 재실측에서는 모바일이 corner radius 99(알약형)였으나,
   2026-09-21 디자인 수정 요청으로 모바일도 5px(--mili-radius-button)로 바뀌어 PC 와 같아졌다 —
   그래서 모바일 전용 반경 오버라이드를 없앴다. (2026-09-14 PC 영향 재검사: 모바일 전용 값이
   미디어쿼리 없이 전역에 걸리면 PC 입력창까지 바뀌는 회귀가 있었으니, 다시 모바일만 다르게 해야
   하면 반드시 @media (max-width: 767.98px) 안에서만 덮어쓸 것.) */
/* 셀렉터에 .auth-split__form 조상을 더 얹은 이유 — .mili-style-refresh .form-control(§6-1,
   background-color/border-color 를 var(--mili-surface)/var(--mili-control-border)로 못박는 규칙)와
   .mili-theme-dark .form-control(다크 12% 화이트 테두리) 이 전부 클래스 2개(0,2,0)라, 입력창 요소에
   클래스 하나만 더한 .auth-field/input.auth-field(0,1,0/0,1,1)로는 특정성이 못 미치거나 겨우 같아
   파일 순서 싸움이 됐다 — 그 순서가 이 로그인 규칙보다 뒤라 매번 졌다(style-diff.cjs 실측, 2026-09-11:
   테두리 10px 로 뭉개짐 → input. 로 고쳐도 라이트에서 다시 다크 테두리색으로 뭉개짐). 조상 클래스
   하나를 더해 항상 0,2,1 이상으로 만들어야 어떤 조합이 나중에 추가돼도 안전하다. */
.auth-split__form input.auth-field {
    background: var(--mili-neutral-870);
    border: 1px solid var(--mili-neutral-750);
    border-radius: var(--mili-radius-button);
    padding: 0.75rem 1.25rem;
}

/* 라이트 실측(node 4038:312531) — neutral 스케일은 다크·라이트 공용값이라 테마별로 다른
   단계를 직접 골라야 한다(다크 870/750 어두운 회색 → 라이트 100/400 옅은 회색). */
.mili-light .auth-split__form input.auth-field {
    background: var(--mili-neutral-100);
    border-color: var(--mili-neutral-400);
}

/* 아이디 저장 체크박스 — 전역 .form-check-input(다크: 반투명 화이트 오버레이, 라이트: 테두리만
   보정)은 이 화면 실측과 다르다(다크 4054:210790/2318:37239, 라이트 4038:276941) — 입력창과 같은
   면·테두리 색을 그대로 쓴다. .mili-theme-dark 를 더해 특정성을 올려야 전역
   .mili-theme-dark .form-check-input(0,2,0)을 확실히 이긴다(같은 값이면 파일 순서에 기대게 된다 —
   위 .auth-field 반경 버그와 같은 사고를 여기서도 만들지 않으려는 것). */
.mili-theme-dark .auth-split__form .form-check-input {
    background-color: var(--mili-neutral-870);
    border-color: var(--mili-neutral-750);
}

.mili-light .auth-split__form .form-check-input {
    background-color: var(--mili-neutral-100);
    border-color: var(--mili-neutral-400);
}

/* "아이디 저장" 라벨 — 명시 규칙이 없어 상속된 --mili-ink 를 썼는데, 위 heading 과 같은 이유로
   라이트에서만 실측(4038:276942, neutral/900)과 미세하게 어긋난다. */
.mili-light .auth-split__form .form-check-label {
    color: var(--mili-neutral-900);
}

.auth-field::placeholder {
    color: var(--mili-neutral-600);
    opacity: 1;
}

/* 로그인 하단 보조 링크 (아이디 찾기 | 비밀번호 찾기 | 회원가입) — Figma 실측은 Body/B3(14px)/
   Caption(12px)로 서로 다르지만, 사용자 요청(Body/B2/Regular 16px 통일)을 그대로 반영한다. */
.auth-links {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--mili-space-4);
    margin-top: var(--mili-space-5);
    font-size: var(--mili-body-b2-size);
}

.auth-links a {
    color: var(--mili-neutral-550);
    text-decoration: none;
    font-weight: var(--mili-font-weight-regular);
}

/* neutral-550(#bfc0c2)은 다크 배경 실측값이라 흰 배경에서는 거의 안 보인다 — 라이트 실측
   (node 4038:312531, Link 텍스트)은 neutral/700(#66686c)이라 테마별로 갈라 써야 한다. */
.mili-light .auth-links a {
    color: var(--mili-neutral-700);
}

.auth-links a:hover {
    color: var(--mili-primary-strong);
}

/* 실측(다크 4054:210797 #46494e, 라이트 4038:276947 #e9e9e9)은 공용 --mili-border(다크 #2B3036)
   보다 한 단계 밝다 — 링크 텍스트와 같은 neutral 계단을 쓴다. */
.auth-links__divider {
    width: 1px;
    height: 0.875rem;
    background: var(--mili-neutral-750);
}

.mili-light .auth-links__divider {
    background: var(--mili-neutral-400);
}

/* 로그인 화면의 개인정보처리방침 줄 — .site-footer 를 그대로 재사용하되(위 §6 주석 참고),
   공용 여백(상60/하100)은 3열 사이트 푸터 기준이라 좁은 인증 패널엔 과하다.
   Figma 실측(node 2224:106262/106264)은 링크줄→구분선 32px·구분선→문구 32px·문구 뒤엔 없음이라
   .auth-split__form 범위로 좁혀 오버라이드한다(다른 화면의 .site-footer 는 영향받지 않음).
   글자도 요청대로 나머지 세 링크와 같은 크기·색으로 맞추고, 법정 강조 스타일(굵게+ink)은
   이 줄이 이미 유일한 항목이라 구분선만으로 충분해 걷어낸다(사용자 확인, 2026-08-26). */
.auth-split__form .site-footer {
    margin-top: var(--mili-space-8);
    padding: var(--mili-space-8) 0 0;
}

.auth-split__form .site-footer__links {
    font-size: var(--mili-body-b2-size);
}

.auth-split__form .site-footer__link,
.auth-split__form .site-footer__link--strong {
    color: var(--mili-neutral-550);
    font-weight: var(--mili-font-weight-regular);
}

/* 위 .auth-links 와 같은 이유(neutral-550 은 다크 실측값) — 라이트에서는 neutral/700 으로. */
.mili-light .auth-split__form .site-footer__link,
.mili-light .auth-split__form .site-footer__link--strong {
    color: var(--mili-neutral-700);
}

/* ===== 6-1) 라벨/입력 2열 폼 표 ===== */

/*
 * 회원가입·찾기 화면의 "좌 라벨 / 우 입력" 표.
 * 모바일에서는 라벨이 위로 쌓이고, md 이상에서 2열이 된다(레거시 테이블 레이아웃 대체).
 */
.form-table {
    border-top: 2px solid var(--mili-ink);
    margin-bottom: var(--mili-space-6);
}

.form-table__row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.375rem;
    padding: 0.875rem 0;
    border-bottom: 1px solid var(--mili-border);
}

.form-table__label {
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
    font-size: 0.9375rem;
    margin: 0;
}

.form-table__field {
    min-width: 0;
}

.form-table__hint {
    display: block;
    margin-top: 0.375rem;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

@media (min-width: 768px) {
    .form-table__row {
        grid-template-columns: 11rem minmax(0, 1fr);
        gap: var(--mili-space-4);
        align-items: center;
        padding: 1rem 0.75rem;
    }

    .form-table__label {
        align-self: center;
    }
}

/* 입력 + 버튼(중복확인 등)을 한 줄에 두는 묶음 */
.field-inline {
    display: flex;
    flex-wrap: wrap;
    gap: var(--mili-space-2);
    align-items: center;
}

.field-inline > .form-control,
.field-inline > .form-select {
    flex: 1 1 8rem;
    min-width: 0;
}

.field-inline > .btn {
    flex: 0 0 auto;
}

/* 필수 표시 */
.required-mark {
    color: var(--mili-danger);
}

.form-legend {
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
    margin-bottom: var(--mili-space-2);
}

/* ===== 6-2) 약관 동의 ===== */

/* 2026-09-21 사용자 지정 — 항목 사이 간격 28px → 40px (대응 space 토큰 없음). */
.terms-block {
    margin-bottom: 40px;
}

.terms-block__title {
    font-size: 1.0625rem;
    font-weight: 800;
    color: var(--mili-ink);
    margin-bottom: var(--mili-space-2);
}

/* 약관 본문 — 고정 높이 스크롤 박스 */
.terms-box {
    height: 11rem;
    overflow-y: auto;
    padding: 1rem 1.125rem;
    border: 1px solid var(--mili-border);
    border-radius: 0;
    background: var(--mili-surface);
    font-size: var(--mili-body-b3-size);
    line-height: 1.7;
    color: var(--mili-body);
}

.terms-box h4 {
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
    margin: 0.875rem 0 0.25rem;
}

.terms-box h4:first-child {
    margin-top: 0;
}

.terms-box p,
.terms-box li {
    margin-bottom: var(--mili-space-1);
}

.terms-box ul {
    padding-left: 1.125rem;
    margin-bottom: var(--mili-space-2);
}

.terms-agree {
    margin-top: 0.625rem;
}

/* 전체 동의 줄 */
.terms-all {
    display: flex;
    justify-content: center;
    padding: 1rem;
    margin-bottom: var(--mili-space-6);
    border-radius: 0;
    background: var(--mili-bg);
    border: 1px solid var(--mili-border);
}

.terms-all .form-check-label {
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

/* 동의 화면에서 처리방침 전문(/privacy-policy)으로 나가는 안내 줄 */
.terms-policy-link {
    margin-bottom: 0;
    text-align: center;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

/* 폼 하단 버튼 줄 (취소 / 저장) */
.form-actions {
    display: flex;
    justify-content: center;
    gap: var(--mili-space-2);
    margin-top: var(--mili-space-6);
}

.form-actions .btn {
    min-width: 7rem;
}

/*
 * 2026-08-30 Figma 반영(node 3184:42220/3230:71254 모바일, 1978:193152 PC) — 제목이 카드
 * 바깥(배경 없음, 중앙정렬)에 있고 그 아래 카드가 "안내 박스"를 한 겹 더 감싸는 2단 구조로
 * 재구성. 제목(.auth-card__title)은 이제 .auth-card 의 형제(자식이 아님)로 마크업을 바꿨다
 * — withdraw.jsp/withdraw-cancel.jsp/re-privacy.jsp 공통. 본문 폭은 화면 CSS 에서 max-width 를
 * 박지 않는 규칙(§8)에 따라 shellWidth="narrow"(--mili-content-narrow, 960px, 실측과 일치)를
 * 호출 화면에서 넘기고 .auth-card 는 그 안에서 width:100% 로 채운다.
 */
.auth-shell {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-block: 2.5rem;
}

.auth-card__title {
    font-weight: 800;
    text-align: center;
    margin-bottom: 40px; /* 실측(1978:193152): Heading~카드 간격 40px. 8단계 스케일에 40px 칸이 없어 리터럴 */
}

@media (max-width: 767px) {
    .auth-card__title {
        margin-bottom: var(--mili-space-5); /* 실측(3184:42233~42183): 모바일은 20px */
    }
}

.auth-card {
    width: 100%;
    background: var(--mili-neutral-930);
    border: none;
    border-radius: var(--mili-radius-card);
    box-shadow: none;
    padding: var(--mili-space-8); /* 실측(1978:193197 Container:margin): 상하좌우 32px */
}

.mili-light .auth-card {
    background: var(--mili-neutral-50);
}

@media (max-width: 767px) {
    .auth-card {
        /* 실측(3184:42183/42184): 모바일은 좌우 패딩 0 — 안내 박스만 자체 24px 패딩으로 들여쓴다 */
        padding: var(--mili-space-8) 0;
    }
}

/* 안내 박스 — 부트스트랩 alert-warning(황색)을 걷어내고 시안의 뉴트럴 카드로 교체.
   실측: 3184:42185(다크 Neutral/870) / 3230:71272(라이트 Neutral/100). */
.auth-card .alert.alert-warning {
    background: var(--mili-neutral-870);
    border: none;
    border-radius: var(--mili-radius-card);
    padding: var(--mili-space-6); /* 24px */
    color: var(--mili-neutral-100);
}

.mili-light .auth-card .alert.alert-warning {
    background: var(--mili-neutral-100);
    color: var(--mili-neutral-900);
}

.auth-card .alert.alert-warning > p:first-child {
    font-size: 1.125rem; /* 18px, 실측 Body/B1/SemiBold */
    font-weight: 600;
    line-height: var(--mili-body-b1-line); /* 1.4, 실측과 토큰이 그대로 일치(25.2px) */
}

/* 목록 — 실측(Body/B2/Regular)은 행간 160%인데 전역 --mili-body-b2-line 은 130%(다른 화면 기준)라
   토큰을 못 쓴다(.btn.large 의 같은 사례를 따라 리터럴로 둔다). 강조 항목은 <strong> 기본 굵기(700)가
   아니라 실측 SemiBold(600)로 낮춘다 — 이 화면(3184:42193/3230:71280, 3184:42189/3230:71276)만의
   규칙이라 전역 strong 재정의 대신 이 스코프에서만 건다. */
.auth-card .alert.alert-warning ul {
    line-height: 1.6;
}

.auth-card .alert.alert-warning strong {
    font-weight: var(--mili-font-weight-semibold);
}

/* 링크 — 다크는 전역 --mili-primary(#B4FF39)와 실측(Primary/500)이 이미 같다. 라이트만
   실측값(Primary/Text #2F871D)으로 스코프 교체 — theme/figma-classes.css 의 .tag.vod 라이트와
   같은 값·같은 판단(기존 팔레트 어디와도 안 맞는 별도 브랜드 그린이라 리터럴로 남긴다).
   밑줄 — 실측(3184:42195/3230:71282)엔 언더라인이 없다. 부트스트랩 리셋의 기본 a 밑줄을 끊는다. */
.auth-card a {
    text-decoration: none;
}

.mili-light .auth-card a {
    color: #2F871D;
}

/* 탈퇴 사유 텍스트영역 — 실측: 3184:42199(다크, 테두리 없음) / 3230:71286(라이트, Neutral/400 테두리) */
.auth-card textarea.form-control {
    background: var(--mili-neutral-870);
    border: none;
    border-radius: var(--mili-radius-card);
}

.auth-card textarea.form-control::placeholder {
    color: var(--mili-neutral-600); /* 실측 3184:42200 Neutral/600 */
}

.mili-light .auth-card textarea.form-control {
    background: var(--mili-neutral-100);
    border: 1px solid var(--mili-neutral-400);
    color: var(--mili-neutral-900);
}

.mili-light .auth-card textarea.form-control::placeholder {
    color: var(--mili-neutral-700); /* 실측 3230:71287 Neutral/700 */
}

/* ===== 7) 빈 상태 ===== */

/* 2026-08-13 QA17 재지적 — 상하 패딩이 좁아 보인다는 지적으로 3rem → 4.5rem. */
/* 2026-08-25 — 라이트 모드는 --mili-surface 와 --mili-app-bg 가 둘 다 #FFFFFF라 배경만으로는
   영역이 안 보였다. .widget-card 와 같은 방식으로 테두리를 둬 라이트에서도 경계가 드러나게 한다. */
.empty-state {
    padding: 155px 1rem;
    text-align: center;
    color: var(--mili-muted);
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
}

/* 2026-08-25 — Figma 버튼 컴포넌트(8qAqeXw50F8GKDGkeW0Pua, node 2042:215427) Default(Primary)
   실측값을 .empty-state 안의 CTA 하나에만 한정했었으나(라임 유지가 "기결정"이라고 잘못
   기록돼 있었다), 사용자 확인 결과 그런 결정을 내린 적이 없고 시안대로 전역 적용이 맞다 —
   .btn-primary 자체를 흑백으로 바꾸므로 이 화면 전용 오버라이드는 삭제한다.
   (2026-08-27 후속 — .btn-primary 는 값이 같은 theme/figma-classes.css `.btn.default` 로 통합되어 사라졌다.) */

/* ===================================================================
   8) 앱 셸 (좌측 사이드바 + 상단바 + 본문) — 모든 화면 공용
   -------------------------------------------------------------------
   짝이 되는 마크업 3개:
     common/_app-sidebar.jsp   좌측 IA
     common/_app-topbar.jsp    상단 유틸리티
     .app-frame / .app-frame__body / .app-page   (호출 화면이 직접 씀)

   한때 같은 얼개가 다섯 벌로 복제돼 있었다 — .home-layout / .mypage-dash-layout /
   .catalog-layout / .pbl-layout / .assess-layout. 전부 이 클래스로 수렴시켰고
   화면 전용 CSS 에는 세로 여백 같은 화면 고유값만 남겼다. **새 화면은 이 클래스를 쓴다.**
   (직접 쓰지 말고 common/_shell-top.jsp 를 include 한다 — 아래 규약 참고.)

   ---------- 본문 폭 · 좌우 여백 ----------
   정본 규칙은 mydocs/ss02/design-system.md §7 레이아웃 그리드다.

     .app-page           --mili-content-standard (1200px)  Standard — 기본값(대부분의 화면)
     .app-page--wide     --mili-content-max      (1440px)  Wide     — 홈 · 미션 여정
     .app-page--narrow   --mili-content-narrow    (960px)  Narrow   — 읽기·폼 전용

   좌우 여백은 padding 이 아니라 width 가 만든다:
       width: min(calc(100% - 여백×2), 최대폭)
   여백(--mili-page-padding)은 구간마다 48 / 24 / 20px 로 토큰이 알아서 바뀐다
   (구간 정의는 theme/reference.css §페이지 그리드 한 곳이 정본).

   ⚠️ **화면 전용 CSS 에서 본문 열의 max-width · 좌우 padding · margin-inline 을 쓰지 말 것.**
   예전에는 화면마다 시안 실측값(VOD 목록 1360 / VOD 상세 1120 / PBL 1200 / 역량진단 900)을
   자기 CSS 에서 덮어썼는데, 이 파일보다 화면 CSS 가 나중에 로드되는 탓에 위 모바일 규칙이
   조용히 무효화됐다(375px 폰에서 좌우 32px 이 그대로 남았다). 폭이 다르면 값을 쓰지 말고
   _shell-top.jsp 에 shellWidth 로 wide|narrow 를 넘긴다. 세로 여백만 화면 CSS 소관이다.

   사이드바 폭은 --mili-sidebar-width (≥1280px 240px / 1024–1279px 80px 아이콘 레일,
   <1024px 는 230px 드로어라 본문 자리를 차지하지 않는다).
   홈(/)도 2026-08-06 부터 이 규칙 안에 있다(.app-page--wide) — 더 이상 예외가 아니다.
   자세한 경위는 design-system.md §7-4.
   =================================================================== */

.app-frame {
    display: flex;
    min-height: 100vh;
}

.app-frame__body {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
    background: var(--mili-canvas);
}

/* ---------- 상단바 ----------
   마크업은 WEB-INF/views/common/_app-topbar.jsp 하나다.

   실측(참고 앱 계산값): 배경은 블러 유리가 아니라 **불투명 단색**(--mili-app-bg 상당,
   우리 토큰으론 --mili-canvas)이고 backdrop-filter 는 아예 쓰지 않는다. 위아래는 12px 로 고정이고
   좌우는 --mili-page-padding 을 그대로 따라간다 — §7 개편 이후 48/24/20px 로 바뀐다(도입 당시엔
   32px 였다. 전에 "16px 24px 8px" 로 위아래가 다른 비대칭값을 썼던 건 실측이 아니라 어림값이었다).
   다크 테마는 테두리도 없다(라이트에서만 하단 경계선). */
/*
 * 높이(72px, 767px 이하 56px) · 좌우 패딩(--mili-page-padding) · 배경 · 아래 테두리는
 * theme/reference.css 의 .mili-page-header 가 준다(마크업이 두 클래스를 함께 단다).
 * 여기서는 겹침 순서(sticky)만 맡는다 — 값을 다시 쓰면 참고 규칙을 덮어 버린다.
 */
/* ⚠️ 위 주석의 "마크업이 두 클래스를 함께 단다"는 사실이 아니다 — _app-topbar.jsp 는
   .app-topbar 만 달고 .mili-page-header 를 달지 않으며, 아래 .app-topbar__inner 래퍼도 두지 않고
   유틸리티를 <header> 의 직계 자식으로 둔다. 그래서 높이·좌우 패딩·가로 배치를 아무도 주지 않아
   .app-topbar__spacer 의 flex:1 이 죽고 유틸리티(테마·검색·알림·프로필)가 좌측으로 몰려 있었다.
   마크업을 바꾸는 대신(전 화면이 이 조각 하나를 쓴다) 실제 마크업 모양에 맞춰 여기서 준다. */
/* ⚠️ 2026-08-12 확인 — 배경이 없어서 sticky 로 붙어도 투명해 스크롤하는 본문 콘텐츠가
   그대로 비쳐 보였다(과정/프로젝트 상세에서 재현). pbl/missions.css 주석은 이 바가 "이미
   불투명"이라고 전제하는데 실제로는 아니었다 — 셸 배경과 같은 --mili-canvas 로 막는다. */
.app-topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    /* 2026-09-18 Figma 재실측(node 2042:216606) — 8px 균일 → 12px(사용자 확인). 아이콘 3개
       내부 간격은 .app-topbar__icons 가 0으로 다시 좁힌다. */
    gap: var(--mili-space-3);
    height: var(--mili-topbar-height);
    padding-inline: var(--mili-page-padding);
    background: var(--mili-canvas);
}

/* 테마·검색·알림 아이콘 3개는 서로 붙어있다(gap 0) — 그룹 경계(.app-topbar 의 12px)와
   대비된다. */
.app-topbar__icons {
    display: flex;
    align-items: center;
    gap: 0;
}

.app-topbar__divider {
    width: 1px;
    height: 18px;
    background: rgba(var(--mili-line-rgb), 0.8);
}

/* 사용처 없음(마크업에 이 래퍼가 없다). 값은 위 .app-topbar 가 직접 들고 있다. */
.app-topbar__inner {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
}

/* 아래 테두리 유무는 reference.css 의 .mili-page-header / .mili-light .mili-page-header 가 정한다
   (다크는 없고 라이트만 1px). 좌우 패딩도 --mili-page-padding 이 32/24/20/16px 로 알아서 바뀐다. */

.app-topbar__spacer {
    flex: 1 1 auto;
}

/* 브레드크럼(Figma node 3425:73349 · 갤러리 상세 4038:313315 · 공지사항 상세 라이트 4682:236611 실측)
   Body/B3/Regular 14px · 줄높이 130% · 자간 0, 항목 사이 구분자 "/" 와 gap 4px.
   글자색은 다크 Neutral/600(#989a9e), 라이트 Neutral/800(#2b2e33) — 라이트가 다크와 다른 토큰이다.
   마크업은 _app-topbar.jsp(crumbCurrent 가 있는 화면만). 규칙·진행 현황: docs/design/breadcrumb-rollout.md.
   AI 판단(시안 미명시): 상위 항목 링크의 hover 밑줄, 현재 화면 항목은 링크가 아닌 텍스트. */
.app-breadcrumb {
    flex: 0 1 auto;
    min-width: 0;
    color: var(--mili-neutral-600);
    font-size: var(--mili-body-b3-size);
    line-height: var(--mili-body-b3-line);
    font-weight: var(--mili-font-weight-regular);
}

.mili-light .app-breadcrumb {
    color: var(--mili-neutral-800);
}

.app-breadcrumb__list {
    display: flex;
    align-items: center;
    gap: var(--mili-space-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

.app-breadcrumb__item {
    white-space: nowrap;
}

.app-breadcrumb__item + .app-breadcrumb__item::before {
    content: "/";
    margin-inline-end: var(--mili-space-1);
}

/* 좁아지면 브레드크럼만 줄어들고(아래 말줄임) 우측 유틸리티·프로필 칩은 그대로여야 한다 —
   이 규칙이 없으면 칩 안의 "이름님"이 두 줄로 꺾인다(laptop 1100px 에서 재현). */
.app-topbar__icons,
.app-topbar__divider,
.app-topbar__profile-chip {
    flex-shrink: 0;
}

.app-topbar__profile-chip {
    white-space: nowrap;
}

/* 긴 강의·프로젝트명은 잘라서 우측 유틸리티를 밀어내지 않게 한다. */
.app-breadcrumb__item--current {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.app-breadcrumb__link {
    color: inherit;
    text-decoration: none;
}

.app-breadcrumb__link:hover,
.app-breadcrumb__link:focus-visible {
    color: inherit;
    text-decoration: underline;
}

/* 1024px 미만은 모바일 GNB(로고+아이콘)로 바뀐다 — 모바일 헤더 시안(node 4044:197950)엔 브레드크럼이 없다. */
@media (max-width: 1023.98px) {
    .app-breadcrumb {
        display: none;
    }
}

/* 드로어일 때만 보이는 좌측 로고(데스크톱은 사이드바에 로고가 있다). */
.app-topbar__brand {
    display: inline-flex;
    margin-left: var(--mili-space-1);
    text-decoration: none;
}

.app-topbar__brand img {
    display: block;
    width: 90px;
    height: auto;
}

/* 로고 SVG 는 흰색(#fff) 고정 자산이라 라이트 배경에서는 그냥 두면 안 보인다
   (.app-sidebar__brand 에 이미 같은 처리가 있다 — 상단바 쪽엔 빠져 있었다). */
.mili-light .app-topbar__brand img {
    filter: brightness(0);
}

/* 드로어 열기 버튼. 아이콘 버튼과 달리 테두리 없는 고스트다. */
.app-topbar__menu {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 6px;
    border: 0;
    background: transparent;
    color: var(--mili-fg-muted);
    font-size: var(--mili-heading-h4-size);
    line-height: 1;
    cursor: pointer;
}

.app-topbar__menu:hover,
.app-topbar__menu:focus {
    color: var(--mili-ink);
}

/* 우상단 아이콘 버튼(검색·알림·테마 토글). 시안: size-9 rounded-[14px] bg-white/5 border-white/10 */
/* 실측: size-10(40px) · 배경 없음 · opacity 75%→100% (hover 는 배경이 아니라 불투명도로 표시한다).
   이전에는 36px + 배경 채움이었는데 참고는 그 방식을 쓰지 않는다. */
/* 2026-08-25: Figma node 2042:215356("공통" Header) 실측도 이 버튼을 40px 로 그려 위 실측과
   일치하고, 반경은 rounded-[10px] — design-tokens.md 가 이미 "아이콘 버튼"용으로 지정해 둔
   --mili-radius-control(10px) 그대로다. VOD/PBL 화면(.mili-style-refresh 미적용)은 그동안
   반경 지정이 아예 없어 각져 있었다 — 여기서 기본값을 채운다. 그 밖의 화면은
   .mili-style-refresh .app-icon-btn(아래 §"Jalynne 공통 시각 보정")이 더 높은 명시도로
   --mili-radius-card(12px)를 계속 덮어써 이번 변경의 영향을 받지 않는다. */
/* 2026-08-26 재확인(같은 node 2042:215356) — 이 버튼은 테두리가 없다. 예전 border-current/15 는
   참고 저장소 실측 근거였는데, 공통 Header 시안이 정본이라 이번엔 전 화면(VOD/PBL 포함)에서
   테두리를 없앤다(사용자 확인) — 아래 .mili-style-refresh .app-icon-btn 의 border-color:
   transparent 오버라이드는 이제 중복이지만 해가 없어 그대로 둔다. */
.app-icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: var(--mili-radius-control);
    background: transparent;
    color: var(--mili-fg);
    font-size: var(--mili-body-b1-size);
    line-height: 1;
    text-decoration: none;
    opacity: 0.75;
    transition: opacity 160ms ease;
    cursor: pointer;
}

.app-icon-btn:hover,
.app-icon-btn:focus {
    opacity: 1;
    color: var(--mili-ink);
}

/* reference.css 의 `button:not(.btn) { font: ... !important }` 가 <button> 자기 자신의
   font-size 를 14px 로 못박는다(버튼 글자 크기 정책) — 그런데 이 아이콘 버튼들 중 절반은
   <button>(테마 토글 등), 절반은 <a>(알림벨 등)라 같은 .app-icon-btn 인데도 아이콘 크기가
   갈렸다(실측: 테마 토글 14px vs 알림벨 18px, 2026-08-31 모바일 상단바 사용자 지적).
   자식 <i> 에 font-size 를 직접 박아 상속을 끊는다 — !important 는 "같은 요소"의 속성끼리만
   싸우고 상속에는 개입하지 않으므로, 여기서 직접 지정한 값이 부모의 !important 를 그냥 이긴다. */
.app-icon-btn > .bi {
    font-size: var(--mili-body-b1-size);
}

/* 2026-08-31 Figma QA(node 3382:63411) — 테마 토글 아이콘을 sun/moon 인라인 SVG 두 장으로 바꾸면서
   전환은 JS 클래스 토글이 아니라 .mili-light 유무로만 가른다(다크→sun 노출, 라이트→moon 노출 —
   기존 부트스트랩 아이콘 토글과 같은 의미). */
.app-topbar__theme-icon--moon {
    display: none;
}

.mili-light .app-topbar__theme-icon--sun {
    display: none;
}

.mili-light .app-topbar__theme-icon--moon {
    display: block;
}

/* 읽지 않은 알림 표시 점.
   2026-08-26 Figma Header Light 인스턴스 재조회(node 2042:216666) — Primary/500 로컬값이
   #afe57e 다. 전역 --mili-brand(=--mili-primary)는 최근 계속 재조정 중이라(618F20→9FE970→
   91CC5C) 이 지점만 사이드바 선택색과 같은 방식으로 시안 로컬값에 고정한다(사용자 확인). */
:root {
    --mili-notif-dot: var(--mili-brand);
    --mili-notif-dot-rgb: var(--mili-brand-rgb);
}

.mili-light {
    /* 2026-09-18 Figma Color 노드 재개편 — Primary/500 라이트 로컬값이 #afe57e → #1a987f 로
       바뀌었다(민트→티일 색상군 전환). 위 주석 방침(로컬값에 고정) 그대로 유지, 값은 새
       Primary/500 라이트와 정확히 일치해 var() 로 참조한다. */
    --mili-notif-dot: var(--mili-primary-500);
    --mili-notif-dot-rgb: 26, 152, 127;
}

.app-icon-btn__dot {
    position: absolute;
    top: 7px;
    right: 7px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--mili-notif-dot);
    box-shadow: 0 0 4px rgba(var(--mili-notif-dot-rgb), 0.8);
}

/*
 * 알림 드롭다운 패널(_app-topbar.jsp, app-shell.js#initNotificationPanel).
 *
 * 모달이 아니라 툴팁/팝오버 느낌 — 배경 스크림을 두지 않는다(js/pbl/brief.js 의 미션 메뉴와
 * 같은 방식). position:fixed 로 화면 기준 배치되고 위치는 JS 가 계산해 top/left 인라인으로 찍는다.
 * z-index 는 사이드바(1045)·스크림(1040)보다 위, 모달(1060)보다 아래로 잡는다.
 */
.notif-panel {
    position: fixed;
    z-index: 1050;
    width: 360px;
    max-width: calc(100vw - 16px);
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.08);
    border-radius: var(--mili-radius-card);
    background: var(--mili-panel-raised);
    box-shadow: var(--mili-shadow-lg);
    overflow: hidden;
}

.notif-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid rgba(var(--mili-fg-rgb), 0.06);
}

.notif-panel__title {
    font-size: 0.9375rem;
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

/* Figma 재확인(2026-08-25, node 2224-93099 "알림_팝업"): 헤더의 "모두 읽음 처리"도 푸터의
   "전체 알림 보기"도 브랜드색이 아니라 밝은 본문색(ink)이다 — 둘 다 톤다운되지 않는다.
   (이전엔 "모두 읽음"이 톤다운 보조텍스트라고 판단했었는데, 이번 실측으로 뒤집혔다.) */
.notif-panel__read-all {
    border: none;
    background: none;
    padding: 0;
    color: var(--mili-ink);
    font-size: 0.75rem;
    font-weight: var(--mili-font-weight-medium);
    cursor: pointer;
}

.notif-panel__read-all:hover {
    color: var(--mili-fg-muted);
}

.notif-panel__list {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0;
    max-height: 360px;
    overflow-y: auto;
}

.notif-panel__item {
    position: relative;
    display: flex;
    align-items: stretch;
}

.notif-panel__item-link {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    flex-direction: column;
    /* 2026-09-18 Figma 재실측(node 2224:70959) — 텍스트 3줄 세로 gap 6px(구 2px). */
    gap: 0.375rem;
    padding: 0.625rem 2.25rem 0.625rem 1rem;
    color: inherit;
    text-decoration: none;
    transition: background-color 0.15s ease;
}

.notif-panel__item-link:hover {
    background: rgba(var(--mili-fg-rgb), 0.05);
}

.notif-panel__item-delete {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border: none;
    border-radius: var(--mili-radius-pill, 999px);
    background: none;
    color: var(--mili-fg-faint);
    font-size: 0.75rem;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.notif-panel__item-delete:hover {
    background: rgba(var(--mili-fg-rgb), 0.08);
    color: var(--mili-ink);
}

.notif-panel__item-delete:disabled {
    opacity: 0.5;
    cursor: default;
}

/* 2026-09-18 Figma 재실측(node 2224:70959) — 제목 Body/B3/SemiBold 14px(구 13px). */
.notif-panel__item-title {
    font-size: 0.875rem;
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-fg-faint);
}

/* 본문 Body/B3/Regular 14px(구 12px). */
.notif-panel__item-message {
    font-size: 0.875rem;
    color: var(--mili-fg-faint);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 날짜 Caption/Regular 12px(구 11px). */
.notif-panel__item-date {
    font-size: 0.75rem;
    color: var(--mili-fg-faint);
}

/* 안읽음만 강조한다 — 읽은 항목이 기본(딤)이고 안읽음이 예외적으로 살아난다. */
.notif-panel__item--unread .notif-panel__item-title,
.notif-panel__item--unread .notif-panel__item-message,
.notif-panel__item--unread .notif-panel__item-date {
    color: var(--mili-ink);
}

.notif-panel__item--unread .notif-panel__item-title::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-right: 0.375rem;
    border-radius: 999px;
    background: var(--mili-brand);
    vertical-align: middle;
}

.notif-panel__empty {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--mili-fg-faint);
    font-size: 0.8125rem;
}

.notif-panel__footer {
    display: block;
    padding: 0.75rem 1.25rem;
    border-top: 1px solid rgba(var(--mili-fg-rgb), 0.06);
    color: var(--mili-ink);
    font-size: 0.75rem;
    font-weight: var(--mili-font-weight-medium);
    text-align: center;
    text-decoration: none;
    transition: background-color 0.15s ease;
}

.notif-panel__footer:hover {
    background: rgba(var(--mili-fg-rgb), 0.05);
}

@media (max-width: 480px) {
    .notif-panel {
        width: calc(100vw - 16px);
    }
}

/*
 * 프로필 드롭다운 패널(_app-topbar.jsp, app-shell.js#initProfilePanel, Figma node 4857:198972).
 * .notif-panel 과 같은 팝오버 방식(position:fixed, 배경 스크림 없음, JS 가 위치 계산).
 */
.profile-panel {
    position: fixed;
    z-index: 1050;
    width: 220px;
    max-width: calc(100vw - 16px);
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.08);
    border-radius: var(--mili-radius-card);
    background: var(--mili-panel-raised);
    box-shadow: var(--mili-shadow-lg);
    overflow: hidden;
}

.profile-panel__header {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    padding: 0.875rem 1rem;
    border-bottom: 1px solid rgba(var(--mili-fg-rgb), 0.06);
}

.profile-panel__avatar {
    width: 40px;
    height: 40px;
    border-radius: var(--mili-radius-pill);
    object-fit: cover;
    flex: none;
}

.profile-panel__info {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    min-width: 0;
}

.profile-panel__name {
    font-size: 0.875rem;
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.profile-panel__unit {
    font-size: 0.75rem;
    color: var(--mili-fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.profile-panel__list {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0;
}

.profile-panel__divider {
    border-top: 1px solid rgba(var(--mili-fg-rgb), 0.06);
    margin: 0.25rem 0;
}

.profile-panel__logout-form {
    padding: 0.25rem 0;
}

.profile-panel__item {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
    width: 100%;
    padding: 0.5rem 1rem;
    border: none;
    background: none;
    color: var(--mili-ink);
    font-size: 0.8125rem;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.profile-panel__item:hover {
    background: rgba(var(--mili-fg-rgb), 0.05);
}

.profile-panel__item i {
    font-size: 1rem;
    color: var(--mili-fg-muted);
}

/* 프로필 칩(로그인 시 이름, 비로그인 시 "로그인").
   2026-08-25: design-tokens.md 가 --mili-radius-control(10px)의 용도로 "아이콘 버튼 · 프로필 칩 ·
   입력 컨트롤"을 이미 명시하고 있고 Figma node 2042:215356 도 이 칩을 rounded-[10px] 로 그리는데,
   반경 지정이 아예 빠져 있었다(각짐). .mili-style-refresh 화면은 이 칩을 박스형으로 안 그려서
   (배경·테두리 자체가 없음, 위 .mili-style-refresh .app-chip-user 참고) 반경이 안 보이고,
   VOD/PBL(스타일 리프레시 미적용) 화면에서만 실제로 각진 채 노출되고 있었다 — 여기서 채운다. */
.app-chip-user {
    display: inline-flex;
    align-items: center;
    gap: var(--mili-space-2);
    padding: 7px 12px 7px 9px;
    border: 1px solid var(--mili-line);
    border-radius: var(--mili-radius-control);
    background: rgba(var(--mili-fg-rgb), 0.05);
    color: var(--mili-ink);
    font-size: var(--mili-caption-size);
    font-weight: var(--mili-font-weight-bold);
    text-decoration: none;
}

.app-chip-user:hover,
.app-chip-user:focus {
    background: rgba(var(--mili-fg-rgb), 0.1);
    color: var(--mili-ink);
}

/* 2026-09-18 Figma 재실측(node 2042:216606) — 이 인스턴스는 실제로는 32px 정사각 사진이 아니라
   27px 원형 그라디언트 "Lv.24" 레벨배지다. 이번 라운드는 레벨배지 UI를 새로 만들지 않기로
   하고, 크기만 시안에 맞춰 27px로 줄인다(이미지 콘텐츠는 기존 프로필 사진 그대로 유지, 사용자
   확인). */
.app-chip-user__avatar {
    width: 27px;
    height: 27px;
    border-radius: 50%;
    object-fit: cover;
    object-position: top;
    background: rgba(0, 0, 0, 0.6);
}

.app-chip-user > .bi {
    font-size: 0.6875rem;
    color: var(--mili-fg-faint);
}

/* 로그인 상태 프로필 버튼 — 2026-09-21 Figma 재실측(공통 Header 원본, get_design_context: 다크 4592:346748 /
   라이트 4592:346364 "Button" 131×40; 사용자 선택 "공통 헤더 전체 적용").
   높이 40 · 패딩 4/8 · gap 4(내부 [아바타|이름] gap 8) · 반경 10 = --mili-radius-control ·
   채움 다크 Neutral/800 / 라이트 Neutral/200 · 이름 12px SemiBold 1.3 다크 Neutral/50 / 라이트 Neutral/900 ·
   아바타 32px 원 · 셰브론 아래 16px.
   위 기본 .app-chip-user 와 .mili-style-refresh .app-chip-user(테두리·배경 없음 + ::before 세로줄 + 오른쪽 셰브론 숨김)
   보다 이기도록 .app-topbar 를 앞에 붙여 특정도를 올렸다(소스 순서에 기대지 않는다). 세로 구분선은 이제 별도
   요소(.app-topbar__divider)가 그린다 — ::before 는 끈다.
   AI 판단(시안 미명시): hover/focus 는 채움을 그대로 두고 포커스 링만 기본 동작에 맡긴다(종전 hover 의 라임 글자색 제거). */
.app-topbar .app-chip-user.app-chip-user--profile {
    gap: var(--mili-space-1);
    height: 40px;
    margin-left: 0;
    padding: var(--mili-space-1) var(--mili-space-2);
    border: 0;
    border-radius: var(--mili-radius-control);
    background: var(--mili-neutral-800);
    color: var(--mili-neutral-50);
    font-size: var(--mili-caption-size);
    font-weight: var(--mili-font-weight-semibold);
    line-height: 1.3;
}

.mili-light .app-topbar .app-chip-user.app-chip-user--profile {
    background: var(--mili-neutral-200);
    color: var(--mili-neutral-900);
}

.app-topbar .app-chip-user.app-chip-user--profile::before {
    content: none;
}

.app-topbar .app-chip-user.app-chip-user--profile:hover,
.app-topbar .app-chip-user.app-chip-user--profile:focus-visible {
    background: var(--mili-neutral-800);
    color: var(--mili-neutral-50);
}

.mili-light .app-topbar .app-chip-user.app-chip-user--profile:hover,
.mili-light .app-topbar .app-chip-user.app-chip-user--profile:focus-visible {
    background: var(--mili-neutral-200);
    color: var(--mili-neutral-900);
}

.app-chip-user__main {
    display: inline-flex;
    align-items: center;
    gap: var(--mili-space-2);
}

.app-chip-user--profile .app-chip-user__avatar {
    width: 32px;
    height: 32px;
}

.app-chip-user__chevron {
    flex: 0 0 auto;
}

/* 좁은 폭에서는 이름을 접고 아바타만 남긴다(칩이 제목을 밀어내지 않게). */
@media (max-width: 575.98px) {
    .app-chip-user__name,
    .app-chip-user > .bi {
        display: none;
    }

    .app-chip-user {
        padding: 7px;
    }
}

/* 사이드바가 상시 노출되는 폭에서는 드로어 버튼·헤더 로고가 필요 없다.
   ⚠️ 배경·position 은 폭이 아니라 **화면 종류(홈이냐 아니냐)** 로 갈린다 — 참고 앱 실측 결과
   .mili-page-header(비-홈)는 모든 폭에서 sticky + 불투명 배경이고, .mili-home-header(홈)만
   absolute + 그라디언트다. 예전엔 이 갈림을 폭 기준으로 잘못 재현해서 1280px 이상에서
   상단바 배경이 통째로 사라졌었다. 홈 전용 오버라이드는 home.css 의
   `.home-shell > .app-topbar` 가 별도로 맡는다(그쪽은 1280 을 그대로 쓴다 — 위 주석 참고). */
@media (min-width: 1024px) {
    .app-topbar__menu,
    .app-topbar__brand {
        display: none;
    }

    .app-icon-btn--menu {
        display: none;
    }
}

/* 모바일 공통 GNB(Figma 8qAqeXw50F8GKDGkeW0Pua node 2859:104669·104546·104694, 2026-08-28) —
   로고+햄버거+알림벨만 남는다. 테마토글·검색·프로필칩은 그 시안(모바일 프레임)에 대응 항목이
   없어 숨긴다(사용자 확인). 이번 시안은 모바일 폭뿐이라 사이드바가 상시 노출되는 1024px 이상은
   범위 밖 — 위 min-width:1024px 블록과 짝을 맞춰 그 아래 폭에서만 적용한다. */
@media (max-width: 1023.98px) {
    .app-topbar__search,
    .app-topbar__profile-chip {
        display: none;
    }
}

/* 본문 열. 기본이 Standard(1200px)이고, 홈·미션 여정만 --wide, 읽기·폼형은 --narrow 다.

   max-width + padding-inline 이 아니라 width: min() 인 이유:
   좌우 여백을 padding 이 아니라 width 가 만들면, 화면 CSS 가 `padding: 3rem 2rem 4rem` 같은
   shorthand 로 세로 여백을 덮어써도 좌우 규칙이 깨지지 않는다. 실제로 그 shorthand 때문에
   6개 화면에서 모바일 여백이 무효화돼 있었다. */
.app-page {
    --app-page-max: var(--mili-content-standard);
    width: min(calc(100% - var(--mili-page-padding) * 2), var(--app-page-max));
    margin-inline: auto;
    padding: 0px 0 56px;    /* 좌우는 0 — 여백은 위 width 가 만든다 */
    /* .app-frame__body 가 flex column 이라 이 본문이 남는 세로 공간을 흡수해야
       .site-footer 가 짧은 화면에서도 뷰포트 바닥에 붙는다(sticky footer 패턴).
       콘텐츠가 뷰포트보다 길면 그냥 자기 콘텐츠 높이만큼만 차지하고 정상 스크롤된다. */
    flex: 1 1 auto;

    /* 콘텐츠 영역 최소 높이(Figma QA 1699:3983). 시안이 말하는 "콘텐츠 영역" 은
       상단바를 포함한 1080px 이고, 이 클래스는 상단바 바깥이라 그만큼 뺀다.
       flex:1 만으로는 뷰포트가 1080 보다 낮을 때 그보다 짧아지므로 별도로 필요하다. */
    min-height: calc(var(--mili-content-min-height) - var(--mili-topbar-height));
}

.app-page--wide {
    --app-page-max: var(--mili-content-max);
}

.app-page--narrow {
    --app-page-max: var(--mili-content-narrow);
}

/* 사이드바가 드로어로 바뀌는 구간부터는 시안의 데스크톱 치수를 그대로 쓰지 않는다.
   1080px 최소 높이를 폰에 그대로 걸면 내용이 한 줄뿐인 화면도 1080px 스크롤이 된다
   (시안은 1920 데스크톱 한 폭만 그려져 있어 이 폭에 대한 지시가 없다 — 우리 판단이다). */
@media (max-width: 1023px) {
    .app-page {
        min-height: 0;
    }
}

/* 모바일은 세로 여백만 줄인다(좌우는 --mili-page-padding 이 20px 으로 이미 바뀌어 있다). */
@media (max-width: 767px) {
    .app-page {
        padding: 8px 0 40px;
    }
}

/* ---------- 그리드 유틸 ----------
   design-system.md §7 이 정한 12/8/4 컬럼 그리드. 새 화면은 브레이크포인트를 직접
   쓰지 말고 이 두 클래스로 배치한다 — 컬럼 수·거터는 --mili-grid-* 토큰이 구간마다
   자동으로 갈아끼운다(정본은 theme/reference.css §페이지 그리드).

   .app-grid    12/8/4 컬럼 원시 그리드. 자식은 기본 전폭이고, 컬럼을 쪼개는 화면만
                각자 grid-column 을 지정한다(reference.css 의 .mili-page-grid 와 동형).
   .app-cards   카드 목록 전용. --app-cards-cols(데스크톱)·--app-cards-cols-md(태블릿)
                숫자만 화면 CSS 에서 지정하면 되고, 모바일은 항상 1열로 쌓는다(규칙 §7). */
.app-grid {
    display: grid;
    grid-template-columns: repeat(var(--mili-grid-columns), minmax(0, 1fr));
    gap: var(--mili-grid-gutter);
}

.app-grid > * {
    grid-column: 1 / -1;
    min-width: 0;
}

.app-cards {
    display: grid;
    gap: var(--mili-grid-gutter);
    grid-template-columns: repeat(var(--app-cards-cols, 4), minmax(0, 1fr));
}

@media (max-width: 1023px) {
    .app-cards {
        grid-template-columns: repeat(var(--app-cards-cols-md, 2), minmax(0, 1fr));
    }
}

@media (max-width: 767px) {
    .app-cards {
        grid-template-columns: 1fr;
    }
}

/* ---------- 화면 전환 탭 → 표준 밑줄 탭(.tab-line) 으로 흡수 ----------
   2026-08-27 탭 표준화. 여기 있던 .line-tabs / .app-subtabs / .community-tabs 세 이름과
   .tabs-bar 정의는 theme/figma-classes.css 의 .tab-line / .tab-line__link / --active /
   .tabs-bar 로 옮겼다(Figma node 2042:215373 Type 1). 마크업도 전부 .tab-line* 로 교체했으므로
   세 옛 이름은 저장소에 더 남아 있지 않다.

   ⚠️ 개정 시안에서 활성 표시가 바뀌었다 — 글자는 중성 최대대비(다크 흰색·라이트 검정)이고
   브랜드 라임은 밑줄에만 남는다. 예전 정의는 글자까지 라임 + Bold 였다.

   ⚠️ 여전히 유효한 두 경고(figma-classes.css 쪽에도 적어 두지 않은, 마크업 쪽 주의사항):
     · 마이페이지 2 depth(사이드바와 같은 층)를 탭으로 다시 그리지 말 것. 예전 .mypage-tabs 가
       그랬다가 /mypage 에서는 탭이 아예 안 나오고 /mypage/assessment-result 에서는 아무 것도
       켜지지 않는 상태로 갈라졌다.
     · 탭(.tab-line, 다른 화면으로 가는 내비게이션)과 알약 탭(.tab-pill, 같은 화면을 좁히는
       보조 탭)의 역할을 섞지 말 것. */

/* ---------- 난이도 레벨 필터(표준 밑줄 탭 + 정렬 드롭다운 한 줄) ----------
   VOD 목록(css/vod/course-list.css .catalog-level-filter)과 PBL 목록(css/pbl/project-list.css
   .pbl-level-filter)이 각각 화면 전용 CSS 로 중복 구현하고 있었다 — 값은 완전히 같은데
   한쪽만 고치면 다른 쪽이 어긋나는 문제가 있었다(2026-08-15 QA37, 탭 사이 gap 버그가
   한쪽만 고쳐졌던 사례). 재사용 컴포넌트라 여기로 옮겼다. 마크업은 화면 CSS 의 클래스와
   함께 쓴다: <nav class="level-filter catalog-level-filter">(화면별 margin 은 그 화면 CSS 가 준다).

   2026-08-27 탭 표준화 — 항목 자체(.level-filter__pills / .level-filter__pill*)는 표준 밑줄 탭
   .tab-line / .tab-line__link 로 교체했다(figma-classes.css, Figma node 2042:215373 Type 1).
   예전엔 같은 밑줄 탭을 리터럴 색(#535353 · #b9b9b9 · #3e4449)으로 따로 구현하고 있었다.
   여기 남는 건 "탭 줄 + 오른쪽 정렬 드롭다운"을 한 줄로 묶는 레이아웃뿐이다. */
.level-filter {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-4);
}

.level-filter > .tab-line {
    margin-bottom: 0;
    min-width: 0;
}

/* 정렬 드롭다운(Figma node 1659:12803) — 테두리 #2B3036=--mili-line, 8px=--mili-radius-filter,
   패딩 13/21px, 16px/24px. 화살표는 네이티브 select 의 것을 끄고(appearance: none)
   ::after 로 그린다 — 브라우저 기본 화살표는 다크 배경에서 색이 제각각이다.
   오른쪽 패딩은 화살표(18px) + 간격(5px) + 오른쪽 여백(21px) 만큼 비워 둔다. */
.sort-form {
    position: relative;
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}

.sort-form::after {
    content: "";
    position: absolute;
    right: 1.3125rem;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 1.5px solid var(--mili-ink);
    border-bottom: 1.5px solid var(--mili-ink);
    transform: translateY(-0.15rem) rotate(45deg);
    pointer-events: none;
}

.sort-select {
    appearance: none;
    -webkit-appearance: none;
    padding: 0.8125rem 3.0625rem 0.8125rem 1.3125rem;
    border: 1px solid var(--mili-line);
    border-radius: var(--mili-radius-filter);
    background: transparent;
    color: var(--mili-ink);
    font-family: inherit;
    font-size: var(--mili-text-md);
    font-weight: var(--mili-font-weight-regular);
    line-height: var(--mili-line-body);
}

.sort-select:hover,
.sort-select:focus-visible {
    border-color: var(--mili-line-strong);
    outline: 0;
}

/* 네이티브 드롭다운 목록은 OS 가 그린다 — 다크 배경을 물려받지 못해 흰 글자/흰 배경이 되는
   조합이 있어 옵션에만 색을 고정한다(여기만 예외적으로 표면색을 직접 쓴다). */
.sort-select option {
    background: var(--mili-surface);
    color: var(--mili-ink);
}

/* JS 가 살아 있으면 선택 즉시 제출하므로 버튼을 숨긴다(js/mypage/learning.js). */
.sort-form__submit {
    margin-left: var(--mili-space-2);
    padding: 0.8125rem 1.3125rem;
    border: 1px solid var(--mili-line);
    border-radius: var(--mili-radius-filter);
    background: transparent;
    color: var(--mili-ink);
    font-size: var(--mili-text-sm);
}

.sort-form--auto .sort-form__submit {
    display: none;
}

/* ===================================================================
   8-1) 마이페이지 하위 화면 공용 (머리글 · 패널)
   -------------------------------------------------------------------
   mypage/_page-top.jsp 와 짝이다. 6개 화면(내 학습·내 로드맵·관심 목록·작성한 글·
   역량진단 결과·프로필)이 같은 조각을 쓰므로 화면별 CSS 가 아니라 여기 둔다.
   탭바는 이 절에 있었으나 §8 위쪽을 거쳐 표준 탭 .tab-line(theme/figma-classes.css)으로 옮겼다.
   =================================================================== */

/* 2026-08-12 Figma QA 2(node 1638-2738) "공통 Title 중앙정렬" 로 마이페이지 전용 머리글
   .mypage-head(왼쪽 정렬 + "● MY PAGE" eyebrow)를 걷어내고 목록형과 같은 .page-hero 를 쓴다.
   마크업은 mypage/_page-top.jsp → common/_page-hero.jsp.
   제목 옆에 있던 보조 표기(진단일·아이디 등)는 .page-hero__meta 로 옮겼다(§5-2'). */

/*
 * 하위 화면의 기본 판. 개요(.mypage-dash-card)와 같은 수치를 쓴다 —
 * 라운드나 표면 농도가 어긋나면 두 화면이 다른 재질로 보인다.
 */
.mypage-panel {
    padding: 20px;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.06);
    /* QA49(8qAqeXw50F8GKDGkeW0Pua, node 1975:8215, 2026-08-19) — .widget-card 와 같은 이유로
       각짐(0)을 카드 토큰으로 옮긴다(2026-08-12 QA16 때 .widget-card 에 먼저 적용한 것과 동일). */
    border-radius: var(--mili-radius-card);
    background: rgba(var(--mili-fg-rgb), 0.02);
}

.mypage-panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
    margin-bottom: var(--mili-space-4);
}

.mypage-panel__title {
    margin: 0;
    font-size: 0.9375rem;
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

.mypage-panel__action {
    font-size: var(--mili-caption-size);
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-brand);
    text-decoration: none;
}

.mypage-panel__action:hover,
.mypage-panel__action:focus {
    color: var(--mili-brand-strong);
}

/* 2단 배치(프로필·역량진단 결과). 좁으면 1단으로 쌓인다. */
.mypage-2col {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--mili-space-4);
    margin-bottom: var(--mili-space-4);
}

@media (min-width: 900px) {
    .mypage-2col {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .mypage-2col--aside {
        grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
        align-items: start;
    }
}

/* ===== 9) 대시보드 위젯 카드 ===== */

/* height: 100% 를 쓰지 않는다. 그리드·플렉스 컨테이너 안에서는 기본 align-items: stretch 가
   이미 형제 카드와 높이를 맞춰주므로 불필요하고, 컨테이너 밖(.app-page 직속)에 놓이면
   해롭다 — .app-page 는 flex: 1 1 auto 인 플렉스 아이템이라 높이가 확정값이라서
   100% 가 "페이지 전체 높이"로 풀리고, 카드 하나가 아래 섹션과 푸터를 통째로 덮는다
   (2026-08-12 마이페이지 개요의 '연간 학습' 카드에서 발생). */
.widget-card {
    display: flex;
    flex-direction: column;
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    /* QA 16(8qAqeXw50F8GKDGkeW0Pua, node 1659:18674, 2026-08-12) — 마이페이지 개요의
       "학습 스트릭"/"역량 진단"/"연간 학습" 이 이 클래스를 쓴다. 시안은 이런 일반 카드류를
       전부 --mili-radius-card(12px)로 두므로 각짐(0)을 카드 토큰으로 옮긴다. */
    border-radius: var(--mili-radius-card);
    box-shadow: var(--mili-shadow-sm);
}

.widget-card__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--mili-space-3);
    padding: 1rem 1.25rem 0.25rem;
}

.widget-card__title {
    margin: 0;
    font-size: var(--mili-body-b2-size);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

.widget-card__action {
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
    text-decoration: none;
    white-space: nowrap;
}

.widget-card__action:hover {
    color: var(--mili-primary-strong);
}

.widget-card__body {
    flex: 1 1 auto;
    padding: 0.75rem 1.25rem 1.25rem;
}

/* 위젯 안의 빈 상태는 카드 테두리와 중복되지 않도록 여백을 줄인다. */
.widget-card__body > .empty-state {
    padding: 1.75rem 1rem;
    border: none;
    background: transparent;
}

/* ===== 10) KPI 타일 ===== */

.kpi-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--mili-space-4);
    margin-bottom: var(--mili-space-5);
}

/* 모서리는 Figma "[공유용] MILI AI" 2437:47381 실측 5px — 모양 이름 토큰의 button 칸이다.
   아이콘 칩은 실측 4px 이지만 새 값을 만들지 않고 가장 가까운 같은 칸에 배정한다. */
.kpi-tile {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 1rem 1.125rem;
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-button);
    box-shadow: var(--mili-shadow-sm);
}

.kpi-tile__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    flex: 0 0 auto;
    border-radius: var(--mili-radius-button);
    background: var(--mili-primary-bg);
    color: var(--mili-primary-strong);
    font-size: var(--mili-body-b1-size);
}

.kpi-tile__label {
    display: block;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

.kpi-tile__value {
    display: block;
    font-size: 1.375rem;
    font-weight: 800;
    line-height: 1.2;
    color: var(--mili-ink);
}

.kpi-tile__unit {
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-medium);
    color: var(--mili-muted);
    margin-left: 0.125rem;
}

/* ===== 11) 알림 스트립 ===== */

.alert-strip {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    margin-bottom: var(--mili-space-5);
}

.alert-strip__icon {
    flex: 0 0 auto;
    font-size: var(--mili-body-b1-size);
}

.alert-strip__body {
    flex: 1 1 auto;
    min-width: 0;
}

/* ===== 12) 주간 학습 스트립 ===== */

/* 2주치(14칸) 스트립. 요일이 열로 맞아떨어지므로 라벨 줄은 위에 한 번만 두고,
   점은 7열 그리드에 흘려 두 줄(지난주·이번 주)로 접힌다. */
.week-strip {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    text-align: center;
}

/* 트랙을 minmax(0, 1fr) 로 둔다 — 그냥 1fr(=minmax(auto,1fr))이면 각 칸이 점(2.25rem)보다
   좁아지지 못해, 카드가 들어앉은 그리드 열까지 밀어 넓혀 모바일에서 페이지 가로 스크롤이 생겼다. */
.week-strip__labels,
.week-strip__grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 0.375rem;
}

.week-strip__day {
    padding: 0.375rem 0.125rem;
    border-radius: var(--mili-radius-button);
}

@media (max-width: 479.98px) {
    .week-strip__day {
        padding-inline: 0;
    }
}

.week-strip__day--today {
    background: var(--mili-brand-soft);
}

.week-strip__label {
    display: block;
    font-size: var(--mili-caption-size);
    color: var(--mili-muted);
    text-align: center;
}

/* 기본 지름 2.25rem 이되 칸보다 커지지 않는다(좁은 화면에서 원이 잘리거나 카드를 밀지 않게).
   높이는 aspect-ratio 로 따라가므로 줄어들어도 원형이 유지된다. */
.week-strip__dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    max-width: 100%;
    height: auto;
    aspect-ratio: 1;
    border-radius: 50%;
    background: rgba(var(--mili-fg-rgb), 0.08);
    font-size: 0.9375rem;
    color: var(--mili-muted);
}

.week-strip__dot--active {
    background: var(--mili-stat-streak);
    color: var(--mili-neutral-50);
}

.week-summary {
    display: flex;
    flex-wrap: wrap;
    gap: var(--mili-space-5);
    margin-top: var(--mili-space-4);
    padding-top: 0.875rem;
    border-top: 1px solid var(--mili-border);
    font-size: var(--mili-font-size-t2);
    color: var(--mili-muted);
}

.week-summary__value {
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

/* ===== 13) 연간 잔디 히트맵 ===== */

.heatmap {
    --heatmap-cell: 0.75rem;
    --heatmap-gap: 3px;
    /* 잔디·라벨·세로선이 공유하는 열 트랙. 세을 열을 픽셀로 계산해 배치하면
       칸 테두리 두께·반올림 때문에 오른쪽으로 갈수록 어긋나므로, 같은 그리드 트랙을
       쓰게 해 브라우저가 정렬하도록 맡긴다.
       2026-08-27 재지적 — 열 폭을 --heatmap-cell 고정값으로만 두면 53주×12px가 카드
       실제 폭(표준 1200px 셸에서 1000px 안팎)보다 한참 좁아 카드 오른쪽에 빈 공간이
       크게 남았다("학습 기록이 좁아 보인다"). minmax(플로어, 1fr)로 바꿔 넓은 화면에서는
       칸이 카드 폭까지 늘어나고, 좁은 화면(모바일)에서는 플로어 밑으로 안 줄어들고
       .heatmap 의 overflow-x:auto 로 가로 스크롤되게 한다(칸이 너무 작아져 안 보이는
       것보다 스크롤이 낫다). */
    --heatmap-track: repeat(var(--heatmap-weeks, 53), minmax(var(--heatmap-cell), 1fr));
    overflow-x: auto;
    padding-bottom: var(--mili-space-1);
}

/* 분기 라벨 줄(3·6·9·12월). 12개를 다 달면 글자가 붙어 어수선해진다.
   잔디와 똑같은 열 트랙을 써서 라벨이 그 달 첫 주 위에 정확히 서도록 한다. */
.heatmap__months {
    display: grid;
    grid-template-columns: var(--heatmap-track);
    column-gap: var(--heatmap-gap);
    height: 1rem;
    font-size: var(--mili-caption-size);
    color: var(--mili-muted);
}

/* 라벨은 열 하나보다 넓으니 오른쪽으로 번지게 둔다(왜쪽 끝이 기준점). */
.heatmap__month {
    grid-row: 1;
    white-space: nowrap;
    overflow: visible;
}

.heatmap__plot {
    position: relative;
    min-width: max-content;
}

/* "이번 주" 표시 — 잔디 위에 덮는 투명 격자. 같은 트랙을 써야 칸과 어긋나지 않는다.
   ⚠️ 2026-08-27 버그 수정: `width: 100%` 가 없으면 세로선이 왼쪽으로 크게 밀린다.
   position:absolute + left:0(right:auto)면 폭이 shrink-to-fit 이 되고, 그 안에서
   `minmax(--heatmap-cell, 1fr)` 의 1fr 은 늘어날 여지가 없어 하한(12px)으로 접힌다.
   반면 .heatmap__grid 는 정상 흐름이라 카드 폭까지 늘어나 칸이 16~17px 이 된다 —
   결국 두 그리드의 열 피치가 15px vs 19.7px 로 달라져 같은 grid-column 이 다른 위치를
   가리켰다(1440px 실측: 오늘 칸 x=989 인데 세로선 x=827, 약 2개월치 어긋남).
   .heatmap__months 는 정상 흐름 그리드라 이 문제가 없었고, 그래서 월 라벨은 맞고
   세로선만 틀린 상태로 오래 남아 있었다. */
.heatmap__marks {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: grid;
    grid-template-columns: var(--heatmap-track);
    column-gap: var(--heatmap-gap);
    pointer-events: none;
}

/* 왼쪽은 지나온 날, 오른쪽은 남은 날임을 한 눈에 가른다. */
.heatmap__today {
    grid-row: 1;
    justify-self: start;
    width: 2px;
    margin-left: calc(var(--heatmap-gap) / -2 - 1px);
    height: calc(100% + 4px);
    margin-top: -2px;
    border-radius: 1px;
    background: var(--mili-primary);
}

.heatmap__grid {
    display: grid;
    grid-auto-flow: column;
    grid-template-columns: var(--heatmap-track);
    /* 열 폭이 --heatmap-cell 보다 늘어나면 칸 모양(정사각형)을 유지하도록 행도 같은
       플로어로 늘어난다 — 아래 .heatmap__cell 의 aspect-ratio 가 실제 높이를 정한다. */
    grid-template-rows: repeat(7, minmax(var(--heatmap-cell), 1fr));
    gap: var(--heatmap-gap);
}

.heatmap__cell {
    box-sizing: border-box;
    width: 100%;
    aspect-ratio: 1;
    border-radius: 0;
    background: var(--mili-heat-0);
}

/* 집계 구간 안(0단계 포함) 칸에는 옅은 테두리를 둔다. --mili-heat-0(다크 6% 흰색)이
   카드 배경과 거의 구분이 안 가서, 테두리가 없으면 "아직 안 온 달"과 "활동이 0인 날"이
   똑같이 안 보여 잔디 폭이 늘 꽉 차 보이는 문제가 있었다. */
.heatmap__cell--0 { background: var(--mili-heat-0); border: 1px solid var(--mili-border); }
.heatmap__cell--1 { background: var(--mili-heat-1); border: 1px solid var(--mili-border); }
.heatmap__cell--2 { background: var(--mili-heat-2); border: 1px solid var(--mili-border); }
.heatmap__cell--3 { background: var(--mili-heat-3); border: 1px solid var(--mili-border); }
.heatmap__cell--4 { background: var(--mili-heat-4); border: 1px solid var(--mili-border); }

/* 오늘 이후 — 아직 오지 않은 날. 예전에는 완전히 비웠는데, 그러면 잔디가
   "오늘까지"만 있는 것처럼 보여 1년치라는 게 전혀 전달되지 않았다. 이젠 점선
   자리표시로 남겨 격자는 12개월을 꾰 채우되, 활동 0인 날(실선 테두리)과는 갈린다. */
.heatmap__cell--out {
    background: transparent;
    border: 1px dashed var(--mili-border);
    opacity: 0.45;
}

.heatmap__legend {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--mili-space-1);
    margin-top: 0.625rem;
    font-size: var(--mili-caption-size);
    color: var(--mili-muted);
}

.heatmap__legend-item {
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 0;
}

/* ===== 14) 타임라인 / 수료증 / 로드맵 ===== */

.timeline {
    list-style: none;
    margin: 0;
    padding: 0;
}

.timeline__item {
    display: flex;
    align-items: flex-start;
    gap: var(--mili-space-3);
    padding: 0 0 0.75rem;
}

.timeline__marker {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.8125rem;
    height: 2.8125rem;
    flex: 0 0 auto;
    border-radius: var(--mili-radius-filter);
    background: rgba(var(--mili-fg-rgb), 0.08);
    font-size: var(--mili-body-b1-size);
    color: var(--mili-muted);
}

.timeline__item:not(:last-child) .timeline__marker::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    width: 1px;
    height: 1.25rem;
    margin-top: 0.375rem;
    background: var(--mili-border);
    transform: translateX(-50%);
}

.timeline__marker--earn {
    background: rgba(var(--mili-brand-rgb), 0.12);
    color: var(--mili-brand);
}

.timeline__marker--spend {
    background: rgba(255, 137, 4, 0.12);
    color: var(--mili-stat-streak);
}

.timeline__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    padding-top: 0.125rem;
}

.timeline__summary {
    display: block;
    font-size: 0.9375rem;
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.timeline__date {
    flex: 0 0 auto;
    padding-top: 0.375rem;
    font-size: var(--mili-caption-size);
    color: var(--mili-muted);
    white-space: nowrap;
}

/* 좁은 화면에서는 "마커 + 본문 + 날짜" 세 칸이 한 줄에 못 들어간다.
   날짜를 본문 안쪽 위로 올리지 않고 줄을 바꿔, 본문이 잘리지 않게 한다. */
@media (max-width: 575.98px) {
    .timeline__item {
        flex-wrap: wrap;
    }

    .timeline__body {
        flex: 1 1 60%;
    }

    .timeline__date {
        order: 3;
        width: 100%;
        padding-top: 0.25rem;
        padding-left: calc(2.8125rem + var(--mili-space-3));
    }
}

/* 근거줄("문제 · 반복문으로 구구단 출력하기") — 제목줄 아래 한 줄을 통째로 쓴다.
   inline 이면 금액과 같은 줄에 끼어 둘 다 잘린다. */
.timeline__evidence {
    display: block;
    margin: 2px 0;
    font-size: var(--mili-caption-size);
    color: var(--mili-fg-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cert-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.cert-list__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
    padding: 0.625rem 0;
    border-bottom: 1px solid var(--mili-border);
}

.cert-list__item:last-child {
    border-bottom: none;
}

.cert-list__title {
    font-size: var(--mili-font-size-t3);
    color: var(--mili-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cert-list__date {
    flex: 0 0 auto;
    font-size: var(--mili-font-size-t1);
    color: var(--mili-muted);
}

.roadmap-track {
    padding: 0.625rem 0;
    border-bottom: 1px solid var(--mili-border);
}

.roadmap-track:last-child {
    border-bottom: none;
}

.roadmap-track__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--mili-space-3);
    margin-bottom: var(--mili-space-2);
}

.roadmap-track__title {
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.roadmap-track__count {
    flex: 0 0 auto;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

/* ===== 15) 프로필 카드 / PBL 팀 현황 / 리더보드 ===== */

.profile-card__meta {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: var(--mili-font-size-t3);
}

.profile-card__meta-item {
    display: flex;
    justify-content: space-between;
    gap: var(--mili-space-3);
    padding: 0.3125rem 0;
    color: var(--mili-body);
}

.profile-card__meta-item > span:first-child {
    color: var(--mili-muted);
}

.team-status__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-2);
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--mili-border);
}

.team-status__row:last-child {
    border-bottom: none;
}

.team-status__title {
    font-size: var(--mili-font-size-t3);
    color: var(--mili-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.team-status__badge {
    flex: 0 0 auto;
}

.leaderboard {
    list-style: none;
    margin: 0;
    padding: 0;
}

.leaderboard__row {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.375rem;
    border-radius: 0;
}

.leaderboard__row--mine {
    background: var(--mili-primary-bg);
}

.leaderboard__rank {
    width: 1.5rem;
    flex: 0 0 auto;
    text-align: center;
    font-weight: var(--mili-font-weight-bold);
    font-size: var(--mili-font-size-t3);
    color: var(--mili-muted);
}

.leaderboard__rank--top {
    color: var(--mili-primary-strong);
}

.leaderboard__name {
    width: 6.5rem;
    flex: 0 0 auto;
    font-size: var(--mili-font-size-t3);
    color: var(--mili-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.leaderboard__bar {
    flex: 1 1 auto;
    height: 0.5rem;
    border-radius: var(--mili-radius-pill);
    background: var(--mili-heat-0);
    overflow: hidden;
}

.leaderboard__bar-fill {
    height: 100%;
    border-radius: var(--mili-radius-pill);
    background: var(--mili-primary);
}

.leaderboard__value {
    flex: 0 0 auto;
    font-size: var(--mili-font-size-t2);
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-ink);
}

/* ===== 16) 차트 래퍼 ===== */

/* Chart.js 는 maintainAspectRatio:false 로 쓰므로 래퍼가 높이를 확정해야 한다. */
.chart-box {
    position: relative;
    height: 260px;
}

/* ===== 16-1) 5각형 스파이더 차트 N개 + 범례 =====
   2026-08-13 두 탭(온보딩설문·역량진단) 개편 — 온보딩설문(/onboarding)·역량진단(/assessment)
   결과 화면이 똑같이 "영역 3개 × 세부역량 5개 = 5각형 3개"를 그린다. 값의 뜻만 다르다
   (레벨 1~5 vs 정답률 0~100%) — js/common/multi-radar.js 가 서버 JSON 의 min/max/stepSize/
   valuePrefix/valueSuffix 로 그 차이를 흡수해 같은 스크립트로 그린다. */

.radar-grid__notice {
    margin: 0 0 1rem;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-fg-muted);
}

.radar-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--mili-space-5);
}

@media (min-width: 992px) {
    .radar-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.radar-legend {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
}

.radar-legend__item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--mili-space-2);
    font-size: var(--mili-caption-size);
}

.radar-legend__title {
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-fg-body);
}

.radar-legend__value {
    flex: 0 0 auto;
    font-weight: 800;
    color: var(--mili-brand);
}

.radar-legend__value small {
    margin-left: var(--mili-space-1);
    font-size: 0.6875rem;
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-fg-muted);
}

/* 미측정 축(온보딩 1회차 등)은 값을 흐리게 처리해 실제 측정 결과와 구분한다. */
.radar-legend__item--dim .radar-legend__title,
.radar-legend__item--dim .radar-legend__value {
    opacity: 0.55;
}

/* ===== 16-2) 프로필 설문 응답 =====
   QA49(8qAqeXw50F8GKDGkeW0Pua, node 1975:8462, 2026-08-19) 실측 — 질문+답변 한 쌍을
   .onb-profile__item 하나로 묶어 2열로 배치한다(넓은 화면에서 8문항이 4줄로 줄어든다).
   2026-08-30 온보딩설문(/onboarding) 전용에서 여기로 승격 — 역량진단(/assessment) 모바일 결과
   화면(Figma node 3216:55465)도 같은 컴포넌트를 재사용한다. */

/* 바로 위 섹션(영역별 5각형 3개 등)과 붙어 보이지 않도록 띄운다. */
.onb-profile-section {
    margin-top: 2.5rem;
}

.onb-profile {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0 2.5rem;
    margin: 0;
}

@media (min-width: 768px) {
    .onb-profile {
        grid-template-columns: repeat(2, 1fr);
    }
}

.onb-profile__item {
    display: grid;
    grid-template-columns: 12rem 1fr;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--mili-border);
}

/* 2026-09-11 모바일 QA(node 4078:231214) — 모바일에서도 질문-답변을 한 줄(양끝맞춤)로
   유지한다. grid 12rem 고정폭은 좁은 화면에서 넘칠 수 있어 flex 로 바꾼다(데스크톱
   ≥768px 규칙은 그대로 둔다). */
@media (max-width: 767px) {
    .onb-profile__item {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.75rem;
    }

    .onb-profile__q {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* 칩이 2개 이상이면 가로로 나란히 두지 않고 세로로 쌓는다 — 나란히 두면 두 칩 폭
       합계만큼 질문 영역이 좁아져 한글이 음절 단위로 줄바꿈된다. */
    .onb-profile__a {
        flex: 0 0 auto;
        flex-direction: column;
        align-items: flex-end;
    }

    .onb-profile__chip {
        white-space: nowrap;
    }
}

.onb-profile__q {
    font-size: 0.8125rem;
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-fg-muted);
}

.onb-profile__a {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem;
}

.onb-profile__chip {
    display: inline-flex;
    padding: 0.25rem 0.625rem;
    border-radius: var(--mili-radius-badge);
    background: rgba(var(--mili-fg-rgb), 0.06);
    color: var(--mili-fg-body);
    font-size: 0.8125rem;
}

/* ===== 17) 소속·계급 2단 선택기 ===== */

/*
 * 레거시 miliai(mbrEdit.jsp)의 소속/계급 선택 UI 를 이 디자인 시스템으로 옮긴 것이다.
 * 좌측은 구분 select, 우측은 검색형 드롭다운(소속) 또는 일반 select(계급)다.
 * 부대가 456건이라 통째로 렌더하지 않고 구분을 고른 뒤 AJAX 로 받아 검색한다.
 */

.mili-selector {
    display: flex;
    gap: var(--mili-space-2);
    align-items: flex-start;
    flex-wrap: wrap;
}

/* .form-select 는 width:100% 라 그대로 두면 줄을 독차지해 좌우 배치가 무너진다. */
.mili-selector__category {
    flex: 0 0 auto;
    width: auto;
    min-width: 9rem;
}

/* 검색형 드롭다운 (소속 전용) */
.mili-selector__search {
    position: relative;
    flex: 1 1 16rem;
    min-width: 14rem;
}

.mili-selector__input-wrap {
    display: flex;
    align-items: center;
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    /* 2026-08-27 — 폼 컨트롤 반경 일괄 적용. 옆의 .form-select(계급·소속구분)와 같은 칸을 써야
       한 줄에 나란히 선 두 컨트롤의 모서리가 어긋나 보이지 않는다. */
    border-radius: var(--mili-radius-control);
}

.mili-selector__input-wrap:focus-within {
    border-color: var(--mili-primary);
    box-shadow: 0 0 0 0.2rem var(--mili-primary-bg);
}

.mili-selector__input {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    background: transparent;
    padding: 0.5rem 0.75rem;
    font-size: 0.9375rem;
    color: var(--mili-ink);
}

.mili-selector__input:focus {
    outline: none;
    box-shadow: none;
}

.mili-selector__arrow {
    flex: 0 0 auto;
    border: 0;
    background: transparent;
    color: var(--mili-muted);
    padding: 0.5rem 0.75rem;
    line-height: 1;
    cursor: pointer;
}

.mili-selector__arrow:hover {
    color: var(--mili-ink);
}

.mili-selector__list {
    position: absolute;
    top: calc(100% + 0.25rem);
    left: 0;
    right: 0;
    z-index: 1050;
    max-height: 15rem;
    overflow-y: auto;
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    /* 2026-08-27 — 폼 컨트롤 반경 일괄 적용(위 .mili-selector__input-wrap 과 같은 칸). */
    border-radius: var(--mili-radius-control);
    box-shadow: var(--mili-shadow);
}

.mili-selector__list[hidden] {
    display: none;
}

.mili-selector__items {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0;
}

.mili-selector__item {
    padding: 0.4375rem 0.75rem;
    font-size: 0.9375rem;
    color: var(--mili-ink);
    cursor: pointer;
}

.mili-selector__item:hover,
.mili-selector__item:focus {
    background: var(--mili-primary-bg);
    color: var(--mili-primary);
    outline: none;
}

.mili-selector__empty {
    padding: 0.625rem 0.75rem;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

/* 선택 결과 표시: "육군 > 육군본부" */
.mili-selector__selected {
    margin: 0.5rem 0 0;
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-primary);
}

.mili-selector__selected[hidden] {
    display: none;
}

/* "수료증에 나타나는 정보이므로..." 안내 문구 */
.mili-selector__hint {
    margin: 0.25rem 0 0;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

@media (max-width: 575.98px) {
    .mili-selector__category,
    .mili-selector__search {
        flex: 1 1 100%;
    }
}


/* ===================================================================
   18) 앱 사이드바 — 전역 IA
   -------------------------------------------------------------------
   마크업은 WEB-INF/views/common/_app-sidebar.jsp. 치수·색은 바로 아래
   Figma 실측 주석이 정본이다(예전 mili-home-v3 시안 수치는 2026-08-12 재측정으로 대체됐다).

   현재는 홈(/)에서만 쓰지만 상단 navbar(main-layout.jsp) 를 대체할 후보라
   화면 전용 CSS(css/home.css)가 아니라 재사용 컴포넌트로 여기에 둔다.
   =================================================================== */
/* 시안 실측(Figma 8qAqeXw50F8GKDGkeW0Pua, node 1638:2826 라이트 · 1553:1097 다크, 2026-08-12 재확인):
   전체 칸 240 폭(= --mili-sidebar-width) 안에 좌·상·하 12px 여백을 두고, 그 안쪽 카드가
   20px 라운드 · bg는 --mili-sidebar-bg. 우측만 여백 없이 본문에 바로 붙는다(pr 없음).
   예전 "radius 0" 실측은 구버전(참고 앱 계산값) 기준이었다 — 이번에 Figma 원본으로 재측정해 뒤집혔다.
   .app-sidebar(이 요소)는 그 12px 여백 칸일 뿐이라 배경은 app-bg, 카드 자체는 .app-sidebar__card 가 그린다. */

/* 호버/선택 전용 팔레트.
   ⚠️ 2026-08-12 실사용 페이지 목업(Figma node 1653:4184, /courses 전체 화면 캡처)으로
   재대조 — 격리된 컴포넌트 프레임만 보고 "호버=꽉 찬 배경" 이라 짐작했던 게 틀렸다.
   실제 페이지에서 지금 보고 있는 메뉴(VOD 강의)는 꽉 찬 배경 bg-[#b4ff39] 이지만, 이건
   "호버"가 아니라 "1 depth 현재 페이지" 표식이었다(라운드도 14px 가 아니라
   --mili-radius-button(5px)로 좁아진다). 반대로 호버는 어디에도 격리된 예시가 없어
   .mili-style-refresh 절(§Jalynne 보정, 디자이너가 실제 화면 캡처 위에 쓴 CSS — 정적
   Figma 프레임이 못 보여주는 마우스오버 상태의 유일한 실측)의 원래 값(옅은 브랜드 배경)을
   따른다 — 오늘 그 값을 "뒤집힌 것"으로 오인해 꽉 찬 배경으로 바꿨던 걸 되돌린다.

   ⚠️ 2026-08-12 QA(Figma node 1638:2382 "공통 - GNB")로 호버와 2 depth 활성이 **갈렸다.**
   그 전까지 둘 다 --mili-sidebar-tint-*(#2D4511) 한 벌을 썼는데, QA 는 세 값을 따로 지정한다:
     (2-1) 호버               #1A2210
     (2-2) 1 depth 활성       #B4FF39  (= --mili-primary, 이미 일치)
     (2-3) 2 depth 활성       #2D4511  (= --mili-primary-bg, 이미 일치)
   #1A2210 은 사이드바 배경 #090A0C 위에 --mili-primary(#B4FF39)를 10% 얹은 값과 hex 단위까지
   같다(계산: 9+.1*(180-9)=26=0x1A, 10+.1*(255-10)=34=0x22, 12+.1*(57-12)=16=0x10).
   그래서 --mili-sidebar-hover-bg 를 따로 두고, 라이트 값은 같은 공식을 라이트 토큰에 적용해
   유도했다(#FFFFFF 위 --mili-primary #618F20 10% = #EFF4E9) — QA 에 라이트 프레임이 없어
   유도한 유일한 값이다. 라이트에서도 호버(#EFF4E9)가 2 depth 활성(#E8F8C8)보다 한 단 옅어
   다크와 같은 위계가 유지된다.

   결과적으로 톤은 세 단계다:
     --mili-sidebar-hover-*    호버(모든 깊이) — 가장 옅은 배경
     --mili-sidebar-tint-*     2 depth 활성(예: 공지사항, node 1553:1161) — 중간 배경
     --mili-sidebar-current-*  1 depth 활성(지금 있는 페이지) — 꽉 찬 배경

   ⚠️ tint 는 --mili-primary-bg 계열(불투명 토큰)을 쓴다. 처음엔 Figma 로드맵 예시의 리터럴값
   rgba(159,233,112,.1)을 그대로 옮겼는데, 라이트(흰 배경)에서 10% 알파는 거의 안 보인다는
   사용자 확인(2026-08-12)으로 불투명 토큰으로 바꿨다 — 계산해 보면 흰 배경 위 10% 블렌드
   결과(약 rgb(230,247,217))와 --mili-primary-bg 라이트(#E8F8C8 = rgb(232,248,200))가 거의
   같은 색이라 Figma 의도에서 벗어나지 않으면서 불투명이라 항상 또렷하다. */
:root {
    /* 2026-09-22 Figma 재실측(GNB(MB) 컴포넌트, node 2859:106521 "Type=Level1,State=Hover" ·
       2859:106813 "Type=Level2,State=Hover" 를 get_design_context 로 직접 조회) — 위 2026-08-12
       "호버=옅은 틴트" 판정은 구브랜드(#B4FF39) 시절 페이지 캡처로 추정한 값이었다. 2026-09-18
       리브랜드 이후 컴포넌트는 호버를 "State" 변형으로 명시적으로 이름 붙여 두고 있고, 그 값은
       옅은 틴트가 아니라 Primary/500 꽉 찬 채움 + Neutral/900 글자다(사용자 지적으로 재확인 —
       호버는 선택(Selected, rgba 틴트)보다 오히려 진하다). --mili-sidebar-hover-fg 도 같이 둔다. */
    --mili-sidebar-hover-bg: var(--mili-primary-500);
    --mili-sidebar-hover-fg: var(--mili-neutral-900);
    /* 2026-08-25 Figma GNB 재실측(8qAqeXw50F8GKDGkeW0Pua, node 2224:106423 → "마이페이지" 하위
       "프로필" 선택 아이템 get_design_context 직접 조회) — 2 depth 활성 배경이 범용
       --mili-primary-bg(#2D4511)와 다른 전용 값(Primary/900)을 쓴다. 사이드바 전용 토큰이라
       (다른 화면에서 재사용되지 않음, grep 확인) 리터럴로 바꿔도 영향 범위가 없다.
       2026-08-27 정정 — 같이 두던 --mili-sidebar-tint-border(#91D51D, Primary/600)를 지웠다.
       GNB 스펙 시트의 2 depth Selected 정본(node 1978:119794 = Type=Dark/State=Selected/Level=2)
       에는 **보더가 없다** — 채움 하나뿐이다.
       2026-09-18 재개편 — 실측값이 새 Primary/900 다크와 정확히 일치해 var() 로 참조한다.
       (⚠️ 위 2026-08-12 버그 주석은 "참조 대상이 .mili-light 에서 재선언 안 될 때"의 함정이다 —
       --mili-primary-900 은 primitives.css 가 :root/.mili-light 양쪽에 각각 선언해 두고,
       이 --mili-sidebar-tint-bg 자체도 아래 .mili-light 블록에서 별도로 재선언하므로 같은
       함정이 아니다.) */
    --mili-sidebar-tint-bg: var(--mili-primary-900);
    /* 위 재실측 — 2 depth 활성 글자는 Primary/500(#b4ff39)로 --mili-primary와 정확히 일치, 유지. */
    --mili-sidebar-tint-fg: var(--mili-primary);
    --mili-sidebar-current-bg: var(--mili-primary);
    --mili-sidebar-current-fg: var(--mili-primary-on);
}

.mili-light {
    /* ⚠️ 2026-08-12 버그 수정: --mili-sidebar-tint-* 는 var(--mili-primary-bg) 등을 참조하는데,
       참조 대상 커스텀 프로퍼티는 "쓰이는 요소"가 아니라 "선언된 요소"(위 :root, 곧 <html>)
       기준으로 치환된다. .mili-light 는 <body>에 붙는 클래스라 <html>엔 안 걸려 있어, 위 :root
       선언은 항상 다크 --mili-primary-bg(#2D4511)로 치환되고 그 값이 그대로 상속되어 라이트
       모드에서도 사이드바 호버가 어두운 배경으로 나왔다(사용자 확인). 여기서 다시 선언해
       .mili-light 가 실제로 걸리는 요소(<body> 이하) 기준으로 라이트 토큰이 치환되게 한다. */
    /* 2026-08-13 정정: 아래는 "QA 에 라이트 프레임이 없어 유도한 값(#EFF4E9)"이었으나,
       Figma "GNB button case"(8qAqeXw50F8GKDGkeW0Pua, node 1692:4210, hover 상태 라벨)가
       실제로 존재하는 라이트 프레임이었다 — 흰 사이드바 위 rgba(159,233,112,.1) 실측.
       --mili-brand-rgb 가 오늘 #9FE970(159,233,112)로 갱신됐으므로 그 값을 그대로 참조한다.
       2026-09-22 재확인 — 위 다크와 같은 이유(node 2859:106565 "Type=Level1,State=Hover,
       Mode=Light" 직접 조회)로 옅은 틴트가 아니라 Primary/500 라이트 로컬값(#1a987f) 꽉 찬
       채움 + 흰 글자다. */
    --mili-sidebar-hover-bg: var(--mili-primary-500);
    --mili-sidebar-hover-fg: var(--mili-neutral-50);
    /* 2026-08-27 정정 — 위 2026-08-25 실측이 배경과 보더를 뒤바꿔 읽었다. 같은 노드
       (8qAqeXw50F8GKDGkeW0Pua, node 2224:106556 → "프로필" 선택 아이템)와 GNB 스펙 시트의
       2 depth Selected 정본(node 2022:198335 = Type=Light/State=Selected/Level=2)을 다시 조회하면
       둘 다 `bg-[var(--primary/200,#ddf2c8)]` 채움 **하나뿐이고 보더가 없다**. 즉 Primary/200 은
       보더가 아니라 배경이고, 그때 배경으로 넣었던 Primary/100(#EDF8E1)은 이 아이템 어디에도
       안 쓰인다. 배경을 #DDF2C8 로 되돌리고 --mili-sidebar-tint-border 토큰은 지웠다
       (다른 사용처 없음, grep 확인).
       2026-09-18 재개편 — 실측값이 새 Primary/200 라이트와 정확히 일치해 var() 로 참조한다. */
    --mili-sidebar-tint-bg: var(--mili-primary-200);
    /* Figma GNB 재실측(8qAqeXw50F8GKDGkeW0Pua, node 2042:215300 → GNB/Item 1978:119796,
       2026-08-25 사용자 확인) — 2 depth 활성 글자는 다크는 라임(Primary/500) 그대로지만 라이트는
       Neutral/900(거의 검정)이다. 옅은 배경(#EDF8E1) 위에 라임 글자를 얹으면 대비가 거의 없어 안
       보이던 문제를 고친다. --mili-text(라이트 근사 #1E2227)를 재사용 — 위 L1 Default 글자색과
       같은 근사 관행. */
    --mili-sidebar-tint-fg: var(--mili-ink);
    /* 2026-08-25 재실측(node 2224:106556 → 1 depth 선택 "마이페이지" 아이템 직접 조회) — 1 depth
       라이트 선택 배경/글자는 --mili-brand(라이트 #618F20, 진한 초록)/흰 글자가 아니라 Primary/500
       라이트 로컬값(#afe57e, 옅은 라임)과 거의 검정 글자다. 예전 node 2042:215300 실측에서도 같은
       #afe57e가 나와 로컬 변수 해석 오차가 아니라 의도된 값으로 확정한다(11-gnb.md 질문 2 해소).
       2026-09-18 재개편 — Primary/500 라이트 로컬값이 #afe57e → #1a987f 로 바뀌어 같이 갱신
       (검정 글자 대비 5.84:1, AA 기준 통과). 새 값이 스케일 스텝과 정확히 일치해 var() 로 참조. */
    --mili-sidebar-current-bg: var(--mili-primary-500);
    --mili-sidebar-current-fg: var(--mili-neutral-900);
}

.app-sidebar {
    /* 시안 LNB 는 fixed inset-y-0 라 본문을 아무리 내려도 늘 같은 자리에 있다.
       sticky + 100dvh 로 같은 결과를 만든다(fixed 와 달리 flex 자리를 그대로 차지해
       본문에 margin-left 를 따로 줄 필요가 없다).
       이걸 안 걸면 긴 화면에서 사이드바가 본문과 함께 밀려 올라가, 하단의
       계정 메뉴가 문서 맨 아래로 가 버린다(실제로 그렇게 안 보였다). */
    position: sticky;
    top: 0;
    align-self: flex-start;
    /* dvh 를 모르는 브라우저용 폴백을 먼저 둔다(뒤 선언이 이기므로 순서가 중요하다). */
    height: 100vh;
    height: 100dvh;
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    width: var(--mili-sidebar-width);
    padding: 12px 0 12px 12px;
    /* reference.css 의 .mili-lnb 가 이 배경·box-shadow 를 먼저 걸지만(같은 명시도),
       components.css 가 나중에 로드돼 이 규칙이 이긴다 — 여백 칸은 카드가 아니라 app-bg 다. */
    background: var(--mili-app-bg);
    box-shadow: none;
}

/* 실제로 보이는 카드. 배경·라운드·클립·상단 하이라이트를 전부 여기서 맡는다. */
.app-sidebar__card {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--mili-sidebar-bg);
    border-radius: var(--mili-radius-shell);
    overflow: hidden;
    /* 위쪽 모서리만 아주 옅게 띄우는 하이라이트(실측: inset 0 1px 0 rgba(255,255,255,.04)) — 다크 전용. */
    box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.04);
}

.mili-light .app-sidebar__card {
    box-shadow: none;
}

/* 참고 소스 재확인(PortalApp.tsx, 커밋 58ce9b0): px-6 pb-7 pt-10 = 24px 28px 40px.
   좌측만 2rem(32px) — Figma 실측(node 2224:106420) "로고 시작점 = 네비 아이콘 시작점"
   지시에 맞춘 값이다(nav 패딩 16px + 링크 패딩 16px = 32px). 우측(닫기 버튼)은 그대로 24px. */
.app-sidebar__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 2.5rem 1.5rem 1.75rem 2rem;
}

/* 로고 SVG 는 흰색(#fff) 고정 자산이라 라이트 사이드바(흰 배경)에서는 그냥 두면 안 보인다.
   자산을 currentColor 로 바꾸면 <img src> 로 불러오는 다른 화면(상단바·로그인·푸터)까지
   같이 영향받으므로, 사이드바 로고에만 filter 로 검정 반전을 건다. */
.mili-light .app-sidebar__brand img {
    filter: brightness(0);
}

.app-sidebar__brand {
    display: inline-flex;
    text-decoration: none;
}

/* Figma 실측(node 1638:2830 · 1553:1102): 100 x 36.19px(예전 124x46 은 참고 앱 재확인 기준이었다 —
   Figma 원본 재측정으로 되돌렸다). */
.app-sidebar__brand img {
    display: block;
    width: 100px;
    height: auto;
}

/* 로그인 사용자 프로필 카드(Figma 8qAqeXw50F8GKDGkeW0Pua node 2859:106011·2859:106012,
   2026-08-28) — 헤드(로고)와 메뉴 사이. 시안 실측: 아바타 60px(라임 1px 테두리) + 텍스트열,
   좌우 padding 20px/24px, 상단 80px·하단 28px(첫 화면 진입 시 상태표시줄과의 간격).
   ⚠️ 이번 시안은 모바일 프레임뿐이다 — .app-sidebar 는 1024px 이상에서 상시 노출 사이드바로
   재사용되는 공용 컴포넌트라, 폭 제한 없이 두면 데스크톱에도 그대로 새 나간다(실제로 나갔던
   회귀). 아래 min-width:1024px 규칙으로 데스크톱 폭에서는 숨긴다. */
.app-sidebar__profile {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 24px 24px 0 20px;
}

@media (min-width: 1024px) {
    .app-sidebar__profile {
        display: none;
    }
}

/* 비로그인 상태(사용자 확인 2026-08-28) — 아바타 자리는 그대로 두고 안내 문구로 로그인을 유도한다.
   .app-sidebar__profile 이 <a> 로도 쓰이므로 링크 기본 스타일만 지운다. */
.app-sidebar__profile--guest {
    text-decoration: none;
}

.app-sidebar__profile-guest-text {
    font-size: 16px;
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-ink);
}

/* 검색창(Figma node 2859:109460) — 프로필 카드 바로 아래, 메뉴 목록 위였다. 2026-09-08 QA(node
   3382:63411) — Figma는 "햄버거 메뉴 누르면 내 정보 밑에 검색바 추가"를 요청했지만, 사용자가
   반대로 이 검색바를 완전히 숨기기로 결정했다(GNB 검색 아이콘 → /search 이동으로 충분하다는
   판단). 폭 무관 항상 숨김 — 1024px 이상에서만 숨기던 예전 미디어쿼리를 지운다. */
.app-sidebar__search-wrap {
    display: none;
}

.app-sidebar__profile-avatar {
    flex: 0 0 auto;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    border: 1px solid var(--mili-primary);
    background: var(--mili-neutral-900);
    object-fit: cover;
}

/* 2026-08-28 재실측(node 2859:105999, get_metadata 좌표) — Lv~이름줄 사이만 2px, 이름줄~부대명
   사이는 0px 로 균일하지 않다. gap 을 여기서 0 으로 두고 .app-sidebar__profile-level 에만
   margin-bottom 을 준다. */
.app-sidebar__profile-info {
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 0;
}

/* Lv. 텍스트 — 시안은 본문색 50% 알파(rgba(248,249,250,.5)). 전용 토큰이 없어
   --mili-fg-rgb(= --mili-text = --mili-ink 의 원본)로 같은 블렌드를 재현한다(사이드바 다른 곳의
   --mili-fg-faint 관행과 다르게 가는 이유: faint 는 다크·라이트 각각 고정값이라 이 알파 블렌드와
   정확히 일치하지 않는다).
   ⚠️ <p> 라 Bootstrap 기본 margin-bottom:1rem(16px) 이 걸린다 — margin 을 다시 0 으로 밀고
   시안 실측값(2px)만 아래 여백으로 준다. */
.app-sidebar__profile-level {
    margin: 0 0 2px;
    font-size: 10px;
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.2;
    color: rgba(var(--mili-fg-rgb), 0.5);
}

.app-sidebar__profile-line {
    display: flex;
    align-items: baseline;
    gap: 4px;
    min-width: 0;
}

.app-sidebar__profile-name {
    font-size: 20px;
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.4;
    letter-spacing: -0.4px;
    color: var(--mili-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-sidebar__profile-rank {
    flex: 0 0 auto;
    font-size: 14px;
    font-weight: var(--mili-font-weight-semibold);
    line-height: 1.3;
    color: var(--mili-ink);
}

/* <p> 라 위 .app-sidebar__profile-level 과 같은 이유로 margin 을 0 으로 민다. */
.app-sidebar__profile-unit {
    margin: 0;
    font-size: 12px;
    font-weight: var(--mili-font-weight-semibold);
    line-height: 1.3;
    color: var(--mili-fg-muted);
}

/* 참고 PortalApp.tsx:308 — nav 는 gap-2(8px) / px-4 py-2(8px 16px). */
.app-sidebar__nav {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
    padding: 8px 16px;
}

/*
 * 항목의 **형태와 치수만** 여기서 정한다.
 * 색·테두리·호버(라임 꽉 찬 배경 + 어두운 글자)는 theme/reference.css 의
 * .mili-nav-item 이 맡는다 — 마크업이 두 클래스를 함께 달고 있다.
 * 여기에 상태 색을 다시 쓰면 components.css 가 나중에 로드돼 참고 규칙을 덮어 버린다
 * (실제로 여기 있던 흰색 4% 호버가 참고의 라임 호버를 덮고 있었다 — 지웠다).
 * 예외는 아래 --active 하나뿐이고, 그건 의도한 편차다(reference.css §LNB 주석 참고).
 *
 * 참고 실측: min-h-12(48px) · px-4 py-3(12px 16px) · gap-3.5(14px) · text-[15px] · 아이콘 19px.
 * ⚠️ 비활성 항목에 상시 테두리를 두지 않는다(참고 커밋 bcf66fd "Remove inactive sidebar borders").
 */
.app-sidebar__link {
    position: relative;
    display: flex;
    min-height: 48px;
    align-items: center;
    gap: 14px;
    padding: 12px 16px;
    border: 0;
    /* QA 2-1 "GNB Menu 라운드값 : 0"(Figma 1638:2382) — 예전 14px 는 참고 앱 값이었다.
       상태별로 라운드가 달라지지 않게 여기서 한 번만 0 으로 못박는다. */
    border-radius: var(--mili-radius-none);
    background: transparent;
    color: var(--mili-text);
    /* 2026-09-18 Figma 재실측("LNB 변경 0916" node 4446:276254) — 15px/1.4 → Body/B2 토큰
       (16px/1.6, 사용자 확인). B2 라인하이트를 1.6배로 바꾼 전역 결정과 같은 근거. */
    font-size: var(--mili-body-b2-size);
    line-height: var(--mili-body-b2-line);
    font-weight: var(--mili-font-weight-regular);
    text-decoration: none;
    transition: color 180ms ease, background-color 180ms ease, outline-color 180ms ease;
}

/*
 * 그룹 부모는 <button> 이라 UA 기본 배경이 있다. :where() 로 감싸 **명시도 0** 으로 지운다 —
 * 그냥 .app-sidebar__link--group { background: transparent } 로 쓰면 components.css 가
 * reference.css 보다 나중에 로드되므로 .mili-nav-item-active 의 라임 채움을 덮어 버린다.
 */
:where(button.app-sidebar__link) {
    background: transparent;
}

.app-sidebar__icon {
    flex: 0 0 auto;
    width: 19px;
    height: 19px;
}

/* 2026-09-22 재실측(§18 도입부 --mili-sidebar-hover-bg/fg 참고) — reference.css 의
   .mili-nav-item:hover(꽉 찬 배경 + 어두운 글자)가 방향은 맞았다. 다만 값은 범용
   --mili-primary/--mili-primary-on(다른 화면과 공용, 라이트에서 옅은 Primary-200 계열)이 아니라
   사이드바 전용 --mili-sidebar-hover-bg/fg(Primary-500 꽉 찬 채움)를 쓴다. */
.app-sidebar__link:hover,
.app-sidebar__link:focus-visible {
    background-color: var(--mili-sidebar-hover-bg);
    color: var(--mili-sidebar-hover-fg);
}

/* 활성 항목은 참고와 같이 굵기까지 한 단 올린다(font-semibold). 색은 아래 --active 가 준다. */
.app-sidebar__link--active {
    font-weight: var(--mili-font-weight-semibold);
}

/* 2026-09-18 Figma 재실측 — 1depth 활성이 "꽉 찬" 단색 채움에서 옅은 배경 + 진한 글자로
   바뀌었다(사용자 확인). 다크는 기존 11-gnb.md 가 이미 인용하던 정본 스펙시트 노드
   `2859:106013`(Dark, GnbMb "Level1/Selected")를 get_design_context 로 직접 재실측 —
   배경 rgba(173,252,172,0.2)(Primary/200 계열 저채도 오버레이, 토큰 매핑 없는 리터럴이라
   그대로 옮김) · 글자 Primary/200 #C5FEC4. 라이트는 같은 스펙시트의 짝 노드 `2859:106564`
   실측 — 배경 Primary/50 #e0f2ef · 글자 Primary/800 #0e6b55(앞서 "LNB 변경 0916" 실측과
   정확히 일치, 재확인 완료). 라운드는 없다(2026-08-12 결정 유지). :hover / :focus-visible
   까지 함께 잡는 것은 배경이 기본 hover 색으로 되돌아가지 않게 하려는 것. */
.app-sidebar__link--active,
.app-sidebar__link--active:hover,
.app-sidebar__link--active:focus-visible {
    background-color: rgba(173, 252, 172, 0.2);
    color: var(--mili-primary-200);
}

.mili-light .app-sidebar__link--active,
.mili-light .app-sidebar__link--active:hover,
.mili-light .app-sidebar__link--active:focus-visible {
    background-color: var(--mili-primary-50);
    color: var(--mili-primary-800);
}

/* ---------- 2 depth 아코디언 ----------
   IA 가 3 depth 라 커뮤니티·역량진단·마이페이지는 하위 항목을 갖는다.
   마크업은 _app-sidebar.jsp — 부모는 <button>(이동하지 않는다), 열림 상태는
   aria-expanded + <ul hidden> 두 속성뿐이고 CSS 도 그 둘만 본다. */
/* 링크와 같은 형태를 물려받고 버튼 UA 기본값(시스템 폰트·가운데 정렬·width:auto)만 지운다
   — 계정 메뉴(.app-sidebar__account-link)에서 이미 쓰는 처리다. */
.app-sidebar__link--group {
    width: 100%;
    border: 0;
    -webkit-appearance: none;
    appearance: none;
    font: inherit;
    font-size: 15px;
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
}

.app-sidebar__caret {
    flex: 0 0 auto;
    margin-left: auto;
    width: 14px;
    height: 14px;
    color: var(--mili-fg-faint);
    transition: transform 160ms ease;
}

/* 2026-09-22 Figma 재실측(node 2859:108083 "Community" 펼침 · 2859:108460 "Mypage" 접힘, GNB(MB))
   — 2026-09-18 판정이 반대였다(사용자 지적으로 재확인). 두 상태 아이콘의 원본 SVG path 를 직접
   받아 대조한 결과 접힘이 기본(아래) 방향, 펼침이 180도 돌아간 위쪽 방향이다
   (접힘 "M3.5 5.25L7 8.75L10.5 5.25" · 펼침 "M3.5 8.75L7 5.25L10.5 8.75"). 마크업의 기본 SVG
   path(M6 9l6 6 6-6)가 이미 아래 방향이라 접힘엔 별도 규칙이 필요 없고, 펼침만 회전시킨다. */
.app-sidebar__link--group[aria-expanded="true"] .app-sidebar__caret {
    transform: rotate(180deg);
}

/* 2026-09-18 Figma 재실측 — 정본 스펙시트 노드(2859:106013 Dark/2859:106564 Light, GnbMb
   "Level1/Selected") 직접 확인 결과 장식은 "펼친 그룹"이 아니라 "현재 활성" 1depth 항목에
   붙는다(리프인 "강의"도 포함) — 네 모서리에 각진 괄호 장식. 높이는 min-h-48px 그대로다
   ("LNB 변경 0916" 데모 프레임의 50px 는 이 정본과 달라 채택하지 않는다). 마크업을 늘리지
   않고 8겹 linear-gradient 배경으로 4개 "⌐" 모양 모서리를 그린다 — currentColor 를 써서
   글자와 같은 색을 자동으로 따라간다.
   ⚠️ background-color(위 --active/--current 규칙)와 background-image(여기)를 분리해서 써야
   한다 — background 단축 속성끼리 겹치면 나중 규칙이 background-image 를 지워 버린다. */
.app-sidebar__link--active,
.app-sidebar__link--group.app-sidebar__link--current {
    position: relative;
    background-image:
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor),
        linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-size: 8px 1px, 1px 8px, 8px 1px, 1px 8px, 8px 1px, 1px 8px, 8px 1px, 1px 8px;
    background-position:
        top left, top left,
        top right, top right,
        bottom left, bottom left,
        bottom right, bottom right;
}

/* 호버하거나 이 그룹이 현재 위치일 때는 항목이 꽉 차므로 캐럿만 흐린 회색으로 남으면
   배경에 묻힌다. currentColor 를 물려받아 아이콘·글자와 같이 어두워지게 한다. */
.app-sidebar__link--group:hover .app-sidebar__caret,
.app-sidebar__link--group:focus-visible .app-sidebar__caret,
.app-sidebar__link--group.app-sidebar__link--current .app-sidebar__caret {
    color: inherit;
}

/* 2026-09-18 Figma 재실측("LNB 변경 0916") — 좌측 레일 선이 새 시안에 없다(사용자 확인,
   들여쓰기만으로 위계 표시). 기존엔 margin-left 12px + padding-left 16px(합계 28px) +
   레일 선이었으나, 이제 들여쓰기 24px 한 번만 준다. */
.app-sidebar__sub {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 2px 0 2px 24px;
    list-style: none;
}

/* ⚠️ 필수. 위 display:flex(작성자 스타일)가 UA 의 [hidden]{display:none} 을 이겨서
   접어도 그대로 보인다. 같은 함정을 드로어 스크림에서 이미 겪었다(이 절 맨 아래). */
.app-sidebar__sub[hidden] {
    display: none;
}

/* 하위 항목에는 아이콘을 넣지 않는다 — 들여쓰기와 레일이 이미 위계를 말한다.
   테두리는 투명으로 미리 자리를 잡아 둔다(활성될 때 글자가 1px 밀리지 않게). */
.app-sidebar__sublink {
    display: flex;
    align-items: center;
    /* 2026-09-18 Figma 재실측(정본 노드 2859:106795/106820, Level2 Selected) — 좌우 패딩
       10px → 16px, 높이 32px 명시(min-height, "LNB 변경 0916" 데모의 36px 대신 정본 채택). */
    min-height: 32px;
    padding: 7px 16px;
    border: 0;
    /* 1 depth 와 같은 QA 2-1 규칙(라운드 0) — 2 depth 만 둥글면 시안의 각진 표식과 어긋난다. */
    border-radius: var(--mili-radius-none);
    outline: 1px solid transparent;
    /* 2026-08-25 Figma GNB 재실측(node 2224:106556/2224:106423 → 2 depth 기본 아이템
       get_design_context 직접 조회) — 기본(비활성) 글자는 다크 #e9e9e9/라이트 #0e1013로,
       두 값 모두 본문 대비색(--mili-ink 다크 #F8F9FA·라이트 #1E2227)에 가깝다. 기존
       --mili-fg-muted(다크·라이트 공통 #868E96 회색)는 다크에서는 너무 어둡고 라이트에서는
       너무 밝아 두 모드 다 시안과 어긋났다 — 범용 회색 토큰이 아니라 본문색을 쓴다. */
    color: var(--mili-ink);
    font-size: var(--mili-body-b3-size);
    line-height: var(--mili-body-b3-line);
    text-decoration: none;
    transition: background-color 160ms ease, color 160ms ease, outline-color 160ms ease;
}

/* 호버 색은 깊이를 가리지 않는다 — 1 depth 와 같은 --mili-sidebar-hover-bg/fg 를 쓴다
   (2026-09-22 재실측, 위 --mili-sidebar-hover-fg 도입 참고). */
.app-sidebar__sublink:hover,
.app-sidebar__sublink:focus-visible {
    background: var(--mili-sidebar-hover-bg);
    color: var(--mili-sidebar-hover-fg);
}

/* 2 depth 전용 — 호버와 같은 옅은 톤(§18 도입부 --mili-sidebar-tint-*, node 1553:1161
   공지사항 실측). 1 depth 의 꽉 찬 --current 와는 다른, 한 단 낮은 세기의 표식이다.
   ⚠️ 2026-08-25 버그 수정 — 글자색이 1 depth 용 --mili-sidebar-current-fg 를 쓰고 있었다.
   --mili-sidebar-tint-fg 토큰이 이미 정확한 값(다크 라임/라이트 근검정)으로 정의돼 있었는데
   이 규칙만 잘못 참조해 다크는 어두운 단색, 라이트는 흰 글자(옅은 배경에 묻혀 거의 안 보임)로
   나오고 있었다 — 11-gnb.md 질문 4·5 의 원인이 바로 이 한 줄이었다. */
/* 2026-08-27 — 여기서 outline-color 로 라임 테두리를 그리고 있었으나 시안(node 1978:119794
   다크 · 2022:198335 라이트)의 2 depth Selected 에는 보더가 없다. 채움만 남긴다.
   기반 규칙의 투명 outline 은 그대로 두되(포커스 링 동작을 이 변경으로 건드리지 않는다),
   활성 상태에서 색을 입히지는 않는다. */
/* 2026-09-18 Figma 재실측(정본 노드 2859:106795 Dark/2859:106820 Light, GnbMb "Level2/Selected"
   직접 확인) — 2depth 활성도 1depth 와 정확히 같은 톤을 쓴다: 다크 rgba(173,252,172,0.2) bg +
   Primary/200 글자, 라이트 Primary/50 bg + Primary/800 글자(종전 --mili-sidebar-tint-* 는 더
   진한 톤이었다). */
.app-sidebar__sublink--active,
.app-sidebar__sublink--active:hover,
.app-sidebar__sublink--active:focus-visible {
    background-color: rgba(173, 252, 172, 0.2);
    color: var(--mili-primary-200);
    font-weight: var(--mili-font-weight-semibold);
}

.mili-light .app-sidebar__sublink--active,
.mili-light .app-sidebar__sublink--active:hover,
.mili-light .app-sidebar__sublink--active:focus-visible {
    background-color: var(--mili-primary-50);
    color: var(--mili-primary-800);
}

/* 그룹 부모도 펼침 여부와 상관없이 항상 칠한다(2026-08-12 사용자 확인 — 톤 자체는 2026-09-18
   L1 활성 재실측으로 tint 로 바뀜, 위 .app-sidebar__link--active 참고). */
.app-sidebar__link--group.app-sidebar__link--current {
    background-color: rgba(173, 252, 172, 0.2);
    color: var(--mili-primary-200);
}

.mili-light .app-sidebar__link--group.app-sidebar__link--current {
    background-color: var(--mili-primary-50);
    color: var(--mili-primary-800);
}

/* ---------- 계정 메뉴 (사이드바 최하단) ----------
   로그인 상태에 따라 프로필·로그아웃 / 로그인·회원가입·찾기 로 갈린다.
   주 메뉴보다 한 단 낮은 위계라 아이콘을 키우지 않고 글자도 작게 둔다. */
/* 바닥으로 미는 건 .app-sidebar__nav 의 flex:1 1 auto 가 이미 한다 — 여기 margin-top:auto 를
   덧붙이면 nav 가 줄어들어야 할 때 서로 밀어내므로 두지 않는다. */
.app-sidebar__account {
    padding: 0.75rem 1rem 1rem;
    border-top: 1px solid var(--mili-line);
}

/* Figma 실측(node 1638:2890) #D6D6D6 — 토큰 --mili-line(라이트 #DEE2E6)과 살짝 갈린다. */
.mili-light .app-sidebar__account {
    border-top-color: #D6D6D6;
}

/* 로그아웃은 CSRF 토큰이 필요해 <a> 가 아니라 <form><button> 이다.
   폼이 레이아웃에 끼어들지 않게 껍데기를 지운다. */
.app-sidebar__logout {
    margin: 0;
}

.app-sidebar__account-link {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
    width: 100%;
    padding: 0.4375rem 0.5rem;
    border: 0;
    border-radius: var(--mili-radius-filter);
    background: transparent;
    color: var(--mili-fg-muted);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 160ms ease, color 160ms ease;
    /* reference.css 의 전역 `button { font: ... !important }` 리셋(14px)이 로그아웃(<button>)에만
       걸려 형제 링크(프로필, <a>)와 폰트 크기가 갈렸었다 — !important 로 12px 을 되돌린다. */
    font-size: var(--mili-caption-size) !important;
    font-weight: var(--mili-font-weight-medium) !important;
}

.app-sidebar__account-link:hover,
.app-sidebar__account-link:focus {
    background: rgba(var(--mili-fg-rgb), 0.06);
    color: var(--mili-ink);
}

/* 좁아져도 줄바꿈 대신 말줄임 — 세로로 글자가 쌓이는 사고를 막는다.
   flex 자식은 기본 min-width:auto 라 내용 폭 밑으로 줄어들지 않으므로 min-width:0 이 필수다. */
.app-sidebar__account-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 비로그인 계정 메뉴 — Figma 실측(node 2224:106420) "로그인 · 회원가입" 한 줄.
   나머지 계정 메뉴(로그아웃)와 달리 아이콘 버튼 형태가 아니라 색만 있는 단순 텍스트 링크다. */
.app-sidebar__account-links {
    display: flex;
    align-items: center;
    gap: var(--mili-space-1);
    margin: 0;
    padding: 0.4375rem 0.5rem;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-text);
}

/* <a>(로그인·회원가입) 뿐 아니라 로그아웃의 <button> 도 같은 클래스를 쓴다 — 버튼은 브라우저
   기본 테두리·배경·패딩·폰트가 남아 있어 border/background/padding/font 를 명시적으로 지워야
   링크와 똑같은 텍스트로 보인다. reference.css 의 전역 `button { font: ... !important }` 리셋도
   같이 덮어써야 폰트 크기가 안 튄다.

   2026-09-14 QA(node 3931:115694, "글자 호버 말고 전체 메뉴 호버") — 글자에만 밑줄이 붙던
   호버를 다른 사이드바 메뉴 항목(.app-sidebar__link 등)과 같은 "항목 전체 배경 호버"로
   바꾼다. inline → inline-flex + 패딩을 주고, 같은 폭만큼 음수 마진으로 상쇄해 줄 흐름은
   그대로 유지한다(로그인·회원가입 두 링크가 가운데 점으로 붙어 있는 한 줄 배치가 안 밀리게). */
.app-sidebar__account-link-inline {
    display: inline-flex;
    align-items: center;
    border: 0;
    border-radius: var(--mili-radius-button);
    padding: var(--mili-space-1) var(--mili-space-2);
    margin: calc(var(--mili-space-1) * -1) calc(var(--mili-space-2) * -1);
    background: transparent;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    font-size: var(--mili-body-b3-size) !important;
    font-weight: var(--mili-font-weight-regular) !important;
    font-family: inherit !important;
    transition: background-color 150ms ease-out;
}

.app-sidebar__account-link-inline:hover,
.app-sidebar__account-link-inline:focus-visible {
    background-color: var(--mili-sidebar-hover-bg);
    /* 2026-09-22 — 위 --mili-sidebar-hover-bg 가 옅은 틴트에서 꽉 찬 채움으로 바뀌면서
       color:inherit(본문색) 로는 대비가 안 나온다. 같은 호버 토큰 짝인 --fg 를 명시한다. */
    color: var(--mili-sidebar-hover-fg);
    text-decoration: none;
}

.app-sidebar__account-sep {
    color: var(--mili-fg-muted);
}

.app-sidebar__scrim {
    display: none;
}

/*
 * 1024–1279px 아이콘 레일 폐지(2026-08-12 사용자 확인) — 이 폭에서도 데스크톱과 같은
 * 240px 전체 사이드바를 그대로 보여준다. theme/reference.css 의 축소 규칙(§166–195)이
 * 이 구간에서 바꾸는 4가지(사이드바 폭 변수·카드 레이아웃·nav 패딩·항목 정렬/패딩·라벨 clip)를
 * 정확히 같은 선택자로 되돌려 무효화한다 — reference.css 는 참고 미러라 손대지 않는다.
 * 같은 명시도라도 components.css 가 나중에 로드되므로 이 규칙이 이긴다.
 */
@media (min-width: 1024px) and (max-width: 1279px) {
    :root {
        --mili-sidebar-width: 240px;
    }

    .mili-lnb > :first-child {
        display: flex;
        padding-inline: 0;
        place-items: normal;
    }

    .mili-lnb nav {
        padding-inline: var(--mili-space-4);
    }

    .mili-lnb .mili-nav-item {
        justify-content: flex-start;
        padding-inline: var(--mili-space-4);
    }

    .mili-lnb .mili-nav-item span {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip: auto;
        white-space: normal;
    }
}

/*
 * 좁은 화면에서는 드로어로 바뀐다(원본 MobileSidebar: 폭 230px, bg-black/60 스크림).
 * 열고 닫기는 js/common/app-shell.js 가 .app-sidebar--open 으로 한다.
 *
 * 실측: Tailwind lg(1024px) 미만이 드로어다(참고 앱은 이 breakpoint 를 재정의하지 않는다).
 * ⚠️ 홈(/)은 예외다 — home.css 의 고정 캔버스(1920=271+1649)가 자기 기준폭 1280 에
 * 맞춰져 있어, 이 값을 1024 로 낮추면 1024~1279px 구간에서 사이드바는 상시 노출로 바뀌는데
 * 홈 캔버스는 아직 모바일 흐름 모드인 과도기가 생긴다. 사용자 확인 후 감수하기로 한 트레이드오프다
 * — 다른 화면(강의·프로젝트·마이페이지 등) 전부를 정확히 맞추는 쪽을 택했다.
 */
@media (max-width: 1023.98px) {
    .app-sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        /* 2026-08-28 정정 — Figma 실측(node 2859:104546 "모바일 공통_LNB") 상 LNB 인스턴스가
           right-0 · width 278px 로, 드로어가 왼쪽이 아니라 **오른쪽**에서 나온다(햄버거도
           우측에 있는 것과 짝이 맞다 — 사용자 지적으로 재확인). 폭도 옛 230px 대신 시안 실측
           278px 로 맞춘다. */
        right: 0;
        z-index: 1045;
        width: 278px;
        /* top/bottom 과 height 가 함께 걸리면 height 가 이긴다. 드로어는 "화면 높이 그대로"가
           정답이라 여기서 다시 못박는다 — 뒤 화면 스크롤은 열려 있는 동안 잠기므로
           (js/common/app-shell.js lockScroll) 주소창 접힘으로 100dvh 가 흔들리지 않는다. */
        height: 100vh;
        height: 100dvh;
        max-height: 100vh;
        max-height: 100dvh;
        /* 데스크톱의 12px 여백 · app-bg 배경은 떠 있는 카드용이다 — 드로어는 화면을 통째로
           덮는 패널이라 다시 edge-to-edge 로 되돌린다(Figma 는 이 폭을 다루지 않는다). */
        padding: 0;
        background: var(--mili-sidebar-bg);
        transform: translateX(100%);
        transition: transform 300ms ease-out;
    }

    .app-sidebar--open {
        transform: translateX(0);
    }

    .app-sidebar__card {
        height: 100%;
        min-height: 0;
        border-radius: 0;
        box-shadow: none;
    }

    /* 메뉴가 길어도 이 영역만 스크롤되고 아래 계정 메뉴(로그아웃)는 늘 화면 맨 아래 남는다.
       overflow 가 걸린 flex 아이템이라 자동 최소크기는 이미 0 이지만, 브라우저별 편차를
       없애려고 명시한다. */
    .app-sidebar__nav {
        min-height: 0;
    }

    /* 홈 인디케이터(노치 기기)에 로그아웃이 가리지 않도록 아래 안전영역만큼 더 띄운다. */
    .app-sidebar__account {
        padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    }

    /* 2026-08-28 정정(사용자 지적) — 로고는 좌상단 상단바(_app-topbar.jsp)에만 있고, 햄버거로
       여는 드로어 안에는 없다(Figma 재확인). 데스크톱(상시 노출 사이드바)의 로고는 그대로 둔다 —
       마크업은 공용이라 이 폭에서만 숨긴다. */
    .app-sidebar__head {
        display: none;
    }

    .app-sidebar__scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1040;
        background: rgba(0, 0, 0, 0.6);
    }

    .app-sidebar__scrim[hidden] {
        display: none;
    }
}

/* 드로어가 열린 동안 뒤 화면 스크롤 잠금. <html> 에 붙이고, body 의 top 은
   js/common/app-shell.js 가 "지금 스크롤 위치의 음수"로 인라인 지정한다
   (그래야 화면이 맨 위로 튀지 않는다). 닫으면 원래 위치로 되돌린다. */
.app-scroll-locked,
.app-scroll-locked body {
    overflow: hidden;
}

.app-scroll-locked body {
    position: fixed;
    left: 0;
    right: 0;
    width: 100%;
}

/* ===================================================================
   18b) 모바일 하단 탭바
   -------------------------------------------------------------------
   마크업은 WEB-INF/views/common/_app-bottom-nav.jsp(_shell-end.jsp 가 include).
   Figma 8qAqeXw50F8GKDGkeW0Pua, node 3382:62110("GNB", 다크 전용) 실측(2026-08-31) —
   사이드바 드로어와 별개로 ≤767px 에서만 보이는 5항목(강의/프로젝트/로드맵/내학습/대시보드)
   고정 탭바다(2026-08-27 사용자 확인으로 셸 공용 신규 구현, 강의·프로젝트 목록 화면 한정이
   아니라 다크 셸을 쓰는 전 화면에 적용). 항목 구성은 2026-08-31 시안 반영으로 "홈" 제거,
   "로드맵" 추가, "마이페이지"→"대시보드" 개명(사용자 확인). 라이트 모드는 시안에 정의가
   없어 기존 로직을 유지한다.
   z-index 1030 — 사이드바 드로어(1045)·스크림(1040)보다 아래라 드로어를 열면 그 밑에 깔린다. */
.app-bottom-nav {
    display: none;
}

@media (max-width: 767px) {
    .app-bottom-nav {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1035;
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 90px;
        /* 실기기 하단 세이프에어리어(홈 인디케이터)에 안 눌리도록 — 시안엔 없는 값이라
           AI 판단으로 추가한다(다른 fixed 하단바인 .detail-cta--sheet 와 같은 처리). */
        padding: 10px 20px calc(20px + max(0px, env(safe-area-inset-bottom)));
        border-radius: var(--mili-radius-shell) var(--mili-radius-shell) 0 0;
        background: var(--mili-neutral-870);
        box-shadow: 0 -7px 3.1px rgba(0, 0, 0, 0.25);
        /* 2026-08-31 QA 이슈 — iOS Chrome/삼성 브라우저(WKWebView) 전용 버그 우회로
           translateZ(0)를 여기 무조건 걸었었으나, Safari 본체에도 그대로 적용되면서
           바닥 오버스크롤(러버밴드) 바운스 중 컴포지팅 레이어가 시각 뷰포트 재배치를
           따라가지 못해 오히려 Safari에서 튕기는 원인이 됐다(2026-09-08 재보고).
           Safari는 이 보정 자체가 필요 없으므로(주석상 "Safari는 정상") CSS에서는
           transform 을 걸지 않는다 — WKWebView 래퍼에서만 app-shell.js 의
           initBottomNavViewportFix() 가 인라인으로 translateZ(0)+translateY(...)를 준다. */
    }

    .mili-light .app-bottom-nav {
        background: var(--mili-neutral-50);
        box-shadow: 0 -7px 5px rgba(0, 0, 0, 0.1);
    }

    /* 탭바에 가려지지 않도록 본문 하단 여백을 넓힌다(탭바 높이 90px 기준). */
    .app-page {
        padding-bottom: calc(40px + 90px);
    }
}

.app-bottom-nav__link {
    display: flex;
    flex: 1 0 0;
    min-width: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 12px 20px 8px;
    /* 2026-09-18 리터럴 14px → 토큰(사용자 확인, --mili-radius-tab 신설). */
    border-radius: var(--mili-radius-tab);
    text-decoration: none;
    color: var(--mili-neutral-500);
}

.app-bottom-nav__link:hover,
.app-bottom-nav__link:focus {
    color: var(--mili-neutral-500);
}

.mili-light .app-bottom-nav__link,
.mili-light .app-bottom-nav__link:hover,
.mili-light .app-bottom-nav__link:focus {
    color: var(--mili-neutral-930);
}

/* 2026-09-18 Figma 재실측(node 2859:109854) — 19px → 18px(사용자 확인). */
.app-bottom-nav__icon {
    width: 18px;
    height: 18px;
}

/* 2026-09-18 Figma 재실측 — 10px → 12px, weight Medium 추가(사용자 확인, 줄바꿈/잘림 여부는
   5-2/5-3 스크린샷으로 검증). */
.app-bottom-nav__label {
    font-size: 12px;
    font-weight: var(--mili-font-weight-medium);
    line-height: 1.6;
    white-space: nowrap;
}

/* 활성(강의) 실측: 다크는 --mili-brand 다크값(#B4FF39)과 정확히 일치해 토큰을 그대로 쓴다.
   라이트는 #2F871D 로 --mili-primary 라이트(#91CC5C) 등 기존 토큰 어디와도 안 맞아, 사이드바의
   --mili-sidebar-tint-* 와 같은 이유로 하단탭바 전용 리터럴 변수를 새로 둔다(2026-08-27 사용자
   확인 — 전용 토큰 추가). */
.app-bottom-nav__link--active,
.app-bottom-nav__link--active:hover,
.app-bottom-nav__link--active:focus {
    color: var(--mili-brand);
}

.mili-light .app-bottom-nav__link--active,
.mili-light .app-bottom-nav__link--active:hover,
.mili-light .app-bottom-nav__link--active:focus {
    /* 2026-09-18 재개편 확인 — #2F871D 는 새 Color 노드의 "Primary_Light_Text/700"과 정확히
       일치한다. 이 램프는 Primary-Dark/Primary_Light(배경·채움용) 재개편 대상이 아니라 텍스트
       전용 별도 램프라 값이 그대로 유지됐다 — 손대지 않는다. */
    --mili-bottomnav-active-fg: #2F871D;
    color: var(--mili-bottomnav-active-fg);
}

/* ===================================================================
   19) 등장 연출
   -------------------------------------------------------------------
   mili-home-v3 시안의 진입 애니메이션을 옮긴 것(타이밍·이징 그대로).
   시안은 클래스명이 home-* 라 홈 전용처럼 보였지만 어느 화면에서나 쓸 수 있어
   .reveal 로 이름을 바꿔 재사용 컴포넌트로 둔다.

   스태거는 JSP 인라인 style 대신 --d1~--d6 모디파이어로 준다
   (색·간격 하드코딩 금지 규칙과 같은 이유로 마크업에 수치를 넣지 않는다).
   =================================================================== */
@keyframes reveal-rise {
    from {
        opacity: 0;
        transform: translateY(22px) scale(0.985);
        filter: blur(6px);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
        filter: blur(0);
    }
}

@keyframes progress-grow {
    0% {
        transform: scaleX(0);
        filter: brightness(0.7);
    }
    70% {
        filter: brightness(1.35);
    }
    100% {
        transform: scaleX(1);
        filter: brightness(1);
    }
}

.reveal {
    opacity: 0;
    animation: reveal-rise 620ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
    will-change: transform, opacity, filter;
}

.reveal--d1 { animation-delay: 80ms; }
.reveal--d2 { animation-delay: 160ms; }
.reveal--d3 { animation-delay: 240ms; }
.reveal--d4 { animation-delay: 320ms; }
.reveal--d5 { animation-delay: 400ms; }
.reveal--d6 { animation-delay: 480ms; }

/* 진행바 채우기. transform 이라 width 를 건드리지 않으므로 리플로우가 없다. */
.progress-grow {
    transform: scaleX(0);
    transform-origin: left center;
    animation: progress-grow 1350ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
    will-change: transform, filter;
}

/*
 * 모션을 줄이도록 설정한 사용자 대응.
 * 미디어쿼리를 여러 곳에 나눠 두면 나중에 한쪽만 고치는 사고가 나므로 여기 한 블록에 모은다.
 */
@media (prefers-reduced-motion: reduce) {
    .app-sidebar {
        transition: none;
    }

    /* 등장 연출은 "결과 상태"만 남기고 끈다. opacity 를 되돌리지 않으면 화면이 비어 버린다. */
    .reveal,
    .progress-grow {
        opacity: 1;
        animation: none;
        transform: none;
        filter: none;
    }
}

/* ===================================================================
   21) 다크 셸 공용 오버라이드 (.mili-theme-dark)
   -------------------------------------------------------------------
   VOD/PBL 목록(mili-ai-4-screens-share 참조 시안)부터 Bootstrap 컴포넌트를
   다크 셸 안에서 그대로 쓴다. 라이트 화면(대부분)은 .mili-theme-dark 가
   body 에 없어 이 블록의 영향을 받지 않는다 — 화면별 재작성 없이 이 스코프
   안에서만 브랜드를 라임으로, 표면을 반투명 화이트로 바꾼다.
   =================================================================== */
/* 2026-08-27 탭 표준화로 삭제: 여기 있던 .mili-theme-dark .filter-chip:hover /
   .filter-chip--active 오버라이드(브랜드 라임 틴트 + !important)와 그 라이트 짝
   .mili-light .filter-chip--active(--mili-primary-bg 채움 + 브랜드 테두리).
   알약 탭의 활성은 이제 브랜드 틴트가 아니라 명암 반전(다크 흰 알약·라이트 검은 알약)이고,
   그 정의는 theme/figma-classes.css 의 .tab-pill__link--active 한 곳뿐이다 — 테마별
   재도색 규칙을 여기 다시 두면 그때처럼 로드 순서 싸움이 재발한다. */

.mili-theme-dark .form-select,
.mili-theme-dark .form-control {
    background-color: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.1);
    color: var(--mili-fg);
}

.mili-theme-dark .form-select {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23ffffff80' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
}

.mili-theme-dark .form-select:focus,
.mili-theme-dark .form-control:focus {
    background-color: rgba(255, 255, 255, 0.06);
    border-color: rgba(var(--mili-brand-rgb), 0.4);
    color: var(--mili-fg);
    box-shadow: 0 0 0 0.25rem rgba(var(--mili-brand-rgb), 0.15);
}

/* 네이티브 <option> 팝업은 브라우저(OS) 렌더링이라 배경색을 지정하지 않으면 항상 흰 배경으로
   뜬다 — 어두운 페이지 위에서 옵션 목록만 하얗게 튀는 문제(2026-08-15, 갤러리 댓글 "관련 문제"
   드롭다운 스크린샷으로 확인). color 뿐 아니라 background-color 도 같이 지정해야
   Chromium/Firefox 가 어두운 목록으로 그린다(Safari 는 OS 팝업이라 못 바꾼다 — 알려진 한계). */
.mili-theme-dark .form-select option {
    background-color: var(--mili-panel-raised);
    color: var(--mili-fg);
}

.mili-theme-dark .alert-success {
    background: rgba(var(--mili-brand-rgb), 0.1);
    border-color: rgba(var(--mili-brand-rgb), 0.25);
    color: var(--mili-fg);
}

.mili-theme-dark .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* Bootstrap pagination — "색면 배지"(시안 실측)로 재구성한다.
   ⚠️ 예전엔 .mili-theme-dark 에만 걸려 있어 라이트 모드는 Bootstrap 기본값(파란 링크색·
   회색 hover/disabled 배경)이 그대로 새어 나왔다 — 색상·간격이 다크와 달라 보인 원인
   (2026-08-12 확인). rgba 값을 --mili-fg-rgb 기반으로 바꿔 .mili-light 에도 같이 건다
   (--mili-fg-rgb 는 라이트 스코프에서 어두운 텍스트색으로 이미 뒤집혀 있다). */
/* 2026-08-26 Figma "공통" 시안의 Pagination 컴포넌트 실측으로 교체. 예전 값은 숫자 칸마다 옅은
   판(rgba(fg,.04))을 깔고 글자를 40% 로 흐리게 죽여 놨는데, 시안은 그 반대다 — 판은 없고
   글자가 또렷하며, 채움은 "지금 페이지"와 "hover" 두 상태에만 들어간다.

   2026-08-27 개정(node 2051:218398, 사용자 확인) — 채움이 라임 브랜드에서 중성 팔레트로
   바뀌었다. 값은 theme/variables.css 의 --mili-pagination-* 한 곳에서만 관리한다.

     항목      40×40, 반경 5px(--mili-radius-button), 사이 간격 12px(--mili-space-3)
     기본      배경 없음 + --mili-pagination-fg 16px Regular
     hover     --mili-pagination-hover-bg 채움 + --mili-pagination-on
     활성      hover 와 같은 배경 + --mili-pagination-active-border 1px + 16px SemiBold

   ⚠️ 실제로 화면에 칠해지는 건 theme/figma-classes.css 의 `.pagination-btn` 이다
   (paging.jsp 가 모든 .page-link 에 그 클래스를 같이 붙이고, figma-classes.css 가 이 파일보다
   먼저 로드되지만 배경·글자색을 이 블록이 건드리지 않기 때문). 여기 --bs-* 는 `.pagination-btn`
   없이 렌더되는 경우의 안전망이므로 <b>같은 토큰으로 맞춰 둔다</b> — 두 계층이 어긋나면
   "어디를 고쳐도 안 바뀐다"가 된다(개정 전 실제로 active 배경이 서로 달랐다). */
.mili-theme-dark .pagination,
.mili-light .pagination {
    --bs-pagination-color: var(--mili-pagination-fg);
    --bs-pagination-bg: transparent;
    --bs-pagination-border-color: transparent;
    --bs-pagination-hover-color: var(--mili-pagination-on);
    --bs-pagination-hover-bg: var(--mili-pagination-hover-bg);
    --bs-pagination-hover-border-color: transparent;
    --bs-pagination-focus-color: var(--mili-pagination-on);
    --bs-pagination-focus-bg: var(--mili-pagination-hover-bg);
    --bs-pagination-focus-box-shadow: 0 0 0 0.2rem rgba(var(--mili-fg-rgb), 0.2);
    --bs-pagination-active-color: var(--mili-pagination-on);
    --bs-pagination-active-bg: var(--mili-pagination-active-bg);
    --bs-pagination-active-border-color: var(--mili-pagination-active-border);
    --bs-pagination-disabled-color: rgba(var(--mili-fg-rgb), 0.2);
    --bs-pagination-disabled-bg: transparent;
    --bs-pagination-disabled-border-color: transparent;
    gap: var(--mili-space-3);
}

.mili-theme-dark .page-link,
.mili-light .page-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 시안 실측 40×40 = --mili-control-height-lg. 높이는 고정하지 않고 min-height 로 둔다 —
       좁은 폭에서 글자가 커지거나 줄바꿈이 나도 칸이 글자를 자르지 않게 하기 위함이다. */
    min-width: var(--mili-control-height-lg);
    min-height: var(--mili-control-height-lg);
    border-radius: var(--mili-radius-button);
    /* 2026-09-02 — 옛 t4 대신 신규 타이포 토큰으로. figma-classes.css 의 .pagination-btn 이 이미
       --mili-body-b2-size 를 쓰고 있었다(값은 1rem 으로 같지만 이름이 갈라져 있었다) — 두 계층이
       같은 이름을 참조해야 값이 갈라질 여지가 없다. */
    font-size: var(--mili-body-b2-size);
    font-weight: var(--mili-font-weight-regular);
    margin: 0;
}

/* 시안은 "지금 페이지"만 SemiBold 다(나머지는 Regular). */
.mili-theme-dark .page-item.active .page-link,
.mili-light .page-item.active .page-link {
    font-weight: var(--mili-font-weight-semibold);
}

/* 2026-08-27 — 좁은 폭 축소를 <b>여기</b>로 옮긴다(기존 문제 수정).
   theme/figma-classes.css 에 같은 목적의 규칙(.pagination-btn { width:32px })이 2026-08-26 부터
   있었지만 <b>한 번도 먹은 적이 없다</b> — .pagination-btn 은 (0,1,0)인데 바로 위
   `.mili-theme-dark .page-link` 가 (0,2,0)이고 이 파일이 figma-classes.css 보다 <b>나중에</b>
   로드돼 min-width:40px 이 계속 이겼다. 그래서 390px 에서 9칸(처음·이전·1~5·다음·끝)이
   9×40 + 8×12 = 456px 를 요구해 350px 컨테이너를 넘고 "다음·끝"이 둘째 줄로 밀려 있었다
   (2026-08-27 실측 재현). 시안(node 2051:218398)에는 좁은 폭 프레임이 없어 축소값은 우리
   판단이다 — 색·반경 등 시안 값은 그대로 두고 크기와 간격만 줄인다.
   --mili-control-height(32px) × 9 + --mili-space-1(4px) × 8 = 320px ≤ 350px 로 한 줄에 들어간다. */
@media (max-width: 767.98px) {
    .mili-theme-dark .pagination,
    .mili-light .pagination {
        gap: var(--mili-space-1);
    }

    .mili-theme-dark .page-link,
    .mili-light .page-link {
        min-width: var(--mili-control-height);
        min-height: var(--mili-control-height);
    }
}

/* ---------- 아래는 학습 플레이어(/mypage/learning/{n})가 추가로 쓰는 컴포넌트 ---------- */

/*
 * 체크박스·라디오. Bootstrap 은 테두리를 rgba(0,0,0,.25) 로 박아 두어 검정 판 위에서 사실상
 * 보이지 않는다. 체크 표시 SVG 도 흰색이 박혀 있어 라임 배경 위에서 대비가 나오지 않으므로
 * --bs-form-check-bg-image 를 브랜드 위 글자색(--mili-brand-on)으로 다시 그린다.
 */
.mili-theme-dark .form-check-input {
    background-color: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.25);
}

.mili-theme-dark .form-check-input:checked {
    background-color: var(--mili-brand);
    border-color: var(--mili-brand);
}

.mili-theme-dark .form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%2311170d' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}

.mili-theme-dark .form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%2311170d'/%3e%3c/svg%3e");
}

.mili-theme-dark .form-check-input:focus {
    border-color: rgba(var(--mili-brand-rgb), 0.5);
    box-shadow: 0 0 0 0.25rem rgba(var(--mili-brand-rgb), 0.15);
}

/* .text-success(수료 체크) 는 Bootstrap 기본값이 어두워 다크에서 거의 검게 보인다. */
.mili-theme-dark .text-success {
    color: var(--mili-code-success) !important;
}

/*
 * .card 와 .text-muted 는 PBL 프로젝트 상세가 다크 셸로 옮겨오며 처음 쓰인다.
 * 부트스트랩 기본값이 라이트(흰 카드·짙은 회색 글자)라 그대로 두면 어두운 배경 위에
 * 흰 박스가 뜨고 보조 문구는 읽히지 않는다. 표면만 list-group 과 같은 결로 맞춘다 —
 * 화면별로 카드를 새로 만들면 라이트 화면과 마크업이 갈라진다.
 */
.mili-theme-dark .card {
    --bs-card-bg: rgba(255, 255, 255, 0.03);
    --bs-card-color: var(--mili-fg-body);
    --bs-card-border-color: rgba(255, 255, 255, 0.08);
    --bs-card-cap-bg: rgba(255, 255, 255, 0.04);
}

/* 커뮤니티 FAQ(community/faq-list.jsp)가 다크 셸로 옮겨오며 처음 쓰인다.
   Bootstrap 기본값이 흰 판 + 남색 활성이라 검은 캔버스에서 통째로 튄다.
   화살표 아이콘은 색이 data-URI 안에 박혀 있어(#212529 / 파랑) 다시 그려야 한다
   — 위 .form-select 와 같은 형식에서 stroke 색만 바꾼 것이다.
   collapse 동작 자체는 base-layout.jsp 가 싣는 bootstrap.bundle 이 그대로 처리한다. */
.mili-theme-dark .accordion {
    --bs-accordion-color: var(--mili-fg-body);
    --bs-accordion-bg: rgba(255, 255, 255, 0.03);
    --bs-accordion-border-color: rgba(255, 255, 255, 0.08);
    --bs-accordion-btn-color: var(--mili-fg);
    --bs-accordion-btn-bg: transparent;
    --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23ffffff80' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
    /* 2026-09-18 재개편 — 라임(레거시 --mili-lime-400 #aaff19)이 브랜드 민트그린(--mili-primary
       다크 #85f686)으로 수렴했다(variables.css §1 원시 팔레트 주석 참고). */
    --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2385f686' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
    --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--mili-brand-rgb), 0.15);
    --bs-accordion-active-color: var(--mili-brand);
    --bs-accordion-active-bg: rgba(var(--mili-brand-rgb), 0.1);
}

.mili-theme-dark .text-muted {
    color: var(--mili-fg-muted) !important;
}

/* ---------- 아래는 마이페이지 하위 화면이 추가로 쓰는 컴포넌트 ---------- */

/*
 * 진행바. .progress 는 --mili-primary-500(파랑) 을 박아 두어 라임 셸에서 혼자 파랗게 남는다.
 * 트랙도 --bs-secondary-bg(밝은 회색)라 검정 판 위에서 흰 띠로 튄다.
 * (개요의 PBL 미션·로드맵 진행바도 같은 컴포넌트라 함께 라임으로 맞춰진다)
 */
.mili-theme-dark .progress {
    --bs-progress-bg: rgba(255, 255, 255, 0.08);
    --bs-progress-bar-bg: var(--mili-brand);
}

/* 안내/오류 알림. 파스텔 배경을 그대로 두면 검정 판 위에 흰 카드가 얹힌 것처럼 보인다. */
.mili-theme-dark .alert-info,
.mili-theme-dark .alert-warning,
.mili-theme-dark .alert-danger {
    background: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.1);
    color: var(--mili-fg-body);
}

.mili-theme-dark .alert-danger {
    border-color: rgba(229, 72, 77, 0.35);
    color: #ff8b8f;
}

/*
 * 라벨/입력 2열 폼 표(프로필). border-top 2px 가 --mili-ink 라 다크에서 흰 굵은 선이 되어
 * 표 위에 자를 얹은 것처럼 보인다. 다크에서는 경계를 그리는 대신 옅게 얹는다(시안 방식).
 */
.mili-theme-dark .form-table {
    border-top-color: var(--mili-line-strong);
}

.mili-theme-dark .form-table__row {
    border-bottom-color: var(--mili-line);
}

/*
 * 소속·계급 2단 선택기. 표면·글자는 시맨틱 토큰이라 다크를 따라오지만
 * 포커스 링·선택 결과 색만 --mili-primary-*(파랑) 하드코딩이라 여기서 브랜드로 돌린다.
 */
.mili-theme-dark .mili-selector__input-wrap {
    background: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.1);
}

.mili-theme-dark .mili-selector__input-wrap:focus-within {
    border-color: rgba(var(--mili-brand-rgb), 0.4);
    box-shadow: 0 0 0 0.2rem rgba(var(--mili-brand-rgb), 0.15);
}

.mili-theme-dark .mili-selector__list {
    background: var(--mili-panel-raised);
    border-color: var(--mili-line-strong);
}

.mili-theme-dark .mili-selector__item:hover,
.mili-theme-dark .mili-selector__item:focus {
    background: rgba(var(--mili-brand-rgb), 0.12);
    color: var(--mili-brand);
}

.mili-theme-dark .mili-selector__selected {
    color: var(--mili-brand);
}


/* ===================================================================
   21-1) 정렬 드롭다운 (.mili-sort-dropdown*) — VOD/PBL 목록 공용
   -------------------------------------------------------------------
   마크업은 vod/course-list.jsp · pbl/project-list.jsp 가 각각 쓴다(둘 다 Bootstrap
   dropdown 관례 그대로, data-bs-toggle). 예전엔 .catalog-sort-dropdown__*(VOD)와
   .pbl-sort-dropdown__*(PBL)로 화면마다 완전히 같은 CSS 를 복제해 두고 있었다 —
   화면 전용 CSS 미러링 규칙(CLAUDE.md)은 "화면 고유 스타일"에 적용되는 것이지, 이렇게
   두 화면이 값까지 100% 같은 재사용 컴포넌트에는 맞지 않아 여기로 합친다(2026-08-28).

   2026-09-08 사용자 지적 — "최신순" 토글을 공용 버튼 카탈로그(theme/figma-classes.css,
   node 2042:215427)의 Small/Text 버튼(`.btn.small.text`)으로 바꿔봤으나, **PC 는 원래
   모양(테두리 있는 박스형 토글)을 그대로 유지하라는 재지적**으로 되돌린다 — Small/Text
   전환은 모바일 전용 사본(.mili-sort-dropdown-row--mobile .mili-sort-dropdown__toggle,
   화면별 CSS)에만 마크업 클래스(`btn small text`)로 남아 있고, 이 PC 기본 규칙은 아래
   테두리·배경·색을 다시 직접 정의한다(2224:106605 "드롭다운" 컴포넌트 실측값, 2026-08-28
   /2026-09-07 이력 그대로). */
/* 2026-09-07 Figma 실측(node 3462:86329, 기본 상태) — 텍스트는 다크 Neutral/100(#f8f9fa,
   = --mili-fg-rgb 다크 그대로) · 라이트 Neutral/800(#2b2e33, ≈ --mili-fg-rgb 라이트 #1E2227)
   로 둘 다 흐림 없는 진한 글자다. 이전엔 다크 rgba(fg,0.55)·라이트 리터럴 #3E4449 로 흐리게
   처리했었는데 그런 "보조 액션이라 흐리게"는 시안에 없던 임의 해석이었다. */
.mili-sort-dropdown__toggle {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.15);
    border-radius: var(--mili-radius-button);
    background: transparent;
    color: rgb(var(--mili-fg-rgb));
    font-size: var(--mili-caption-size);
    font-weight: var(--mili-font-weight-semibold);
    cursor: pointer;
}

.mili-sort-dropdown__toggle:hover,
.mili-sort-dropdown__toggle:focus,
.mili-sort-dropdown__toggle[aria-expanded="true"] {
    border-color: rgba(var(--mili-brand-rgb), 0.4);
    color: var(--mili-brand);
}

/* reference.css 의 전역 hover 밝기(.mili-app-transition ...:hover { filter: brightness(1.045) })
   가 여기 걸리면 border/text 색이 실측보다 밝게 뜬다 — 이미 색 자체를 바꿔 hover 를 표현하므로
   .app-icon-btn(§"버튼 밝기 무효화")과 같은 이유로 무효화한다. */
.mili-app-transition .mili-sort-dropdown__toggle:hover {
    filter: none !important;
}

.mili-light .mili-sort-dropdown__toggle:hover,
.mili-light .mili-sort-dropdown__toggle:focus,
.mili-light .mili-sort-dropdown__toggle[aria-expanded="true"] {
    border-color: var(--mili-brand);
    color: var(--mili-brand);
}

.mili-sort-dropdown__toggle i {
    font-size: 0.875rem;
    transition: transform var(--mili-motion-fast) ease;
}

/* 시안 "Selected/Item Hover" 상태 — 열렸을 때 화살표가 아래가 아니라 위를 향한다.
   bi-chevron-down 은 좌우 대칭이라 180도 회전이 곧 bi-chevron-up 과 같은 모양이다. */
.mili-sort-dropdown__toggle[aria-expanded="true"] i {
    transform: rotate(180deg);
}

/* Bootstrap 기본 .dropdown-menu/.dropdown-item 은 라이트 전용 배색이라(흰 배경 판),
   다크 셸에서 붕 뜬 흰 팝업으로 보이지 않도록 --mili-* 토큰으로 다시 칠한다.
   테두리는 --mili-border 가 아니라 --mili-search-border 와 정확히 같은 값이라 그걸 재사용한다.
   배경은 2026-09-22 2차 재실측(§ dropdown-item:hover 주석 참고)으로 범용 --mili-bg 가 아니라
   이 컴포넌트 전용 --mili-dropdown-bg 를 쓴다(다크는 --mili-bg 의 Neutral/900 보다 한 단
   밝은 Neutral/850, 라이트는 --mili-bg 와 같은 Neutral/50). */
.mili-sort-dropdown__menu {
    /* Figma 시안은 메뉴를 토글과 별개로 제 콘텐츠에 맞춰 늘어나는 요소로 그린다(items-end
       column). width:100% 로 토글 너비에 강제로 맞추면(2026-08-25 당시엔 폭이 맞았지만)
       토글 padding 을 좁히자(2026-08-28, §21-1 참고) 항목 텍스트가 오른쪽 padding 을 못
       받고 테두리에 붙어 보였다 — width:max-content 로 항목 텍스트만큼 늘어나게 하고,
       min-width:100% 로 토글보다 좁아지지는 않게 바닥을 잡는다. */
    width: max-content;
    min-width: 100%;
    padding: 0.75rem 0.5625rem;
    font-size: 0.8125rem;
    background: var(--mili-dropdown-bg);
    border: 1px solid var(--mili-search-border);
    border-radius: var(--mili-radius-button);
    box-shadow: var(--mili-shadow-lg);
}

.mili-sort-dropdown__menu .dropdown-item {
    padding: 0.25rem 0.625rem;
    border-radius: var(--mili-radius-button);
    color: var(--mili-fg-body);
}

.mili-app-transition .mili-sort-dropdown__menu .dropdown-item:hover {
    filter: none !important;
}

/* 항목은 <li><a class="dropdown-item">…</a></li> 구조라 .dropdown-item 자신이 항상 부모 <li>
   의 유일한(=마지막) 자식이다 — .dropdown-item:not(:last-child) 는 그래서 한 번도 걸리지 않았다.
   li 기준으로 걸어야 실제로 항목 사이 간격이 생긴다. */
.mili-sort-dropdown__menu li:not(:last-child) {
    margin-bottom: 0.375rem;
}

/* 2026-09-22 2차 재실측(node 4839:188289 라이트/4839:188481 다크, get_design_context) —
   Hover 배경은 범용 --mili-surface-hover 가 아니라 이 컴포넌트 전용 --mili-dropdown-hover-bg
   다(theme/variables.css 참고, 다크 Neutral/800 · 라이트 Neutral/200 — 1차 재실측 당시엔
   호버 안 된 기본 항목을 잘못 짚어 다크 Neutral/850·라이트 Neutral/100 으로 기록했었다).
   Hover 글자는 다크 흰색(Neutral/50)·라이트 거의 검정(Neutral/900)으로 --mili-fg(다크
   Neutral/100 #f8f9fa·라이트 #1e2227)와 근접하지만 정확히는 다른 값이라 실측대로 고정한다. */
.mili-sort-dropdown__menu .dropdown-item:hover,
.mili-sort-dropdown__menu .dropdown-item:focus {
    background: var(--mili-dropdown-hover-bg);
    color: var(--mili-neutral-50);
}

.mili-light .mili-sort-dropdown__menu .dropdown-item:hover,
.mili-light .mili-sort-dropdown__menu .dropdown-item:focus {
    color: var(--mili-neutral-900);
}

/* "선택됨" 배경은 --mili-primary-bg(배지·필용, 값이 다르다)가 아니라 이 컴포넌트 전용
   --mili-dropdown-selected-bg 를 쓴다(theme/variables.css 참고, 2026-09-22 재실측으로
   Primary/500 그대로 확정). 글자는 다크·라이트 둘 다 브랜드 그린이 아니라 각각
   Neutral/900(다크, 밝은 초록 배경 위 어두운 글자)·Neutral/50(라이트, 진한 초록 배경 위
   흰 글자)다 — 이전엔 다크만 브랜드 그린(배경과 같은 색이라 사실상 안 읽힘)으로 잘못
   그려져 있었다. */
.mili-sort-dropdown__menu .dropdown-item.active {
    background: var(--mili-dropdown-selected-bg);
    color: var(--mili-neutral-900);
}

.mili-light .mili-sort-dropdown__menu .dropdown-item.active {
    background: var(--mili-dropdown-selected-bg);
    color: var(--mili-neutral-50);
}

/* ===================================================================
   22) "내 로드맵에 담기" 서브패널 (.roadmap-add / .roadmap-panel*)
   -------------------------------------------------------------------
   마크업은 WEB-INF/views/common/_roadmap-add.jsp 이고, 과정 상세(/courses/{no})와
   PBL 상세(/pbl/{offeringNo})가 함께 쓴다. 화면 전용 CSS 가 아니라 재사용 컴포넌트라
   여기에 둔다(로드맵 진행률 트랙 .roadmap-track* 은 위 14) 절에 있다).

   2026-09-02 — Bootstrap dropdown 을 버리고 순수 JS 토글(js/common/roadmap-add.js)로
   바꿨다(실측: Figma 8qAqeXw50F8GKDGkeW0Pua node 3522:40991). 모바일(≤991.98px,
   .detail-cta--sheet)에서는 패널이 시트 전체를 차지하는 전체화면 화면이 되고(위
   5-5-1 절의 .detail-cta--roadmap-open 규칙), 데스크톱(≥992px)에서만 이 절의 팝오버
   규칙으로 트리거 버튼 아래에 뜬다.
   =================================================================== */
.roadmap-add {
    display: contents;
}

.roadmap-panel__header {
    display: flex;
    align-items: center;
    padding-bottom: var(--mili-space-3);
}

.roadmap-panel__back {
    display: inline-flex;
    align-items: center;
    gap: var(--mili-space-1);
    padding: 0;
    border: 0;
    background: none;
    color: var(--mili-neutral-550);
    font-size: var(--mili-body-b2-size);
    font-weight: var(--mili-font-weight-medium);
    cursor: pointer;
}

/* 라이트는 시안이 없다 — 같은 계열 텍스트의 기존 매핑(neutral-550 다크 → neutral-800 라이트)을
   그대로 따른다. */
.mili-light .roadmap-panel__back {
    color: var(--mili-neutral-800);
}

/* 2026-08-25 Figma "드롭다운" 컴포넌트 실측(node 2224:106600) 근거 — 다크 고정 셸
   (mili-dark)이라 Bootstrap 기본 팝업(라이트 전용 흰 배경)에 기대지 않고 --mili-* 토큰을
   명시해야 한다(course-list.css 의 .catalog-sort-dropdown__menu 와 같은 이유). 카드 배경·
   테두리 값은 2026-09-02 재실측(node 3522:40991)으로 갱신 — Neutral/870 배경 +
   colorFillTertiary(rgba(255,255,255,.08)) 테두리, 기존 --mili-bg/--mili-search-border 는
   이 화면에 대한 근거가 없어 컴포넌트 실측값으로 바꿨다. */
.roadmap-panel__card {
    display: flex;
    flex-direction: column;
    /* 모바일 시트(.detail-cta--sheet .roadmap-panel)에서 뒤로가기 헤더를 뺀 나머지를 채운다 —
       패널 쪽 overflow 를 hidden 으로 바꾼 만큼(위 §22 모바일 규칙 주석 참고) 스크롤은 이
       카드 하나에서만 일어나야 한다. min-height:0 이 없으면 flex 축소가 콘텐츠 크기 밑으로
       안 내려가 overflow-y:auto 가 실제로는 못 먹는다. */
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--mili-space-5);
    /* 기본은 시맨틱 표면 — .detail-cta__info-card 와 같은 이유(Neutral/870 원시값은
       테마 무관 항상 어둡다, 2026-09-02 라이트 버그 발견). */
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-button);
    overflow-y: auto;
    /* 목록 끝(맨 위/맨 아래)에 닿아도 스크롤 제스처가 뒤 상세 페이지로 전파되지 않게 한다
       (2026-09-04 사용자 지적 — "스크롤 시 내부가 아니라 밖이 스크롤될 때가 있음"). */
    overscroll-behavior: contain;
}

.mili-theme-dark .roadmap-panel__card {
    background: var(--mili-neutral-870);
    border-color: rgba(255, 255, 255, 0.08);
}

.roadmap-panel__empty {
    padding: 0.5rem 0;
    font-size: var(--mili-body-b3-size);
    color: var(--mili-muted);
}

.roadmap-panel__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
}

.roadmap-panel__row + .roadmap-panel__row {
    margin-top: var(--mili-space-2);
    padding-top: var(--mili-space-2);
    border-top: 1px solid var(--mili-border);
}

.roadmap-panel__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* 시안은 Neutral/400(다크 전용, 거의 흰색) — 대응하는 라이트 실측이 없어 이 카드의 본문
       텍스트 시맨틱 토큰(--mili-ink, 다른 .detail-cta__* 값들과 같은 역할)으로 근사한다. */
    color: var(--mili-ink);
    font-size: var(--mili-body-b3-size);
}

.roadmap-panel__form {
    flex: 0 0 auto;
}

/* 담기(초록, .btn.small.outline 그대로) / 담김(회색) pill — 실측(node 3522:40991):
   담김은 표준 .btn.neutral(둘 다 --mili-neutral-550) 대신 테두리만 한 단계 진한
   --mili-neutral-700 을 쓴다(로드맵 상세와 같은 오버라이드 패턴, 이전엔 화면별 CSS에
   중복 정의돼 있던 것을 컴포넌트 자체로 승격했다 — course-detail.css/project-detail.css
   참고). */
.roadmap-panel__pill {
    flex: 0 0 auto;
    gap: var(--mili-space-1);
}

/* .detail-cta__header-row .btn{width:100%}(위 1048행, 특이도 0,2,0)이 .roadmap-add
   (display:contents)를 뚫고 이 pill 까지 덮어쓴다. 폼으로 감싼 "담기" 버튼은 부모
   (.roadmap-panel__form, flex:0 0 auto)가 폭을 잡아줘 눈에 안 띄지만, "담김" 배지
   (<span>, 래퍼 없이 행의 직계 flex 자식)는 이 규칙 때문에 행 전체 폭(278px)으로 늘어나
   옆 로드맵명을 0px로 밀어낸다(2026-09-22 재현·확인). 같은 특이도(0,2,0)로 맞추고
   소스 순서로 되돌린다 — .roadmap-panel__pill 단독(0,1,0)으로는 못 이긴다. */
.detail-cta__header-row .roadmap-panel__pill {
    width: auto;
}

.roadmap-panel__pill.neutral {
    border-color: var(--mili-neutral-700);
    color: var(--mili-neutral-550);
}

.mili-light .roadmap-panel__pill.neutral {
    border-color: var(--mili-neutral-550);
    color: var(--mili-neutral-800);
}

.roadmap-add__icon {
    flex: 0 0 auto;
}

/* 데스크톱 팝오버 — 위 base 규칙들(.roadmap-panel__header 등)보다 반드시 "뒤"에 와야 한다.
   같은 특이도(0,1,0)인 .roadmap-panel__header{display:flex}가 이 media 블록보다 나중에
   나오면 소스 순서로 다시 이겨버린다(2026-09-02 실제로 이 순서 실수로 데스크톱 팝오버에
   전체화면용 뒤로가기 헤더가 겹쳐 보이는 사고가 있었다 — §9 모바일 오버라이드 규칙과
   같은 종류의 함정). */
@media (min-width: 992px) {
    /* 모바일에서는 레이아웃에 관여하지 않지만(display:contents), 데스크톱 팝오버는
       트리거 기준 절대배치가 필요해 여기서만 자리를 만든다. */
    .roadmap-add {
        display: block;
        position: relative;
    }

    .roadmap-panel {
        position: absolute;
        top: calc(100% + 0.5rem);
        right: 0;
        z-index: 20;
        width: 20rem;
        max-height: 22rem;
    }

    .roadmap-panel[hidden] {
        display: none;
    }

    /* 데스크톱 팝오버에는 전체화면 전용 뒤로가기 헤더가 필요 없다. */
    .roadmap-panel__header {
        display: none;
    }
}

/* ----------------------------------------------------------------------
   헤더 행의 로드맵 담기 트리거 버튼 — "담김 0개"는 .btn.neutral 표준값(테두리·글자 모두
   --mili-neutral-550)이 아니라 테두리만 --mili-neutral-700(실측: node 3522:41074/41048,
   위 pill 과 같은 오버라이드). "N개 담김"은 표준 .btn.outline 그대로(초록, node 3522:40938).
   ---------------------------------------------------------------------- */
.roadmap-add__trigger.neutral {
    border-color: var(--mili-neutral-700);
    color: var(--mili-neutral-550);
}

.mili-light .roadmap-add__trigger.neutral {
    border-color: var(--mili-neutral-550);
    color: var(--mili-neutral-800);
}

/* ===================================================================
   23) 장문 텍스트 에디터 (.mili-editor*)
   -------------------------------------------------------------------
   마크업은 화면 JSP 가 만들고 동작은 js/common/mili-editor.js + Tiptap 벤더 번들이 맡는다.
   현재 쓰는 곳은 PBL 문제 풀이의 장문형 답안·수행 설명이고, 다른 장문 입력란이 생기면
   같은 마크업으로 붙일 수 있게 화면 전용이 아니라 여기에 둔다.

   Tiptap 은 헤드리스라 스타일이 하나도 딸려 오지 않는다. 아래가 그 전부다 —
   .form-control 을 그대로 입히지 않는 이유는 편집면이 <textarea> 가 아니라
   contenteditable 이라 높이·패딩·스크롤을 따로 잡아야 하기 때문이다.
   =================================================================== */

.mili-editor {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    /* 참고의 --mili-radius-control(10px) 은 원본에서도 소비처가 0건인 사문 토큰이다.
       실제 렌더는 각진 형태이므로 --mili-radius-none 을 쓴다. */
    border-radius: var(--mili-radius-none);
}

.mili-editor:focus-within {
    border-color: var(--mili-brand);
}

/* ── 툴바 ── */
.mili-editor__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.125rem;
    flex: 0 0 auto;
    padding: 0.25rem 0.375rem;
    background: var(--mili-panel-sunken);
    border-bottom: 1px solid var(--mili-border);
}

.mili-editor__sep {
    width: 1px;
    height: 1.125rem;
    margin: 0 0.25rem;
    background: var(--mili-border);
}

.mili-editor__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--mili-radius-sm);
    background: transparent;
    color: var(--mili-muted);
    font-size: var(--mili-body-b3-size);
    line-height: 1;
}

.mili-editor__btn:hover {
    color: var(--mili-ink);
    background: var(--mili-surface);
    border-color: var(--mili-border);
}

.mili-editor__btn.is-active {
    color: var(--mili-brand-strong);
    background: var(--mili-brand-soft);
    border-color: var(--mili-brand);
}

/* ── 편집면 ── */
.mili-editor__body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 10rem;
    min-width: 0;
    overflow-y: auto;
}

/*
 * ProseMirror 가 contenteditable 을 얹는 요소. min-height:100% 만으로는 부모(.mili-editor__body)가
 * min-height 만 갖고 height 가 없을 때 퍼센트 계산이 되지 않아(스펙상 "명시적 height" 로 인정되지
 * 않음) 실제 입력면이 글줄 높이만큼만 좁게 잡히는 문제가 있었다(2026-08-13). 부모를 flex column
 * 으로 만들고 flex:1 로 채우면 퍼센트 해석에 기대지 않고 항상 남는 높이를 다 차지한다 —
 * 그래야 시각적으로 커 보이는 영역 어디를 눌러도 실제로 편집면이 활성화된다.
 */
.mili-editor__surface {
    flex: 1 1 auto;
    min-height: 100%;
    padding: 0.75rem 0.875rem;
    color: var(--mili-body);
    line-height: 1.7;
    outline: none;
}

.mili-editor__surface > * {
    margin-top: 0;
    margin-bottom: 0.625rem;
}

.mili-editor__surface > *:last-child {
    margin-bottom: 0;
}

/*
 * 빈 문서의 안내 문구. Tiptap 의 Placeholder 확장이 첫 문단에 data-placeholder 를 심고
 * 실제 글자는 CSS 가 그린다(문서에 텍스트로 들어가면 값에 섞인다).
 */
.mili-editor__surface p.is-editor-empty:first-child::before {
    content: attr(data-placeholder);
    float: left;
    height: 0;
    pointer-events: none;
    color: var(--mili-fg-faint);
}

.mili-editor__surface h1,
.mili-editor__surface h2,
.mili-editor__surface h3 {
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.35;
}

.mili-editor__surface h1 { font-size: var(--mili-heading-h4-size); }
.mili-editor__surface h2 { font-size: 1.0625rem; }
.mili-editor__surface h3 { font-size: 0.9375rem; }

.mili-editor__surface ul,
.mili-editor__surface ol {
    padding-left: 1.375rem;
}

.mili-editor__surface li > p {
    margin: 0;
}

.mili-editor__surface blockquote {
    margin-left: 0;
    padding: 0.125rem 0 0.125rem 0.75rem;
    border-left: 3px solid var(--mili-border);
    color: var(--mili-muted);
}

.mili-editor__surface code {
    padding: 0.0625rem 0.25rem;
    border-radius: var(--mili-radius-sm);
    background: var(--mili-panel-sunken);
    font-size: 0.875em;
}

.mili-editor__surface pre {
    padding: 0.75rem;
    border-radius: var(--mili-radius-sm);
    background: var(--mili-code-bg);
    color: var(--mili-code-text);
    overflow-x: auto;
}

/* pre 안의 code 는 인라인 코드 배경을 물려받으면 이중 배경이 된다. */
.mili-editor__surface pre code {
    padding: 0;
    background: none;
    color: inherit;
    font-size: var(--mili-body-b3-size);
}

.mili-editor__surface hr {
    border: 0;
    border-top: 1px solid var(--mili-border);
}

.mili-editor__surface a {
    color: var(--mili-brand-strong);
}

/* 붙여넣은 이미지(2026-08-13, js/common/mili-editor.js 의 handlePaste). 편집면 폭을 넘지 않게 한다. */
.mili-editor__surface img {
    display: block;
    max-width: 100%;
    height: auto;
    border-radius: var(--mili-radius-sm);
}

/*
 * 원본 <textarea>. 에디터가 붙으면 감추지만 DOM 에는 남는다 — 폼 제출이 계속 이 필드로 간다.
 * display:none 이 아니라 화면에서만 지우는 방식이라야 폼 검증이 "숨은 필드" 로 보고 막지 않는다.
 */
.mili-editor__source.is-editor-managed {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* 초안 보관 안내. 서버 제출로 오해하지 않도록 입력란 바로 아래에 붙인다. */
/* 이 안내문은 <strong> 으로 강조된 조각이 문장 중간에 섞인다("… 아직 <strong>이 브라우저에만</strong>
   임시 보관되며 …"). display:flex 로 두면 아이콘·평문·<strong> 이 <b>각각 독립된 플렉스 아이템</b>이
   되어, 조각마다 따로 줄바꿈하고 조각 사이에는 gap 이 벌어진다 — 폭이 좁아질수록 "이 브라우 / 저에만"
   처럼 낱말이 갈라지고 문장 곳곳에 빈칸이 뚫린 채로 보였다(2026-08-25).

   문장이 한 흐름으로 흐르려면 인라인 서식이어야 하므로 block 으로 되돌린다. flex 가 만들던
   "아이콘 옆에 텍스트 블록이 정렬된" 모양은 내어쓰기로 그대로 재현한다 — padding-left 가 문단
   전체를 아이콘 폭만큼 밀고, 음수 text-indent 가 첫 줄만 도로 당겨 그 자리에 아이콘이 놓인다. */
.mili-editor-note {
    display: block;
    padding-left: 1.375rem;
    text-indent: -1.375rem;
    margin: 0.375rem 0 0;
    font-size: var(--mili-caption-size);
    line-height: 1.5;
    color: var(--mili-muted);
    overflow-wrap: break-word;
}

.mili-editor-note > .bi {
    display: inline-block;
    width: 1rem;
    margin-right: 0.375rem;
    text-indent: 0;
}

/* ===================================================================
   24) 커뮤니티 (공지/Q&A/FAQ/뉴스)
   -------------------------------------------------------------------
   커뮤니티 8개 화면(목록·상세·작성)이 main-layout(라이트 navbar)에서 다크 앱셸로
   옮겨오며 새로 생긴 공용 컴포넌트. 게시판이 4종이라도 화면 얼개는 하나(탭 + 목록/
   아코디언 + 상세 기사 + 답변 스레드)라 재사용 컴포넌트로 여기 둔다.
   =================================================================== */

/* ----- 23-1) 게시판 탭(마크업은 common/_community-tabs.jsp). -----
   2026-08-12 Figma QA 2(node 1638-2738)로 하단 라인형이 되면서 이 절의 사각 채움형 정의를
   걷어냈고, 2026-08-27 탭 표준화로 .community-tabs 라는 이름 자체가 사라졌다.
   이제 표준 밑줄 탭 .tab-line 을 그대로 쓴다(theme/figma-classes.css). */

/*
 * ----- 23-2) 게시글 목록 줄 (.post-list / .post-row*) -----
 * 원래 마이페이지 "작성한 글"(mypage/posts.css) 전용이었지만, 커뮤니티 목록 4종도
 * 같은 "제목 + 게시판 칩 + 날짜" 줄을 쓰게 되어 재사용 컴포넌트로 승격했다.
 * 게시판 유형별 색(--accent)은 그대로 공지=파랑/QNA=브랜드/FAQ=보라/뉴스=노랑이다.
 */

/* 2026-08-15 QA38 재지적 — 카드형(테두리+배경 박스)에서 시안의 구분선형(행 사이 얇은 밑줄만)으로.
   전역 8곳(공지·QNA·뉴스·멘토링, 마이페이지 작성글·수료증, 쇼케이스, 통합검색)이 이 컴포넌트를
   같이 쓴다 — 전부 같은 스타일로 통일하기로 확인함. 행 사이 여백은 이제 border-bottom 이 대신하므로
   .post-list 의 flex gap 은 뺀다(등록증거리 이중이 되어 헐거워진다). */
.post-list {
    list-style: none;
    margin: 0 0 2rem;
    padding: 0;
    display: flex;
    flex-direction: column;
}

.post-row {
    position: relative;
    border-bottom: 1px solid rgba(var(--mili-fg-rgb), 0.2);
    transition: background-color 0.2s ease;
}

.post-row:hover,
.post-row:focus-within {
    /* 2026-08-25 Figma "List" 재확인(node 2058:219171) — hover 배경을 시안 실측값
       (--mili-surface-hover, 다크 Neutral/800 · 라이트 Neutral/300)으로 교체. */
    background: var(--mili-surface-hover);
}

/* 2026-08-26 정정 — 여기 있던 `.mili-light .post-row { background: var(--mili-sidebar-bg) }` 를
   걷어냈다. 근거로 삼았던 노드(2058:219171)와 달리 커뮤니티 라이트 시안(node 2384:86259)은
   기본 행에 채움이 없고(페이지와 같은 흰색) hover 에만 Neutral/300 이 들어간다.
   게다가 이 규칙은 .post-row:hover 와 같은 특정도(0,2,0)인데 파일에서 뒤에 있어 hover 를
   통째로 이겼다 — 라이트에서 행에 마우스를 올리면 강조되기는커녕 #F8F9FA 였던 배경이
   흰색으로 빠져 "배경색이 사라지는" 것처럼 보였다(사용자 지적). 기본 채움을 없애면
   위 .post-row:hover 정본이 두 테마 모두에서 그대로 산다. */

.post-row--notice { --accent: var(--mili-stat-time); }
.post-row--qna { --accent: var(--mili-brand); }
.post-row--faq { --accent: #a78bfa; }
.post-row--news { --accent: var(--mili-stat-badge); }
/* 2026-09-03 사용자 지시 — '기타' 태그(갤러리·콘텐츠 댓글·멘토링 등)도 공지/QNA 처럼 브랜드 색을
   쓴다. 다크는 --mili-primary(#B4FF39)가 이미 Primary/500=Primary/Text 값(위 §auth-card 링크
   주석 근거)이고, 라이트는 전역 --mili-primary(#618F20) 외에 확정 토큰이 없어 그대로 재사용한다. */
.post-row--etc { --accent: var(--mili-primary); }

/* 2026-08-26 Figma 지적 #2 "리스트 여백 수정 필요(커뮤니티 모두 동일)" — 시안 List/Item
   (node 2224:91832 내 인스턴스) 실측 padding 20px 사방. 예전에는 세로 14px + 가로 0 에
   .post-row__title 이 padding-left:20px 리터럴로 왼쪽 여백을 흉내 내고 있었는데, 그러면
   오른쪽(날짜)에는 여백이 없어 hover 배경이 글자에 딱 붙는다. 여백은 줄 전체를 감싸는
   이 링크가 갖는 게 맞다. */
.post-row__link {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    padding: var(--mili-space-5);
    color: inherit;
    text-decoration: none;
}

.post-row__board {
    /* 2026-08-25 Figma "List" Tag(node 2058:219171) — 고정폭 아웃라인 배지에서
       내용폭 알약형으로. 게시판 유형별 색(--accent)은 그대로 유지한다(시안은 브랜드
       그린 단색이지만, 여기선 공지/QNA/FAQ/뉴스를 색으로 가르는 게 의도된 기능이라 보존). */
    flex: 0 0 auto;
    padding: var(--mili-space-1) var(--mili-space-2);
    border: 1px solid;
    border-color: var(--accent);
    border-radius: var(--mili-radius-pill);
    color: var(--accent);
    font-size: 0.625rem;
    font-weight: var(--mili-font-weight-bold);
    text-align: center;
    white-space: nowrap;
    opacity: 0.9;
}

/* 2026-09-03 Figma node 3535:180391 — 마이페이지 작성한 게시글은 "제목" 줄과 "날짜" 줄이
   세로로 쌓인다(.post-row__main 에 날짜가 없다, .post-row__date 가 그 아래 형제로 옴).
   날짜가 없는 줄에서는 자식이 .post-row__main 하나뿐이라 예전과 똑같이 그려진다 — 그래서 이
   컴포넌트를 함께 쓰는 나머지 7곳(공지·QNA·FAQ·멘토링, 마이페이지 수료증, 쇼케이스, 통합검색)은
   마크업을 바꾸지 않아도 된다(날짜를 .post-row__main 안에 그대로 둔다). */
.post-row__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--mili-space-1);
    min-width: 0;
}

/* 2026-09-10 Figma node 4038:313875(데스크톱) 재실측 — 마이페이지 작성한 게시글은 넓은 화면에서
   날짜가 둘째 줄이 아니라 태그+제목과 같은 줄 오른쪽 끝에 온다(390px 모바일 프레임(4038:313876)은
   기존처럼 둘째 줄 유지). .post-row__body 는 위에서 항상 column(세로 쌓기)이 기본이므로, 이 화면만
   ≥576px 에서 row 로 바꿔 .post-row__main(태그+제목)과 .post-row__date 를 한 줄 양끝으로 편다. */
@media (min-width: 576px) {
    .post-row__body--split {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: var(--mili-space-3);
    }
}

.post-row__main {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: var(--mili-space-3);
    min-width: 0;
}

.post-row__title {
    /* 2026-09-04 정정 — display:flex 컨테이너에서는 답글 배지(인라인 자식)와 뒤이은 텍스트가
       섞이면 text-overflow:ellipsis 가 적용되지 않고 그냥 잘려 "…" 없이 끊긴다(글자수가 늘어난
       화면, 예: 마이페이지 작성한 게시글에서 실측). 블록 컨테이너 + 배지를 inline-block 으로
       바꾸면 같은 말줄임 규칙이 혼합 인라인 콘텐츠에도 정상 적용된다. */
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    /* 2026-08-26 Figma 실측(List/Item 의 Text) — Body/B2/Regular 16px/400, Neutral/100.
       예전 값(t3 14px + semibold)은 시안보다 작고 굵었다. 왼쪽 여백은 .post-row__link 의
       padding 으로 옮겼다(위 주석 참고). */
    font-size: var(--mili-font-size-t4);
    font-weight: var(--mili-font-weight-regular);
    color: var(--mili-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.post-row__reply {
    display: inline-block;
    vertical-align: middle;
    margin-right: 0.375rem;
    padding: 0.0625rem 0.375rem;
    border-radius: 0;
    background: rgba(var(--mili-fg-rgb), 0.08);
    color: var(--mili-fg-muted);
    font-size: 0.625rem;
    font-weight: var(--mili-font-weight-bold);
}

/* 2026-08-26 Figma 실측(List/Item 의 Date) — Caption/Regular 12px, Neutral/500 #dcdcdc.
   #dcdcdc 에 정확히 대응하는 토큰이 없어(우리 계단은 #F8F9FA → #ADB5BD → #868E96 → #737373)
   가장 가까운 --mili-fg-body(다크 #ADB5BD / 라이트 #5C636B)로 근사한다. 새 hex 를 박으면
   다크 전용 값이 되어 라이트에서 깨진다. 이전 값 --mili-fg-faint(#737373)는 시안보다 훨씬
   어두워 날짜가 거의 안 읽혔다. */
.post-row__date {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--mili-space-1);
    font-size: var(--mili-font-size-t1);
    color: var(--mili-fg-body);
}

/* 2026-09-10 Figma node 4038:313380(Q&A 목록/있음) — 작성자(user-round 아이콘 + 이름)가
   날짜 앞에 온다. 실측 Neutral/500 #dcdcdc 도 #dcdcdc 처럼 대응 토큰이 없어(위 .post-row__date
   주석과 같은 사정) 같은 근사 규칙으로 --mili-fg-body 를 쓴다. Q&A 목록에서만 쓰이고 나머지
   post-row 재사용 화면(공지·FAQ·뉴스·멘토링 등)에는 이 요소가 없다. */
.post-row__author {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--mili-space-1);
    font-size: var(--mili-font-size-t1);
    color: var(--mili-fg-body);
}

.post-row__chevron {
    flex: 0 0 auto;
    color: var(--mili-fg-faint);
    font-size: var(--mili-caption-size);
    transition: transform 0.2s ease, color 0.2s ease;
}

.post-row:hover .post-row__chevron,
.post-row:focus-within .post-row__chevron {
    color: var(--mili-brand);
    transform: translateX(2px);
}

@media (max-width: 575.98px) {
    /* 2026-08-28 Figma 모바일 프레임 실측(node 2806:35966, 390px) — List/Item 안쪽 여백이
       12px 다. 데스크톱 실측(node 2224:9xxx 등, 위 .post-row__link 정본)은 20px 이라 폭별로
       다르다 — 공용 8곳(공지·QNA·FAQ·멘토링, 마이페이지 작성글·수료증, 쇼케이스, 통합검색)의
       모바일에 함께 적용된다(사용자 확인, 2026-08-28). */
    .post-row__link {
        padding: var(--mili-space-3);
    }

    .post-row__main {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--mili-space-1);
    }

    .post-row__title {
        max-width: 100%;
    }

    /* 마이페이지 작성한 게시글 전용(post-row__main--with-board) — 위 공용 규칙은 .post-row__main
       을 세로로 쌓지만(제목+날짜 화면 7곳), 태그가 이 안에 들어온 화면은 모바일에서도 태그+제목이
       한 줄이어야 한다(Figma 모바일 프레임 동일 구조). */
    .post-row__main--with-board {
        flex-direction: row;
        align-items: center;
    }

    /* 2026-08-15 QA — 날짜·답변 줄이 아이콘 글리프 폭 차이로 세로 정렬이 어긋나 보임.
       아이콘을 고정 폭 박스에 넣어 텍스트 시작 x 를 맞춘다.
       2026-08-26 — 예전에는 여기 padding-left 로 목록 전체를 오른쪽으로 밀었는데, 그건
       .post-row__title 의 padding-left:20px 와 시작 x 를 맞추려던 보정이었다. 이제 여백을
       .post-row__link 가 사방으로 갖게 되어 제목·날짜 모두 같은 x 에서 시작하므로 뺀다. */

    .post-row__date i {
        width: 1rem;
        text-align: center;
        flex: 0 0 auto;
    }

    /* Figma 모바일 프레임(node 4038:313392) 실측 — Q&A 목록 모바일은 작성자를 빼고 날짜만
       보여준다(데스크톱과 다른 점, 임의 축소 아님). */
    .post-row__author {
        display: none;
    }
}

/* ----- 23-3) FAQ 아코디언. QNA·작성한 게시글 목록(.post-list/.post-row, 23-2절)과 같은
   "구분선만 있는 연속 리스트"로 맞춘다(2026-08-15) — 펼치기 동작(collapse)은 그대로 두고
   카드 박스(테두리+배경)만 걷어낸다. */

.faq-accordion__item {
    border-bottom: 1px solid rgba(var(--mili-fg-rgb), 0.2);
}

.faq-accordion__q {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    width: 100%;
    padding: 1.25rem 0.25rem;
    border: 0;
    background: transparent;
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-semibold);
    font-size: 0.875rem;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.2s ease;
}

.faq-accordion__q:hover {
    background: rgba(var(--mili-fg-rgb), 0.04);
}

.faq-accordion__q-icon {
    flex: 0 0 auto;
    color: var(--mili-brand);
    transition: transform 0.2s ease;
}

.faq-accordion__q[aria-expanded="true"] .faq-accordion__q-icon {
    transform: rotate(180deg);
}

.faq-accordion__q-text {
    flex: 1 1 auto;
    min-width: 0;
}

.faq-accordion__a {
    padding: 0 0.25rem 1.25rem 2.125rem;
    color: var(--mili-body);
    font-size: var(--mili-body-b3-size);
    line-height: var(--mili-line-body, 1.6);
    white-space: pre-wrap;
}

/* ----- 23-4) 게시글 상세 기사(공지·뉴스·QNA 본문 공용) ----- */

/* 2026-08-15 Figma QA25(node 1700:6510) 실측 borderRadius 8 — 공지·뉴스·QNA 상세가 공용으로
   쓰는 카드라 각지게(0) 두면 세 화면 모두 시안과 어긋난다. 카드 계열 반경 토큰으로 통일. */
.post-detail {
    padding: 1.5rem;
    margin-bottom: var(--mili-space-6);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface);
}

.post-detail__title {
    margin: 0 0 0.5rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--mili-ink);
}

.post-detail__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem;
    margin: 0 0 1.25rem;
    color: var(--mili-fg-faint);
    font-size: var(--mili-body-b3-size);
}

.post-detail__body {
    color: var(--mili-body);
    font-size: 0.9375rem;
    line-height: var(--mili-line-body, 1.6);
    white-space: pre-wrap;
}

/* 갤러리(동료검토) 제출응답 등 <pre> 블록은 브라우저 기본 line-height(약 1.1~1.2)가 부모의
   상속값을 덮어써 유독 줄간격이 좁아 보인다(GalleryService#appendProblem). 같은 sanitize/같은
   클래스를 쓰는 QNA·멘토링 <pre>에도 자연히 적용된다(PBL학습/06.갤러리.md §9.2). */
.post-detail__body pre {
    line-height: var(--mili-line-body, 1.6);
}

/* 업로드 첨부 이미지를 본문 바로 아래 보여준다(community/qna-detail.jsp, "본문에 바로 표시"
   요구사항). */
.post-detail__images {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
    margin: 0.75rem 0;
}

/* 컨테이너 폭을 넘지 않게 잡는다 — 답변·콘텐츠 댓글 본문 안 이미지(사용자가 붙여넣은 경우 등)도
   같은 크기 규칙을 쓴다. */
.post-detail__images img,
.post-detail__body img,
.qna-reply__body img,
.content-comments__body img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0.5rem 0;
}

.post-detail__actions {
    display: flex;
    align-items: center;
    gap: var(--mili-space-3);
    margin-top: var(--mili-space-6);
    padding-top: var(--mili-space-5);
    border-top: 1px solid var(--mili-border);
}

.post-detail__owner-actions {
    margin-left: auto;
    display: flex;
    gap: var(--mili-space-2);
    align-items: center;
}

/* QNA 질문글 첨부파일 목록(게시판 PRD §4.4). */
.post-detail__attachments {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    font-size: var(--mili-body-b3-size);
}

.post-detail__attachments a {
    color: var(--mili-brand);
    text-decoration: none;
}

.post-detail__attachments a:hover,
.post-detail__attachments a:focus-visible {
    text-decoration: underline;
}

/* QNA 질문글 수정 화면의 기존 첨부 관리 목록(community/qna-edit.jsp). */
.qna-edit__attachments {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}

.qna-edit__attachment {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
}

.qna-edit__attachment-label {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
    font-size: var(--mili-body-b3-size);
    color: var(--mili-body);
}

.qna-edit__attachment-delete-form {
    margin: 0;
    flex-shrink: 0;
}

/* 새 첨부 입력행(추가 버튼으로 늘어난다). */
.qna-edit__attach-input-row {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
    margin-bottom: var(--mili-space-2);
}

.qna-edit__attach-input-row .form-control {
    flex: 1 1 auto;
}

.qna-edit__attach-input-remove {
    flex-shrink: 0;
}

/* ----- 23-5) Q&A 답변 스레드 ----- */

.qna-replies {
    list-style: none;
    margin: 0 0 1.5rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
}

/* 2026-08-15 Figma QA25(node 1700:6510) 재지적 — 답글 카드가 rgba(fg,0.02) 배경·거의 안 보이는
   테두리(0.06)·각진 모서리라 시안의 뚜렷한 카드와 달리 화면에서 경계가 안 보였다. .post-detail
   과 같은 표면·테두리 토큰(둘 다 실체가 있는 색상 토큰)으로 맞추고 반경도 카드 계열로 통일. */
.qna-reply {
    padding: 0.875rem 1rem;
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    background: var(--mili-surface-raised);
}

/* 작성자(좌)/작성시각(우) 좌우 배치(2026-08-15, 화면 스크린샷 검토 반영) — 두 자리 모두 비어
   있을 수 있어(탈퇴 회원) flex 로 두면 한쪽이 없어도 반대쪽이 제자리(끝)에 붙는다. */
.qna-reply__meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--mili-space-2);
    margin: 0 0 0.375rem;
    color: var(--mili-fg-faint);
    font-size: var(--mili-caption-size);
}

.qna-reply__body {
    color: var(--mili-body);
    font-size: var(--mili-body-b3-size);
    line-height: var(--mili-line-body, 1.6);
    white-space: pre-wrap;
}

.qna-reply__toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--mili-space-2);
    margin-top: 0.625rem;
}

/* 본인 답변 수정/삭제(js/common/comment-actions.js). */
.qna-reply__actions {
    display: flex;
    gap: var(--mili-space-2);
}

/* 좋아요/싫어요 버튼 쌍(js/common/post-like.js 가 이 컨테이너 안에서 서로를 찾아 갱신한다).
   아이콘+건수만 표시하고 "좋아요"/"싫어요" 글자는 aria-label 로만 전달한다(2026-08-15). */
.qna-reply__reactions {
    display: flex;
    gap: var(--mili-space-2);
}

.qna-reply__edit-form {
    margin-top: 0.625rem;
}

/* 모바일 댓글 카드 — Figma(8qAqeXw50F8GKDGkeW0Pua, node 2963:70324, 390px) 실측.
   community/qna-detail.jsp 의 시안이지만 .qna-reply 는 showcase/detail.jsp 갤러리 댓글과
   공용이라(23-5절 상단 주석) 이 화면 모바일에도 함께 적용된다 — 두 화면이 원래도 같은
   시각 어휘를 쓰기로 한 관계라(2026-08-15, "갤러리 댓글과 통일") 갈라두지 않는다. PC 는
   그대로 둔다. */
@media (max-width: 767px) {
    .qna-reply {
        padding: var(--mili-space-5);
    }

    .qna-replies {
        gap: var(--mili-space-2);
    }

    .qna-reply__meta {
        margin-bottom: 0;
        /* 시안 실측 작성자·날짜 색 neutral/550 — 원시 팔레트 값(다크·라이트 동일값). */
        color: var(--mili-neutral-550);
    }

    .qna-reply__body {
        margin-top: var(--mili-space-3);
        font-size: var(--mili-body-b2-size);
        color: var(--mili-ink);
        /* 공백 없는 긴 URL 등이 섞인 댓글은 줄바꿈이 안 돼 가로 스크롤을 낸다(기존 문제,
           16px 로 커지면서 더 두드러짐) — 모바일 범위 안에서만 함께 고친다. */
        overflow-wrap: anywhere;
    }

    .qna-reply__toolbar {
        margin-top: var(--mili-space-6);
    }
}

/* ===== 24) 스킬 배지 =====
   mypage/badges.jsp(전용 컬렉션)와 마이페이지 요약 미리보기가 함께 쓴다. 배지 마스터 테이블이
   없어(tb_skill 카탈로그 완료 이력 자체가 배지) 카드 하나 = 스킬제목 하나, 도달한 최고 레벨의
   색을 아이콘에 입힌다. 레벨 5색은 --mili-badge-lv1~5, 둥근 배지 반경은 --mili-radius-badge
   (둘 다 theme/variables.css / tokens.css). 배지 도형 자체는 §24-1 .skill-badge-icon 참고
   (common/_skill-badge-icon.jsp 가 렌더한다 — Figma "배지 디자인" node 1381:149 실측). */

.badge-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.375rem;
    padding: 1.25rem 1rem;
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    /* 2026-09-11 Figma QA(node 2452:48184) 3번 "카드 배경 컬러 변경(#1C1F23: Neutral/850)" —
       get_design_context 실측(node 2452:50560) 그대로. --mili-border(다크 #2B3036)는 이미
       시안 값과 정확히 일치해 손대지 않았다. */
    background: var(--mili-neutral-850);
    text-align: center;
}

.mili-light .badge-card {
    /* 이 QA 캡처는 다크 화면뿐이라 라이트 실측 대상이 없다 — 2026-08-30 모바일 QA 때 같은
       컴포넌트(badge-card)에 이미 확정된 라이트 매핑(neutral-50)을 그대로 재사용한다(AI 판단). */
    background: var(--mili-neutral-50);
}

.badge-card--locked {
    opacity: 0.45;
}

.badge-card__field {
    margin: 0;
    color: var(--mili-muted);
    /* 2026-09-11 Figma QA(node 2452:48184) 3번 "폰트 크기 변경(역량:12)" — 11px→12px. */
    font-size: 0.75rem;
    letter-spacing: 0.02em;
}

.badge-card .skill-badge-icon {
    margin: 0.25rem 0;
}

.badge-card__title {
    margin: 0;
    /* 2026-09-11 Figma QA(node 2452:48184) 3번 "폰트 크기 변경(스킬:14)" — get_design_context
       실측(node 2452:50572) 결과 크기는 이미 --mili-body-b3-size(14px)와 같았고, 굵기가
       Pretendard Regular 였다(코드는 semibold였다) — 크기와 함께 굵기도 실측대로 맞춘다. */
    font-weight: var(--mili-font-weight-regular);
    font-size: var(--mili-body-b3-size);
    color: var(--mili-ink);
}

.badge-card__status {
    margin: 0;
    /* 2026-09-11 Figma QA(node 2452:48184) 3번 "폰트 크기 변경(날짜:12)" — --mili-caption-size가
       이미 12px라 그대로. */
    font-size: var(--mili-caption-size);
    /* QA "날짜 컬러 변경(Primary/500 → Neutral/600)" — get_design_context 실측(node 2452:50573)
       그대로. Neutral 단계는 다크·라이트 동일값이라 아래 라이트 오버라이드가 필요하다. */
    color: var(--mili-neutral-600);
}

.mili-light .badge-card__status:not(.badge-card__status--locked) {
    /* 이 QA 캡처는 다크 화면뿐이라 라이트 실측 대상이 없다 — 2026-08-30 모바일 QA 때 같은
       근사(다크 neutral-550~600 계열 → 라이트 neutral-800)를 재사용한다(AI 판단). */
    color: var(--mili-neutral-800);
}

.badge-card__status--locked {
    color: var(--mili-muted);
}

/* 획득 상태: 도달 레벨만큼 채운 별 5개(Figma node 1386:1667 실측 — 진행률 바 이미지가 아니라 별점이다). */
.badge-card__stars {
    display: flex;
    align-items: center;
    gap: 0.125rem;
    margin-top: 0.375rem;
    /* 2026-09-11 Figma QA(node 2452:48184) 3번 "별 크기 변경(14×14)" — get_design_context
       실측(node 2452:50574, Star 1~5 각 14×14) 그대로. 부트스트랩 아이콘 폰트라 font-size가
       곧 별 한 변의 크기다. */
    font-size: 14px;
}

.badge-card__stars .bi-star-fill { color: var(--mili-star-color, currentColor); }
.badge-card__stars .bi-star { color: var(--mili-muted); }

/* 레벨 1~5 도트 — 미획득 카드 하단에서 몇 레벨까지 땄는지 한눈에 보여준다. */
.badge-level-row {
    display: flex;
    align-items: center;
    gap: var(--mili-space-1);
    margin-top: 0.375rem;
}

.badge-level-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: var(--mili-radius-pill);
    background: var(--mili-heat-0);
}

.badge-level-dot--earned.badge-level-dot--lv1 { background: var(--mili-badge-lv1); }
.badge-level-dot--earned.badge-level-dot--lv2 { background: var(--mili-badge-lv2); }
.badge-level-dot--earned.badge-level-dot--lv3 { background: var(--mili-badge-lv3); }
.badge-level-dot--earned.badge-level-dot--lv4 { background: var(--mili-badge-lv4); }
.badge-level-dot--earned.badge-level-dot--lv5 { background: var(--mili-badge-lv5); }

/* ===== 24-1) 배지 도형(공용) =====
   common/_skill-badge-icon.jsp 가 렌더하는 이미지 에셋 스타일 — 2026-08-25 인라인 SVG 생성에서
   Figma 원본 이미지 에셋(images/badges/*.svg, node 2261:43660 다운로드)으로 교체했다. 후광·외곽선·
   내곽 보조선 3겹은 레벨별 색이 이미지에 이미 구워져 있어(더 이상 currentColor 아님) 레벨마다
   다른 파일(-lv1~5)을 쓰고, 채움(그라디언트)은 반대로 레벨 무관·테마별로만 달라 다크/라이트 두
   이미지를 다 렌더한 뒤 아래 --fill-dark/--fill-light 규칙으로 표시만 전환한다.
   4겹(후광/채움/외곽선/내곽선)의 배치(inset)는 Figma 실측값 그대로다 — 모양마다 다르다. */

.skill-badge-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}

.skill-badge-icon__shape {
    position: relative;
    width: 100%;
    height: 100%;
}

.skill-badge-icon__layer {
    position: absolute;
}

.skill-badge-icon__layer svg,
.skill-badge-icon__layer img {
    display: block;
    width: 100%;
    height: 100%;
}

/* 채움 레이어만 다크/라이트 이미지를 둘 다 렌더해두고 표시만 전환한다 — 테마 토글이
   새로고침 없이 body 에 .mili-light 를 붙였다 뗐다 하는 방식(app-shell.js)이라 JSP 렌더
   시점엔 하나로 확정할 수 없다. 그라디언트라 로고(logo-mili-ai.svg)처럼 filter 반전을
   쓰면 라이트 배경에서 색이 어긋나 이미지 자체를 두 벌 둔다. */
.skill-badge-icon__layer--fill-light { display: none; }
.mili-light .skill-badge-icon__layer--fill-dark { display: none; }
.mili-light .skill-badge-icon__layer--fill-light { display: block; }

/* 육각형(1381:457) 실측 inset. */
.skill-badge-icon--hexagon .skill-badge-icon__layer--glow { inset: 2% 8.43%; }
.skill-badge-icon--hexagon .skill-badge-icon__layer--fill,
.skill-badge-icon--hexagon .skill-badge-icon__layer--stroke { inset: 4% 10.16%; }
.skill-badge-icon--hexagon .skill-badge-icon__layer--inner { inset: 16% 20.56%; }

/* 방패(1381:232) 실측 inset. */
.skill-badge-icon--shield .skill-badge-icon__layer--glow,
.skill-badge-icon--shield .skill-badge-icon__layer--fill,
.skill-badge-icon--shield .skill-badge-icon__layer--stroke { inset: 6% 7% 4% 7%; }
.skill-badge-icon--shield .skill-badge-icon__layer--inner { inset: 15% 18% 14% 18%; }

/* 원형(1381:303) 실측 inset. */
.skill-badge-icon--circle .skill-badge-icon__layer--glow { inset: 2%; }
.skill-badge-icon--circle .skill-badge-icon__layer--fill,
.skill-badge-icon--circle .skill-badge-icon__layer--stroke { inset: 4%; }
.skill-badge-icon--circle .skill-badge-icon__layer--inner { inset: 15%; }
.skill-badge-icon--circle .skill-badge-icon__layer--ticks { inset: 0; }

/* Lv 필 라벨 — 도형 우상단에 겹쳐 배치(Figma 실측: 프레임 대비 우측 상단 안쪽).
   배경은 레벨색(--skill-badge-color, --lvN 규칙이 정의), 글자는 항상 고정 어두운색 —
   둘 다 currentColor 를 쓰면 이 요소 자신의 color 하나로 뒤섞이므로 변수로 분리했다. */
/* 2026-09-04 재실측(Figma node 3048:90336 등): Lv 필에 레벨색 은은한 발광(drop-shadow 0 0
   2.216px, 55% 알파)이 걸려 있었다 — --skill-badge-color 는 hex 리터럴이라 알파를 못 태우므로
   호버 발광(dashboard.css §배지 호버 발광)과 같은 방식으로 불투명 색 + 옅은 blur 로 근사한다. */
.skill-badge-icon__level {
    position: absolute;
    top: -0.25rem;
    right: -0.375rem;
    padding: 0.0625rem 0.375rem;
    border-radius: var(--mili-radius-button);
    background: var(--skill-badge-color);
    box-shadow: 0 0 4px var(--skill-badge-color);
    color: #0a0a0a;
    font-family: var(--mili-font-metric);
    font-size: 0.625rem;
    font-weight: var(--mili-font-weight-bold);
    white-space: nowrap;
}

.skill-badge-icon__code {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-family: var(--mili-font-metric);
    font-weight: var(--mili-font-weight-bold);
    font-size: calc(var(--skill-badge-size, 48px) * 0.22);
    color: currentColor;
    line-height: 1;
    white-space: nowrap;
}

.skill-badge-icon--lv1 { color: var(--mili-badge-lv1); --skill-badge-color: var(--mili-badge-lv1); }
.skill-badge-icon--lv2 { color: var(--mili-badge-lv2); --skill-badge-color: var(--mili-badge-lv2); }
.skill-badge-icon--lv3 { color: var(--mili-badge-lv3); --skill-badge-color: var(--mili-badge-lv3); }
.skill-badge-icon--lv4 { color: var(--mili-badge-lv4); --skill-badge-color: var(--mili-badge-lv4); }
.skill-badge-icon--lv5 { color: var(--mili-badge-lv5); --skill-badge-color: var(--mili-badge-lv5); }

/* 후광(glow) 레이어는 다크/라이트가 같은 SVG 를 쓴다(테마 공용 에셋, _skill-badge-icon.jsp:8) —
   흐릿한 블러 스트로크(opacity .7 × stroke-opacity .55 ≈ 알파 .38)라 시안(Figma node 3249:63731
   라이트 · 3048:90331 다크, 2026-09-04 사용자 지적으로 둘 다 재확인)만큼 또렷하게 번지지
   않는다 — 다크도 라이트만큼은 아니지만 확실히 옅다. 에셋을 새로 만들지 않고 두 테마 모두
   레벨색 drop-shadow 를 겹쳐 체감 밝기를 보강한다 — 호버 발광(dashboard.css §배지 호버 발광)과
   같은 방식·같은 --skill-badge-color 토큰을 쓴다. 미획득(locked) 배지는 --skill-badge-color 가
   없어 제외한다(빛나면 "획득했다"고 오독된다). */
.skill-badge-icon:not(.skill-badge-icon--locked) .skill-badge-icon__layer--glow {
    filter:
        drop-shadow(0 0 4px var(--skill-badge-color))
        drop-shadow(0 0 10px var(--skill-badge-color))
        drop-shadow(0 0 10px var(--skill-badge-color));
}

/* 미획득: 역량군 모양이 아니라 공용 원형 + 자물쇠(Figma node 1386:1667 실측).
   2026-08-13 정정: rgba(255,255,255,.06) 리터럴은 스코프가 없어 라이트에도 그대로 적용돼
   흰 배경 위에 흰 6% 오버레이가 얹혀 사실상 안 보였다 — home.css:164 가 쓰는 패턴대로
   --mili-fg-rgb 기반으로 바꾼다(다크는 거의 같은 값, 라이트는 옅은 회색으로 정상 표시). */
.skill-badge-icon--locked {
    border-radius: var(--mili-radius-badge);
    background: rgba(var(--mili-fg-rgb), 0.06);
    color: var(--mili-muted);
}

.skill-badge-icon__lock {
    font-size: 40%;
}

/* ----- 마이페이지 요약 미리보기(2x2, dashboard.jsp) ----- */

.badge-preview-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--mili-space-3);
}

.badge-preview-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.625rem;
    padding: 0.75rem 0.5rem;
    border-radius: var(--mili-radius-card);
    text-align: center;
    text-decoration: none;
}

/* Lv 태그를 배지 도형 위에 겹쳐 배치하는 기준 상자. */
.badge-preview-item__icon-wrap {
    position: relative;
    flex: 0 0 auto;
    width: 3.5rem;
    height: 3.5rem;
}

.badge-preview-item__title {
    margin: 0;
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    /* keep-all 없이는 "업무 분석" 같은 공백 있는 제목도 360px 처럼 좁은 실기기 폭에서
       음절 중간("분/석")에서 끊긴다 — 이 저장소가 한글 제목에 이미 쓰는 관례
       (onboarding.css·home.css 등)를 여기에도 적용한다. */
    word-break: keep-all;
}

/* ============================================================================
   Jalynne 공통 시각 보정 (.mili-style-refresh)
   ----------------------------------------------------------------------------
   원본: .reference/miliai-hddn/site/current/css/design/components.css (316줄, 전량)
   기준: 43a6835 (2026-08-06, "Merge pull request #30 from hddnfig/codex/restore-static-root")

   이 절은 이례적으로 **참고 저장소의 CSS 를 거의 그대로** 옮긴 것이다. 디자이너가 우리 화면을
   캡처한 HTML 위에 직접 쓴 보존 계층이라 셀렉터가 처음부터 우리 클래스명(.app-icon-btn ·
   .app-sidebar__link · .widget-card · .mypage-dash-* …)으로 되어 있다. 참고의 다른 CSS 처럼
   Tailwind DOM 을 겨냥한 게 아니라 번역 없이 붙는다.

   적용 범위 — 참고와 동일하게 **강의(vod)·프로젝트(pbl) 계열을 뺀** 화면이다.
   참고 site/current/DESIGN.md: "강의(/courses 와 상세)와 프로젝트(/pbl 과 상세)를 제외한
   캡처 경로의 <body> 에만 mili-style-refresh 클래스를 명시한다" — 이미 별도 시안이 끝난 화면이라
   공통 보정이 그 시안과 싸우지 않게 하려는 것이다. 우리도 같은 선을 긋는다:
   vod/*·pbl/* 화면 JSP 의 <body> 에는 이 클래스를 붙이지 않는다.

   원본에서 바꾼 것 (전부 라이트 모드 때문이다 — 원본은 다크 캡처만 보고 쓰였다)
     a) --mili-control-border / --mili-control-hover 가 흰색 알파 고정이라 라이트에서 사라진다.
        rgba(var(--mili-fg-rgb), α) 로 바꿔 두 테마에서 같은 세기로 보이게 했다.
     b) 폼 포커스 링 rgb(180 255 57 / 14%) 는 다크 라임 고정이다.
        rgba(var(--mili-brand-rgb), .14) 로 바꿔 라이트에서는 라이트 브랜드색을 쓴다.
     c) --mili-elevated-shadow 의 검정 18% 는 흰 판 위에서 무거워 라이트만 --mili-shadow-lg 로 둔다.
     d) .status-badge 는 우리 마크업에 없다(나머지 23개 셀렉터는 전부 실재). 지우지 않고 두었다 —
        해가 없고 나중에 그 이름을 쓰면 바로 걸린다.
   그 밖의 값·순서·의도는 원본 그대로다.
   ============================================================================ */

.mili-style-refresh {
    /* (a) 원본은 rgb(255 255 255 / 12%) · 7% 고정 */
    --mili-control-border: rgba(var(--mili-fg-rgb), 0.12);
    --mili-control-hover: rgba(var(--mili-fg-rgb), 0.07);
    --mili-elevated-shadow: 0 18px 36px rgba(0, 0, 0, 0.18);
}

/* (c) */
.mili-light.mili-style-refresh,
.mili-light .mili-style-refresh {
    --mili-elevated-shadow: var(--mili-shadow-lg);
}

/* GNB: 유틸리티 컨트롤은 시각적으로 가볍게 두고, 프로필은 박스형 버튼으로 만들지 않은 채
   아이콘 묶음과 구분선으로만 갈라 놓는다. */
.mili-style-refresh .app-icon-btn {
    border-color: transparent;
    background: transparent;
    border-radius: var(--mili-radius-card);
    transition: color var(--mili-motion-fast) ease,
        background-color var(--mili-motion-fast) ease,
        opacity var(--mili-motion-fast) ease;
}

/* hover/포커스: 아이콘 뒤에 둥근 사각형 배경이 채워진다(사용자 제공 참고 이미지 실측) —
   테두리 없이 --mili-surface-interactive 로 꽉 채우고 opacity 를 1로 올려 또렷하게 만든다. */
.mili-style-refresh .app-icon-btn:hover,
.mili-style-refresh .app-icon-btn:focus-visible {
    background: var(--mili-surface-interactive);
    opacity: 1;
}

/* reference.css 의 전역 hover 밝기 필터(brightness(1.045), 이미 선택된 컨트롤은 0.95)가
   .app-icon-btn 에도 걸린다 — 배경이 이미 --mili-surface-interactive(라이트 #F1F3F5, 흰색에
   가까운 밝은 회색)라 1.045 를 곱하면 거의 순수 흰색으로 날아가 배경과 구분이 안 된다(테마 토글
   버튼은 라이트 모드일 때 aria-pressed="true" 라 반대로 0.95 가 걸려 다른 두 아이콘과도 달라
   보였다). 상단바 아이콘은 배경색 자체로 hover 피드백을 주므로 필터는 걸지 않는다.
   reference.css 는 참고 이식본이라 손대지 않고, 특정도를 올려(!important) 여기서만 무효화한다. */
.mili-app-transition.mili-style-refresh .app-icon-btn:hover,
.mili-app-transition.mili-style-refresh .app-icon-btn:focus-visible {
    filter: none !important;
}

.mili-style-refresh .app-chip-user {
    position: relative;
    gap: var(--mili-space-2);
    margin-left: var(--mili-space-2);
    padding: 4px 0 4px 17px;
    border: 0;
    background: transparent;
    transition: color var(--mili-motion-fast) ease, opacity var(--mili-motion-fast) ease;
}

.mili-style-refresh .app-chip-user::before {
    position: absolute;
    left: 0;
    top: 50%;
    width: 1px;
    height: 20px;
    background: var(--mili-control-border);
    content: "";
    transform: translateY(-50%);
}

.mili-style-refresh .app-chip-user:hover,
.mili-style-refresh .app-chip-user:focus-visible {
    background: transparent;
    color: var(--mili-primary);
}

.mili-style-refresh .app-chip-user > .bi {
    display: none;
}

/* LNB: 활성 목적지가 브랜드 신호를 갖고, 중간 섹션은 중성으로 남는다.
   ⚠️ 2026-08-12 두 번 뒤집었다 — 처음엔 이 절 원본(디자이너가 실제 화면 캡처 위에 쓴 CSS)이
   §18 기본 규칙과 반대라고 보고 방향을 바꿨는데, 실사용 페이지 목업(Figma node 1653:4184)으로
   재대조하니 오히려 **이 절 원본이 맞았다** — 호버는 옅은 배경, "지금 있는 페이지"만 꽉 찬
   배경이다(§18 도입부 --mili-sidebar-tint-* /--mili-sidebar-current-* 참고). 원본 값으로
   되돌린다. border-radius 는 legacy --mili-radius-sm(=0, 각짐)을 강제해 왔는데 기본 항목은
   14px 로 둥글다 — 선언을 지워 §18 기본값이 그대로 먹게 둔다(활성은 아래서 다시 각지게 한다,
   Figma 상 탭 자체엔 라운드가 없다 — 2026-08-12 사용자 확인). */
.mili-style-refresh .app-sidebar__link {
    transition: color var(--mili-motion-fast) ease,
        background-color var(--mili-motion-fast) ease;
}

/* 배경은 §18 과 같이 QA 2-1 의 호버 전용 톤(--mili-sidebar-hover-bg)을 쓴다. */
.mili-style-refresh .app-sidebar__link:hover,
.mili-style-refresh .app-sidebar__link:focus-visible {
    background: var(--mili-sidebar-hover-bg);
    border-radius: var(--mili-radius-none);
    color: var(--mili-sidebar-hover-fg);
}

/* 2026-09-18 톤 변경 — 위 기본 §18 규칙과 같은 이유(LNB 변경 0916 재실측). */
.mili-style-refresh .app-sidebar__link--active,
.mili-style-refresh .app-sidebar__link--active:hover,
.mili-style-refresh .app-sidebar__link--active:focus-visible {
    background-color: rgba(173, 252, 172, 0.2);
    border-radius: var(--mili-radius-none);
    color: var(--mili-primary-200);
}

.mili-light.mili-style-refresh .app-sidebar__link--active,
.mili-light.mili-style-refresh .app-sidebar__link--active:hover,
.mili-light.mili-style-refresh .app-sidebar__link--active:focus-visible {
    background-color: var(--mili-primary-50);
    color: var(--mili-primary-800);
}

/* 그룹 부모도 펼침 여부와 상관없이 항상 칠한다 — §18 기본 규칙과 같은 이유(2026-08-12 사용자 확인). */
.mili-style-refresh .app-sidebar__link--group.app-sidebar__link--current {
    background-color: rgba(173, 252, 172, 0.2);
    border-radius: var(--mili-radius-none);
    color: var(--mili-primary-200);
}

.mili-light.mili-style-refresh .app-sidebar__link--group.app-sidebar__link--current {
    background-color: var(--mili-primary-50);
    color: var(--mili-primary-800);
}

.mili-style-refresh .app-sidebar__link--active .app-sidebar__caret,
.mili-style-refresh .app-sidebar__link--current .app-sidebar__caret {
    color: currentColor;
}

/* 1depth 와 같은 이유(2026-09-22 재실측) — --mili-sidebar-hover-fg 를 쓴다. */
.mili-style-refresh .app-sidebar__sublink:hover,
.mili-style-refresh .app-sidebar__sublink:focus-visible {
    color: var(--mili-sidebar-hover-fg);
}

/* 재사용 컨트롤: Bootstrap·화면 동작은 그대로 두고 hover·focus·선택 상태만 같은 계조로 맞춘다.

   2026-08-12: 여기 있던 .app-subtabs__link 규칙 4벌을 걷어냈다. 탭이 하단 라인형으로 바뀌었는데
   이 절이 알약형 시절의 테두리·채움을 뒤에서 다시 칠해 활성 탭이 그대로 초록 채움으로 남았다
   (/roadmap 에서 확인).
   2026-08-27: 같은 이유로 필터칩(.filter-chip) 규칙도 전부 걷어냈다 — 그 칩은 표준 알약 탭
   .tab-pill__link 가 되었고, 배색 정본은 theme/figma-classes.css 한 곳이다. 여기서는 탭에
   포커스 링만 남긴다(hover 시 -1px 들어올림은 시안에 없어 빼고 배경색 전환만 남긴다). */
.mili-style-refresh :is(.btn, .page-link) {
    transition: color var(--mili-motion-fast) ease,
        border-color var(--mili-motion-fast) ease,
        background-color var(--mili-motion-fast) ease,
        box-shadow var(--mili-motion-fast) ease,
        transform var(--mili-motion-fast) ease;
}

.mili-style-refresh :is(.btn:not(:disabled), .page-link):hover {
    transform: translateY(-1px);
}

.mili-style-refresh :is(.btn, .page-link, .tab-line__link, .tab-pill__link):focus-visible {
    outline: 2px solid var(--mili-primary-strong);
    outline-offset: 2px;
}

/* 중성 표면으로 계층만 세운다 — 마이페이지·커뮤니티·쇼케이스·홈·온보딩의 레이아웃은 그대로다.
   border-color 는 다섯 클래스 모두에 걸어도 안전하다(테두리가 없는 클래스는 그냥 무시된다).

   2026-08-19 재지적 — box-shadow 는 border-color 와 달리 실제 테두리가 없어도 항상 그려진다.
   .widget-card/.mypage-panel 은 border: 1px solid 가 실제로 있어 그림자가 "카드 입체감"으로
   보이지만, .empty-state/.result-bar/.table-responsive 는 애초에 테두리가 없는 평평한 요소라
   같은 그림자가 밑변에 붙은 검은 밑줄처럼 보인다(QA31 학습현황, 알림 화면에서 각각 발견).
   그래서 box-shadow 는 실제로 카드 테두리를 가진 두 클래스에만 건다. */
.mili-style-refresh :is(.widget-card, .mypage-panel, .empty-state, .result-bar, .table-responsive) {
    border-color: var(--mili-border);
}

.mili-style-refresh :is(.widget-card, .mypage-panel) {
    box-shadow: var(--mili-shadow-sm);
}

/* 2026-08-13 재지적 — .mypage-panel 은 프로필 수정 폼·QnA 답변 작성 폼처럼 클릭 대상이 아닌
   정적 섹션에도 쓰인다. 카드처럼 뜨는 hover 그림자·이동 효과는 클릭 가능한 .widget-card 에만
   남기고 .mypage-panel 에서는 뺀다. */
.mili-style-refresh .widget-card {
    transition: border-color var(--mili-motion-standard) ease,
        box-shadow var(--mili-motion-standard) ease,
        transform var(--mili-motion-standard) ease;
}

.mili-style-refresh .widget-card:hover {
    border-color: var(--mili-border-strong);
    box-shadow: var(--mili-elevated-shadow);
    transform: translateY(-2px);
}

.mili-style-refresh :is(.badge, .status-badge, .course-card__badge) {
    border-radius: var(--mili-radius-badge);
}

.mili-style-refresh .form-control,
.mili-style-refresh .form-select {
    border-color: var(--mili-control-border);
    background-color: var(--mili-surface);
}

.mili-style-refresh .form-control:hover,
.mili-style-refresh .form-select:hover {
    border-color: var(--mili-border-strong);
}

.mili-style-refresh .form-control:focus,
.mili-style-refresh .form-select:focus {
    border-color: var(--mili-primary-border);
    /* (b) 원본은 rgb(180 255 57 / 14%) 고정 */
    box-shadow: 0 0 0 3px rgba(var(--mili-brand-rgb), 0.14);
}

/* 위 전역 다크 인풋 테두리(12% 화이트)와 특정성이 같아 소스 순서로 이겨 버린다 —
   로그인 입력창은 Figma 실측 테두리(#46494e = --mili-neutral-750)를 써야 해서 여기서 다시 못박는다.
   ⚠️ 2026-09-11 — 로그인이 다크 전용(mili-theme-locked)이던 동안은 이 선택자가 항상 다크였지만,
   토글을 풀고 나니 .mili-light .auth-field(§6-1699, --mili-neutral-400)보다 특정성이 같고 파일
   순서가 이 규칙이 더 뒤라 라이트에서도 이 다크값이 계속 이겨 버렸다(style-diff.cjs 실측으로 확인
   — 라이트인데 렌더값이 #46494e). .mili-theme-dark 를 더해 다크에서만 적용되게 좁힌다. */
.mili-theme-dark.mili-style-refresh .auth-field {
    border-color: var(--mili-neutral-750);
}

@media (max-width: 575.98px) {
    .mili-style-refresh .app-chip-user {
        margin-left: var(--mili-space-1);
        padding-left: var(--mili-space-3);
    }

    .mili-style-refresh :is(.widget-card, .mypage-panel):hover,
    .mili-style-refresh :is(.btn:not(:disabled), .page-link):hover {
        transform: none;
    }
}

/* 나머지 화면군 — 각 화면의 레이아웃은 그대로 두고 반복되는 상호작용 상태만 셸과 맞춘다. */

.mili-style-refresh :is(.mypage-dash-card, .mypage-dash-continue,
    .about-feature, .about-step, .roadmap-card, .post-row) {
    transition: border-color var(--mili-motion-standard) ease,
        background-color var(--mili-motion-standard) ease,
        box-shadow var(--mili-motion-standard) ease,
        transform var(--mili-motion-standard) ease;
}

/* 홈 상단 프로필·인기 학습 카드(.home-card--profile/--recent)는 사용자 지시로 hover 승격 효과(테두리
   강조·그림자·이동)를 뺀다 — 위 공용 :is() 목록에서 .home-card 를 제외한다. */
.mili-style-refresh :is(.mypage-dash-card, .mypage-dash-continue,
    .about-feature, .about-step, .roadmap-card):hover {
    border-color: var(--mili-border-strong);
    box-shadow: var(--mili-elevated-shadow);
    transform: translateY(-2px);
}

.mili-style-refresh .mypage-dash-overview__tile,
.mili-style-refresh .mypage-dash-shortcut {
    border-color: var(--mili-border);
    transition: border-color var(--mili-motion-fast) ease,
        background-color var(--mili-motion-fast) ease,
        color var(--mili-motion-fast) ease,
        transform var(--mili-motion-fast) ease;
}

.mili-style-refresh .mypage-dash-overview__tile:hover,
.mili-style-refresh .mypage-dash-shortcut:hover,
.mili-style-refresh .mypage-dash-shortcut:focus-visible {
    border-color: var(--mili-primary-border);
    background: var(--mili-primary-bg);
    transform: translateY(-1px);
}

.mili-style-refresh .mypage-dash-shortcut:hover .mypage-dash-shortcut__label,
.mili-style-refresh .mypage-dash-shortcut:focus-visible .mypage-dash-shortcut__label {
    color: var(--mili-primary);
}

/* QA38(2026-08-15) — 구분선형으로 바뀌며 hover 도 카드 승격(테두리 강조+그림자) 대신
   배경만 옅게 얹는다. 배경색 정본은 §23-2 의 .post-row:hover(--mili-surface-hover) 다.

   2026-08-26 삭제 — 여기 있던 `background: var(--mili-surface-raised)` 는 위 주석("정본을
   그대로 쓴다")과 달리 다른 토큰으로 정본을 덮고 있었다. 특정도가 한 단계 높아(0,3,0) 항상
   이겼고 그 결과 다크 hover 가 시안 Neutral/800(#2B2E33) 대신 #1E2227 로 어두워지고,
   라이트 hover 는 #FFFFFF 가 되어 행 배경이 오히려 빠져 보였다. 규칙을 지우면 정본이 산다.
   (여기서 남는 것은 위쪽 §의 transition·transform 처리뿐이다.) */

.mili-style-refresh .search-hero__form {
    border-color: var(--mili-control-border);
    background: var(--mili-surface);
    transition: border-color var(--mili-motion-fast) ease,
        background-color var(--mili-motion-fast) ease,
        box-shadow var(--mili-motion-fast) ease;
}

.mili-style-refresh .search-hero__form:hover {
    border-color: var(--mili-border-strong);
}

.mili-style-refresh .search-hero__form:focus-within {
    border-color: var(--mili-primary-border);
    background: var(--mili-surface-raised);
    /* (b) 상동 */
    box-shadow: 0 0 0 3px rgba(var(--mili-brand-rgb), 0.14);
}

.mili-style-refresh .search-hero__submit {
    transition: color var(--mili-motion-fast) ease, transform var(--mili-motion-fast) ease;
}

.mili-style-refresh .search-hero__submit:hover,
.mili-style-refresh .search-hero__submit:focus-visible {
    color: var(--mili-primary);
    transform: scale(1.06);
}

.mili-style-refresh .onboarding-guide li {
    transition: border-color var(--mili-motion-fast) ease, color var(--mili-motion-fast) ease;
}

.mili-style-refresh .onboarding-guide li:hover {
    border-color: var(--mili-primary-border);
    color: var(--mili-text);
}

@media (max-width: 575.98px) {
    .mili-style-refresh :is(.mypage-dash-card, .mypage-dash-continue,
        .about-feature, .about-step, .roadmap-card):hover,
    .mili-style-refresh .mypage-dash-overview__tile:hover,
    .mili-style-refresh .mypage-dash-shortcut:hover,
    .mili-style-refresh .search-hero__submit:hover {
        transform: none;
    }
}

/* ===================================================================
   25) 콘텐츠 상세 댓글 (.content-comments*)
   -------------------------------------------------------------------
   마크업은 WEB-INF/views/common/_content-comments.jsp 이고, VOD 과정 상세(/courses/{no}),
   PBL 프로젝트 상세(/pbl/{offeringNo}), 로드맵 상세(/roadmap/{no}) 3개 화면이 함께 쓴다
   (게시판 PRD §4.3). 목록 항목 시각은 22) Q&A 답변 스레드(.qna-reply*)와 같은 어휘를 쓴다
   — 좋아요 버튼은 전용 CSS 없이 community/qna-detail.jsp 와 같은 Bootstrap 버튼 관례를 그대로 쓴다.

   2026-09-18 Figma QA(node 3931:115689 "QA 필요 항목", 시안 node 3931:115707) 전체 재작업 —
   "댓글 Padding 값 20x20으로 변경 + 댓글 디자인 형상 모두 일치" 지적을 사용자가 "시안대로 전체
   재작업"으로 확정. showcase/detail.css 의 .showcase-comments(같은 QA 배치, node 3931:115809)가
   이미 같은 카드+헤더 패턴으로 검증돼 있어 그 값을 그대로 따른다(패딩 20px 는 그 화면에서
   "라이브 화면 devtools 캡처로 30px 확인 → 목표 20px" 로 재확인된 값과 동일). PC 전용 — 이 섹션
   전체에 미디어쿼리가 없던 것을 유지한 채 아래 변경분만 PC 전용으로 격리한다(@media (min-width:
   768px)). 768px 미만(모바일)은 이 QA 배치 대상이 아니므로 기존 값을 그대로 보존한다. */

.content-comments {
    margin-top: var(--mili-space-8);
    display: flex;
    flex-direction: column;
}

/* 시안(node 3931:115761)은 작성 폼이 목록 "아래"라 마크업 순서(_content-comments.jsp)를
   그렇게 옮겼는데, 이 조각은 모바일과 공용이라 모바일도 같이 아래로 밀려 버렸다. 이번 QA는
   PC 전용이므로 모바일은 원래 순서(폼이 위)를 유지해야 한다 — DOM 은 그대로 두고 flex order 로만
   화면별 시각 순서를 되돌린다(모바일 기본값 = 원래 순서, PC 에서만 뒤집는다). */
.content-comments > .content-comments__list,
.content-comments > .empty-state {
    order: 2;
}

.content-comments > .content-comments__form,
.content-comments > .content-comments__login-hint {
    order: 1;
}

/* 768px 미만은 QA 대상이 아니다 — 기존(변경 전) 모습 그대로 보존해 회귀를 막는다. */
@media (min-width: 768px) {
    .content-comments {
        padding: var(--mili-space-5);
        border: 1px solid var(--mili-border);
        border-radius: var(--mili-radius-card);
        background: transparent;
    }

    .content-comments > .content-comments__list,
    .content-comments > .empty-state {
        order: 1;
    }

    .content-comments > .content-comments__form,
    .content-comments > .content-comments__login-hint {
        order: 2;
    }
}

.content-comments__title {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
    margin: 0 0 1rem;
    font-size: 1.0625rem;
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-ink);
}

/* 2026-09-18 QA는 PC 전용이라 헤더 아이콘도 PC에서만 보인다 — 모바일은 2026-08-27 확정(아이콘
   제거)을 그대로 유지한다. 마크업(_content-comments.jsp)에는 항상 있고 여기서만 감춘다. */
.content-comments__title-icon {
    display: none;
}

@media (min-width: 768px) {
    /* 시안 실측 Body/B1(18px) — showcase-comments__title 과 동일 근거. */
    .content-comments__title {
        margin: 0 0 var(--mili-space-5);
        font-size: var(--mili-body-b1-size);
        line-height: var(--mili-body-b1-line);
        font-weight: var(--mili-font-weight-semibold);
        color: var(--mili-fg);
    }

    .content-comments__title-icon {
        display: inline-block;
    }
}

.content-comments__count {
    color: var(--mili-fg-faint);
    font-weight: var(--mili-font-weight-semibold);
}

@media (min-width: 768px) {
    /* 시안 실측 rgba(248,249,250,0.6) — --mili-fg-rgb 가 그 값이다(라이트는 자동으로 뒤집힌다).
       showcase-comments__count 와 같은 근거. */
    .content-comments__count {
        color: rgba(var(--mili-fg-rgb), 0.6);
        font-weight: var(--mili-font-weight-regular);
    }
}

.content-comments__form {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
    margin-bottom: var(--mili-space-5);
}

@media (min-width: 768px) {
    /* 시안은 작성 폼이 목록 "아래"다(_content-comments.jsp 에서 마크업 순서도 이미 옮겼다) —
       위 여백 대신 목록과의 간격을 맞춘다. */
    .content-comments__form {
        flex-direction: row;
        align-items: flex-start;
        gap: var(--mili-space-3);
        margin: var(--mili-space-5) 0 0;
    }

    .content-comments__form .content-comments__input {
        flex: 1 1 auto;
    }
}

.content-comments__input {
    resize: vertical;
}

@media (min-width: 768px) {
    /* 시안 실측(node 3931:115763) 배경 rgba(255,255,255,.04) · 테두리 1px rgba(255,255,255,.1) ·
       반경 5px — showcase-comments__form 의 입력 필드와 같은 근거로 --mili-fg-rgb 로 옮겨
       라이트에서도 성립하게 한다. */
    .content-comments__input {
        padding: var(--mili-space-3);
        border: 1px solid rgba(var(--mili-fg-rgb), 0.1);
        border-radius: var(--mili-radius-button);
        background: rgba(var(--mili-fg-rgb), 0.04);
    }

    .content-comments__input::placeholder {
        color: var(--mili-fg-faint);
    }
}

.content-comments__submit {
    align-self: flex-end;
}

@media (min-width: 768px) {
    /* 시안은 입력창과 같은 줄, 오른쪽 — 버튼 색은 공용 .btn.default(다크 흰 판 + 검정 글자)가
       시안과 정확히 같아 그대로 쓴다(showcase-comments__submit-row 와 같은 결론). */
    .content-comments__form .content-comments__submit {
        align-self: stretch;
        height: 44px;
    }
}

.content-comments__login-hint {
    margin-bottom: var(--mili-space-5);
    color: var(--mili-fg-muted);
    font-size: var(--mili-body-b3-size);
}

.content-comments__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
}

@media (min-width: 768px) {
    /* 시안 실측(node 3931:115714) 항목 간격 5px. */
    .content-comments__list {
        gap: 5px;
    }
}

.content-comments__item {
    padding: 0.875rem 1rem;
    border: 1px solid rgba(var(--mili-fg-rgb), 0.06);
    border-radius: var(--mili-radius-card);
    background: rgba(var(--mili-fg-rgb), 0.02);
}

@media (min-width: 768px) {
    /* 시안(node 3931:115715 등)은 항목 카드에 테두리가 없다 — 배경(rgba(fg,0.02))·반경·패딩은
       이미 시안과 일치해 유지하고 테두리만 지운다. */
    .content-comments__item {
        border: none;
    }
}

/* 작성자(좌)/작성시각(우) 좌우 배치 — qna-reply__meta 와 같은 이유(2026-08-15). */
.content-comments__meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--mili-space-2);
    margin-bottom: 0.375rem;
    font-size: var(--mili-caption-size);
    color: var(--mili-fg-faint);
}

.content-comments__writer {
    font-weight: var(--mili-font-weight-semibold);
    color: var(--mili-fg-muted);
}

.content-comments__dtm {
    color: var(--mili-fg-faint);
}

/* 좋아요+싫어요 버튼 쌍 컨테이너(qna-reply__reactions 와 같다). */
.content-comments__reactions {
    display: flex;
    gap: var(--mili-space-2);
}

/* 2026-09-15 Figma 실측(node 4038:313318/4038:313323, 갤러리 상세/댓글) — 이 좋아요/싫어요
   버튼(미클릭 상태)은 공용 .btn.neutral 과 값이 다르다: 다크 테두리 Neutral/700(#66686c),
   라이트 배경 Neutral/50(흰색). 공용 .btn.neutral 은 다른 15개 이상 화면(취소·첨부 등)이 함께
   쓰고 2026-08-26 에 지금 값으로 이미 확정돼 있어(같은 파일 상단 .btn.neutral 주석 참고)
   전역을 바꾸지 않는다 — 사용자 확인(2026-09-15): 이 컨테이너로 스코프를 한정한다. */
.content-comments__reactions .btn.neutral {
    border-color: var(--mili-neutral-700);
}

.mili-light .content-comments__reactions .btn.neutral {
    background: var(--mili-neutral-50);
}

/* 사용자 확인(2026-09-16) — 댓글 좋아요/싫어요는 클릭(활성화) 후에도 아웃라인만 유지한다.
   전역 .btn.outline/.btn.error 의 hover 채움(figma-classes.css)은 사이트 다른 화면(Q&A 답변,
   갤러리 답글 등)에 그대로 두고, 이 컨테이너에서만 hover 시에도 배경을 채우지 않도록 되돌린다. */
.content-comments__reactions .btn.outline:hover {
    background: transparent;
    border-color: var(--mili-primary-700);
    color: var(--mili-primary-500);
}

.mili-light .content-comments__reactions .btn.outline:hover {
    background: transparent;
    border-color: var(--mili-primary-600);
    color: var(--mili-primary-600);
}

.content-comments__reactions .btn.error:hover {
    background: transparent;
    border-color: var(--mili-error);
    color: var(--mili-error);
}

.mili-light .content-comments__reactions .btn.error:hover {
    background: transparent;
    border-color: var(--mili-error);
    color: var(--mili-error);
}

/* 비로그인 사용자가 보는 정적 좋아요/싫어요 칩(_content-comments.jsp) — 클릭할 수 없으므로
   커서·hover 반응을 끈다. 모양은 로그인 상태의 .btn.small.neutral 과 동일해야 한다(Figma 실측). */
.content-comments__reactions--static .btn {
    cursor: default;
    pointer-events: none;
}

.content-comments__body {
    margin: 0 0 0.625rem;
    color: var(--mili-body);
    font-size: var(--mili-body-b3-size);
    line-height: var(--mili-line-body, 1.6);
    white-space: pre-wrap;
}

.content-comments__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-2);
    flex-wrap: wrap;
}

/* 본인 댓글 수정/삭제(js/common/comment-actions.js). */
.content-comments__owner-actions {
    display: flex;
    gap: var(--mili-space-2);
}

/* 마크업(_content-comments.jsp)엔 아이콘·글자 라벨이 둘 다 있다 — 모바일은 기존 텍스트 라벨
   버튼("수정"/"삭제") 그대로, PC 만 시안대로 아이콘 전용으로 바꾼다(이 QA 는 PC 전용). */
.content-comments__action-icon {
    display: none;
}

@media (min-width: 768px) {
    /* 2026-09-18 Figma QA(node 3931:115731/115732) — 수정/삭제가 아이콘 전용 버튼이 되면서
       .content-comments__reactions 와 같은 테두리색(Neutral/700)을 쓴다(시안은 두 버튼군이
       같은 부품이다). 아이콘만 남아 좌우 패딩이 남으므로 정사각형에 가깝게 폭을 높이와
       맞춘다. */
    .content-comments__owner-actions .btn {
        width: 28px;
        padding-left: 0;
        padding-right: 0;
    }

    .content-comments__action-icon {
        display: inline-block;
    }

    .content-comments__action-label {
        display: none;
    }

    .content-comments__owner-actions .btn.neutral {
        border-color: var(--mili-neutral-700);
    }

    .mili-light .content-comments__owner-actions .btn.neutral {
        background: var(--mili-neutral-50);
    }

    /* 삭제 버튼은 모바일에서 .btn.error(빨강)을 그대로 유지하지만(회귀 방지), 시안은 삭제도
       edit 과 같은 neutral 외곽선이라 PC 에서만 무채색으로 덮는다 — 카탈로그 클래스는 그대로
       쓰고(.btn.error) 이 컴포넌트 안에서만 색을 재정의한다(qna-reply/showcase-comments 가
       같은 이유로 .btn.neutral 색을 컨테이너 스코프로 덮는 관례와 동일). */
    .content-comments__delete-btn.btn.error {
        border-color: var(--mili-neutral-700);
        color: inherit;
    }

    .mili-light .content-comments__delete-btn.btn.error {
        background: var(--mili-neutral-50);
    }
}

.content-comments__edit-form {
    margin-top: 0.625rem;
}

/* ===================================================================
   24) 리치 텍스트 본문 (.mili-rich)
   -------------------------------------------------------------------
   서버가 정제해 내보낸 본문 HTML 을 담는 컨테이너다(user/text/RichText.java).
   PBL 프로젝트·미션·문제의 설명·평가기준·산출물이 여기로 들어오고, 다른 도메인에서
   같은 성격의 본문이 생기면 그대로 쓸 수 있게 화면 전용이 아니라 여기에 둔다.

   허용 태그 집합을 RichText 의 Safelist 와 <b>같게</b> 맞춘다 — 허용했는데 스타일이 없으면
   브라우저 기본 모양(과한 여백·글머리 위치)이 튀어나온다.

   ⚠️ white-space 를 pre-line/pre-wrap 으로 바꾸지 말 것. 정제된 HTML 에는 원문에 있던
   태그 사이 개행이 남아 있어서, pre-line 을 걸면 그것이 빈 줄로 살아나 문단 간격이 벌어진다
   (평문의 개행은 RichText 가 이미 <br>·<p> 로 옮겨 두었으므로 CSS 가 할 일이 없다).
   =================================================================== */

.mili-rich {
    color: var(--mili-body);
    line-height: 1.7;
    word-break: break-word;
}

/* 첫·마지막 요소의 바깥 여백은 컨테이너가 정한다(블록 사이 간격만 여기서 준다). */
.mili-rich > *:first-child { margin-top: 0; }
.mili-rich > *:last-child  { margin-bottom: 0; }

.mili-rich p {
    margin: 0 0 0.625rem;
}

/* 2026-08-28 — h1·h2 를 이 표에 들였다. 갤러리(showcase) 원글은 BoardService.CONTENT_SAFELIST 가
   h1~h6 를 다 허용하는데 여기에 규칙이 없어 브라우저 기본값(h1 = 2em = 32px)이 그대로 나왔다
   — 화면 제목(--mili-hero-title-size 36px)과 거의 같은 크기라 본문이 제목처럼 보였다
   (2026-08-28 사용자 지적, 로드맵 상세 설명에서 확인). 값은 쓰기 쪽(.mili-editor__surface,
   h1 20px)과 맞춘다 — 에디터에서 보던 크기와 저장 후 크기가 어긋나지 않게. */
.mili-rich h1,
.mili-rich h2,
.mili-rich h3,
.mili-rich h4,
.mili-rich h5,
.mili-rich h6 {
    margin: 1rem 0 0.375rem;
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.35;
}

.mili-rich h1 { font-size: var(--mili-heading-h4-size); } /* 20px — 쓰기 쪽과 같은 값 */
.mili-rich h2 { font-size: 1.125rem; }
.mili-rich h3 { font-size: 1.0625rem; }
.mili-rich h4 { font-size: 0.9375rem; }
.mili-rich h5,
.mili-rich h6 { font-size: var(--mili-body-b3-size); }

.mili-rich ul,
.mili-rich ol {
    margin: 0 0 0.625rem;
    padding-left: 1.375rem;
}

.mili-rich li {
    margin-bottom: 0.1875rem;
}

/* 목록 항목 안의 문단은 줄바꿈만 하고 항목 간격을 벌리지 않는다(에디터 산출물이 <li><p>로 감싼다). */
.mili-rich li > p {
    margin: 0;
}

.mili-rich blockquote {
    margin: 0 0 0.625rem;
    padding: 0.125rem 0 0.125rem 0.75rem;
    border-left: 3px solid var(--mili-border);
    color: var(--mili-muted);
}

.mili-rich code {
    padding: 0.0625rem 0.25rem;
    border-radius: var(--mili-radius-sm);
    background: var(--mili-panel-sunken);
    font-size: 0.875em;
}

.mili-rich pre {
    margin: 0 0 0.625rem;
    padding: 0.75rem;
    border-radius: var(--mili-radius-sm);
    background: var(--mili-code-bg);
    color: var(--mili-code-text);
    overflow-x: auto;
}

/* pre 안의 code 가 인라인 코드 배경을 물려받으면 이중 배경이 된다. */
.mili-rich pre code {
    padding: 0;
    background: none;
    color: inherit;
    font-size: var(--mili-body-b3-size);
}

.mili-rich hr {
    margin: 0.875rem 0;
    border: 0;
    border-top: 1px solid var(--mili-border);
}

.mili-rich a {
    color: var(--mili-brand-strong);
}

/* 붙여넣기로 들어온 이미지(2026-08-13, BoardService.sanitize 가 img 를 허용하면서 추가). */
.mili-rich img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 0 0 0.625rem;
    border-radius: var(--mili-radius-sm);
}

.mili-rich strong,
.mili-rich b {
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-bold);
}

/* ==========================================================================
   사이트 푸터 (.site-footer)

   개인정보처리방침을 전 화면에서 상시 노출하기 위한 공통 조각
   (WEB-INF/views/common/_site-footer.jsp). 다크 앱셸과 라이트 navbar 레이아웃
   양쪽에 들어가므로 색은 전부 토큰으로만 잡는다 — 고정 색을 쓰면 한쪽 테마에서 묻힌다.
   배경도 지정하지 않는다 — 부모 셸의 배경(다크/라이트)을 그대로 비쳐야 한다.

   Figma node 1626:1189 이식(2026-08-12). login.jsp 는 이 파일을 include 하지 않고
   .site-footer/__links/__link/__link--strong 네 클래스만 손으로 재현한다(좁은 인증
   패널이라 아래 3열 레이아웃이 안 들어간다) — 그 넷의 이름·의미를 바꾸면 거기도 고친다.
   __links 가 flex + justify-content:center 라 text-align 없이도 login.jsp 에서
   가운데 정렬로 보인다.
   ========================================================================== */

/* 2026-08-26 Figma Footer 인스턴스 재조회(node 2042:216411 다크 · 2123:272795 라이트) — 주소·
   연락처값·메뉴링크·소셜설명 텍스트는 Neutral/400(#e9e9e9, 다크)/Neutral/850(#1c1f23, 라이트)다.
   기존 --mili-body(--mili-text-secondary)로 근사하기엔 차이가 커서(사용자 확인, 시안 값 그대로
   반영) 사이드바 §18과 같은 방식으로 이 섹션 전용 토큰을 둔다 — "색은 전부 토큰으로만" 방침은
   유지된다. 라벨류("Contact us"·"주요 메뉴"·"소셜 미디어")는 Figma도 Neutral/600·650이라
   기존 --mili-muted 그대로 둔다(대상 아님). */
:root {
    --mili-footer-text: var(--mili-neutral-400);
}

.mili-light {
    --mili-footer-text: var(--mili-neutral-850);
}

/* 여백은 Figma QA 1699:3983 이 정한다 — 위 60px / 아래 100px, 콘텐츠 영역과는 200px.
   (시안 프레임 높이 410 = 59 + 251 + 100 이고, 내부 콘텐츠 폭은 1200px 로 우리와 같다.)
   margin-top 이 곧 "콘텐츠 영역 종료 지점 ↔ Footer 간격" 이다. */
.site-footer {
    margin-top: var(--mili-footer-gap);
    padding: 60px 0 100px;
    border-top: 1px solid var(--mili-border);
    color: var(--mili-muted);
    font-size: var(--mili-body-b3-size);
}

/* 좁은 폭에서는 200px 이 화면 한 판을 통째로 비운다 — 시안에 없는 폭이라 우리가 줄인다.
   상하 패딩(60/100)은 시안이 명시한 값이라 폭과 무관하게 유지한다. */
@media (max-width: 1023px) {
    .site-footer {
        margin-top: 80px;
    }
}

.site-footer__inner {
    width: min(calc(100% - var(--mili-page-padding) * 2), var(--mili-footer-content-max));
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: 40px;
}

/* ---------- 상단 2열(브랜드·주소 / 주요 메뉴) ---------- */

.site-footer__top {
    display: grid;
    grid-template-columns: minmax(0, 486fr) minmax(0, 284fr);
    gap: var(--mili-space-6);
}

@media (max-width: 900px) {
    .site-footer__top {
        grid-template-columns: 1fr;
        gap: 28px;
    }
}

.site-footer__col {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-5);
    min-width: 0;
}

/* 2026-09-18 Figma 재실측 — 로고↔주소블록 칸만 28px(다른 칼럼과 다름, 사용자 확인). */
.site-footer__col--brand {
    gap: 28px;
}

.site-footer__col-title {
    margin: 0;
    color: var(--mili-muted);
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-medium);
}

.site-footer__brand {
    display: inline-flex;
    width: fit-content;
}

.site-footer__brand img {
    display: block;
    height: auto;
}

/* 로고 SVG(images/home/logo-mili-ai.svg)가 fill="#fff" 로 고정돼 있어 라이트 배경에서
   흰 바탕에 흰 로고로 사라진다. 다른 곳(사이드바)은 항상 어두운 바탕 위에만 올라가
   문제가 없지만, 이 푸터는 라이트 navbar 화면(main-layout.jsp)에서도 흰 배경 위에
   그대로 놓이므로 라이트 스코프에서만 반전해 어두운 잉크색으로 보이게 한다.
   2026-09-08: 로그인 화면도 라이트 모드가 생기며 같은 문제가 생겨 아래에 같은 규칙을 뒀다. */
.mili-light .site-footer__brand img {
    filter: invert(1);
}

.mili-light .auth-split__brand img {
    filter: invert(1);
}

.site-footer__address {
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: var(--mili-body-b3-size);
    line-height: 1.4;
    color: var(--mili-footer-text);
}

.site-footer__address p {
    margin: 0;
}

.site-footer__address strong {
    color: inherit;
    font-weight: var(--mili-font-weight-bold);
}

/* 연락처 블록(Figma QA 1699:3983 의 1-2). 예전엔 "Phone …/Email …" 처럼 라벨이 값 앞에
   붙어 있었는데, 시안은 "Contact us" 라벨 하나가 흐리게 위에 서고 번호·메일이 그 아래
   두 줄로 내려온다(라벨 ↔ 값 10px, 값끼리 5px). */
.site-footer__contact {
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: var(--mili-body-b3-size);
    line-height: 1.4;
    color: var(--mili-footer-text);
}

.site-footer__contact-title {
    margin: 0;
    color: var(--mili-muted);
    font-weight: var(--mili-font-weight-medium);
}

.site-footer__contact-lines {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.site-footer__contact-lines p {
    margin: 0;
}

.site-footer__menu-grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--mili-space-6);
}

.site-footer__menu-list {
    display: flex;
    flex-direction: column;
    gap: 15px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-footer__menu-list a {
    color: var(--mili-footer-text);
    font-size: var(--mili-body-b3-size);
    text-decoration: none;
    white-space: nowrap;
}

.site-footer__menu-list a:hover,
.site-footer__menu-list a:focus-visible {
    color: var(--mili-ink);
    text-decoration: underline;
}

.site-footer__social-desc {
    margin: 0;
    color: var(--mili-footer-text);
    font-size: var(--mili-body-b3-size);
    line-height: 1.4;
}

.site-footer__social-links {
    display: flex;
    gap: 10px;
}

/* Figma node 2042:215356("공통") 실측 — 다크 소셜 버튼 배경은 Neutral/870(#15181c, = --mili-surface)이지
   --mili-surface-interactive(#2b3036, 상단바 아이콘 hover 배경과 같은 톤)가 아니다. 라이트는 Neutral/200
   (#f3f3f3)으로 --mili-surface-interactive(#f1f3f5)와 거의 같아 그대로 둔다(2026-08-25). */
.site-footer__social-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--mili-radius-button);
    background: var(--mili-surface);
    color: var(--mili-body);
    font-size: 15px;
    text-decoration: none;
}

.mili-light .site-footer__social-link {
    background: var(--mili-surface-interactive);
}

.site-footer__social-link:hover,
.site-footer__social-link:focus-visible {
    color: var(--mili-ink);
    background: var(--mili-border-strong);
}

/* ---------- 하단 바(정책 링크 · 저작권) ---------- */

.site-footer__bottom {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--mili-space-3);
    padding-top: var(--mili-space-5);
    border-top: 1px solid var(--mili-border);
    font-size: var(--mili-caption-size);
}

.site-footer__links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    /* 2026-09-18 Figma 재실측(node 2042:215356 하위 Footer) — gap 18px(구 --mili-space-2=8px). */
    gap: 18px;
}

.site-footer__link {
    color: var(--mili-muted);
    text-decoration: none;
}

.site-footer__link:hover,
.site-footer__link:focus-visible {
    color: var(--mili-ink);
    text-decoration: underline;
}

/*
   개인정보보호법 제30조가 처리방침을 "다른 표기와 구분되도록 표시" 하라고 요구한다.
   굵기와 색을 함께 올려 나머지 링크와 확실히 구분한다.
*/
.site-footer__link--strong {
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-bold);
}

.site-footer__divider {
    color: var(--mili-border);
}

.site-footer__copyright {
    margin: 0;
    color: var(--mili-muted);
}

/* ===== 24-1) 관련 스킬 카드 =====
   vod/course-detail.jsp · pbl/project-detail.jsp · 로드맵 미리보기 모달(vod/_course-preview.jsp,
   pbl/_project-preview.jsp) 3개 화면이 공유한다. 예전엔 화면이 2개뿐이라 CLAUDE.md 의 "화면 전용
   CSS 는 뷰 경로 미러링" 원칙에 따라 각 화면 CSS 에 그대로 복제해 뒀지만, 로드맵 미리보기 모달은
   roadmap/detail.jsp·mypage/roadmap-edit.jsp 에 AJAX 조각으로 꽂히는 것이라 그 두 화면 CSS(
   css/roadmap/roadmap.css)를 또 복제하면 유지보수만 어려워진다 — 2개 이상이 쓰는 진짜 공용
   컴포넌트라 여기로 옮긴다(CLAUDE.md "재사용 컴포넌트는 theme/components.css 에만"). */

.course-skills__lead {
    margin: 0 0 1rem;
    color: var(--mili-muted);
    font-size: var(--mili-body-b3-size);
    line-height: 1.6;
}

.course-skills__group + .course-skills__group {
    margin-top: 1.125rem;
    padding-top: 1.125rem;
    border-top: 1px solid var(--mili-border);
}

.course-skills__field {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0 0 0.625rem;
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    letter-spacing: -0.01em;
    color: var(--mili-brand-strong);
}

/* 실측(Figma node 1543:828): 아이콘(레벨색 육각형) + 스킬명을 세로로 쌓은 카드 그리드, 한 줄에 3장. */
.course-skills__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--mili-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 576px) {
    .course-skills__list {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.skill-chip {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.625rem;
    padding: 1.25rem 0.75rem;
    border: 1px solid var(--mili-border);
    /* QA49(8qAqeXw50F8GKDGkeW0Pua, 2026-08-19) — .mypage-panel/.widget-card 와 같은 이유로
       각짐(0)을 카드 토큰으로 옮긴다. */
    border-radius: var(--mili-radius-card);
    background: var(--mili-panel-sunken);
    text-align: center;
}

/* 배지 도형은 §24-1 .skill-badge-icon(common/_skill-badge-icon.jsp)이 그린다 — 역량군별
   실제 모양(육각형/방패/원형)을 쓴다. 이 칩 안에서는 기본 48px 그대로 둔다. */

.skill-chip__title {
    color: var(--mili-ink);
    font-weight: var(--mili-font-weight-semibold);
    font-size: var(--mili-body-b3-size);
}

/* 다크 셸 보정. */
.mili-theme-dark .skill-chip {
    border-color: rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
}

.mili-theme-dark .course-skills__group + .course-skills__group {
    border-top-color: rgba(255, 255, 255, 0.06);
}

/* 라이트 보정 — 실측(Figma 8qAqeXw50F8GKDGkeW0Pua node 4839:195371, "Light/강의/상세/학습전",
   2026-09-22) 결과 기본 --mili-panel-sunken(#F1F3F5)이 아니라 Neutral/50(흰색) + Neutral/400
   테두리다. VOD 상세·PBL 상세·로드맵 미리보기 모달이 이 컴포넌트를 공유하므로 전부 함께 반영된다. */
.mili-light .skill-chip {
    border-color: var(--mili-neutral-400);
    background: var(--mili-neutral-50);
}

/* ===== 25) 로드맵 항목 미리보기 모달 =====
   roadmap/detail.jsp · mypage/roadmap-edit.jsp 2개 화면이 공유하는 컴포넌트라 화면별 CSS
   대신 여기 둔다(CLAUDE.md "재사용 컴포넌트는 theme/components.css 에만"). 모달 셸 자체는
   common/_roadmap-preview-modal.jsp, 여닫기/포커스트랩은 js/common/roadmap-preview-modal.js —
   구조는 css/pbl/missions.css §mission-modal 을 참고했다.

   본문(.course-preview*)은 VOD·PBL 미리보기 조각(vod/_course-preview.jsp, pbl/_project-preview.jsp)
   이 공용으로 쓴다. 표지 아래 섹션(.course-section/.course-panel/.detail-facts/.curriculum/
   .course-skills__ 계열/.skill-chip 계열)은 vod/course-detail.jsp·pbl/project-detail.jsp 와 100% 같은
   기존 클래스를 그대로 재사용하므로 여기 다시 정의하지 않는다 — 이 절에는 모달 셸과, 기존에
   없던 값(미리보기 전용 그라디언트 표지·수강중 배지·커리큘럼 번호 원)만 추가한다. */

.roadmap-preview-modal {
    position: fixed;
    inset: 0;
    z-index: 1060;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.roadmap-preview-modal[hidden] {
    display: none;
}

/* 공유 모달 셸 배경(scrim) 정본 — 2026-09-02 통일 계획(component-allowlist.md). 아래 7개 모달의
   배경이 전부 "position:absolute; inset:0; background:var(--mili-overlay)"로 리터럴까지 동일했다
   (pbl/brief.css·pbl/work.css 에 있던 mission-modal/mentoring-modal/work-done 몫도 여기로 합쳤다 —
   그 파일들엔 이 규칙을 가리키는 주석만 남겼다). 내부 콘텐츠·열기/닫기 JS 는 화면별로 그대로 둔다
   (모달 8종 중 값이 다른 onboarding-modal__dim/popup-modal__dim(74% 리터럴)·
   mili-learning-viewer-scrim(66% 리터럴)은 Figma 미실측이라 합치지 않았다 — 실측 후 판단). */
.roadmap-preview-modal__scrim,
.recommend-modal__scrim,
.mili-login-modal__scrim,
.mili-notice-modal__scrim,
.mission-modal__scrim,
.mentoring-modal__scrim,
.work-done__scrim,
.skill-requirement-modal__scrim,
.profile-photo-modal__scrim {
    position: absolute;
    inset: 0;
    background: var(--mili-overlay);
}

/*
 * "레벨 부족" 팝업(VOD 과정·PBL 프로젝트 상세, 2026-09-18). 기존 8개 모달 중 어느 것도 용도가
 * 맞지 않아(전부 특정 기능 전용) 새로 추가하되, 셸은 위 공유 scrim + mission-modal__panel 과
 * 같은 값을 그대로 따른다 — CSS 중복 없이 새 루트 클래스 하나만 늘어난 것이다
 * (docs/design/component-allowlist.md "새 변형이 필요할 때" 규칙에 따라 사용자 확인 후 추가).
 */
.skill-requirement-modal {
    position: fixed;
    inset: 0;
    z-index: 1060;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.skill-requirement-modal[hidden] {
    display: none;
}

.skill-requirement-modal__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    width: min(28rem, 100%);
    max-height: 100%;
    background: var(--mili-panel);
    border: 1px solid var(--mili-line);
    border-radius: var(--mili-radius-card);
    box-shadow: var(--mili-shadow-lg);
}

.skill-requirement-modal__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--mili-space-3);
    flex: 0 0 auto;
    padding: 1.125rem 1.25rem 0.875rem;
    border-bottom: 1px solid var(--mili-line);
}

.skill-requirement-modal__title {
    margin: 0;
    font-size: var(--mili-body-b1-size);
    font-weight: 800;
    line-height: var(--mili-line-tight);
    color: var(--mili-fg);
    white-space: nowrap;
}

.skill-requirement-modal__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 1rem 1.25rem 1.25rem;
}

.skill-requirement-modal__list {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.skill-requirement-modal__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--mili-line);
    border-radius: var(--mili-radius-sm);
}

.skill-requirement-modal__name {
    font-size: var(--mili-body-b2-size);
    color: var(--mili-fg);
}

.skill-requirement-modal__level {
    flex: 0 0 auto;
    font-size: var(--mili-caption-size);
    color: var(--mili-fg-muted);
    white-space: nowrap;
}

.roadmap-preview-modal__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    width: min(60rem, 100%);
    max-height: min(90vh, 56rem);
    background: #101319;
    border: 1px solid #3d4249;
    /* Figma 실측 16px → 기존 토큰 중 가장 가까운 --mili-radius-card(12px)로 스냅(CLAUDE.md). */
    border-radius: var(--mili-radius-card);
    /* 시안 고유 그림자값 — 기존 --mili-shadow-lg 와 다르다(리터럴 유지, course-detail.css 상단
       주석과 같은 방침: 시안이 지정한 표면값은 토큰화하지 않는다). */
    box-shadow: 3px 4px 8.7px 0 rgba(0, 0, 0, 0.57);
    overflow-y: auto;
}

/* 닫기(X). Figma 시안에는 없지만 접근성·발견성을 위해 추가(2026-08-12 사용자 확인) — 그라디언트
   히어로 위/다크 본문 위 둘 다에서 대비가 나오도록 반투명 검정 배경을 쓴다. */
.roadmap-preview-modal__close {
    position: absolute;
    top: 0.875rem;
    right: 0.875rem;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 0;
    border-radius: var(--mili-radius-badge);
    background: rgba(0, 0, 0, 0.4);
    color: var(--mili-neutral-50);
    font-size: var(--mili-body-b2-size);
    cursor: pointer;
}

.roadmap-preview-modal__close:hover,
.roadmap-preview-modal__close:focus-visible {
    background: rgba(0, 0, 0, 0.6);
}

.roadmap-preview-modal__body {
    display: flex;
    flex-direction: column;
    min-height: 12rem;
}

/* 조각이 도착하기 전(fetch 대기 중) 표시. js 가 텍스트 노드로 넣는다. */
.roadmap-preview-modal__loading {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    padding: 3rem 1.5rem;
    color: var(--mili-fg-muted);
    font-size: 0.9375rem;
}

/* 외부링크 항목 — 원본 콘텐츠가 없어 제목·설명·새 창 버튼만 보여준다(js 가 채운다). */
.roadmap-preview-modal__ext {
    padding: 2rem 1.75rem;
}

.roadmap-preview-modal__ext-title {
    margin: 0 0 0.75rem;
    font-size: 1.375rem;
    font-weight: 800;
    color: var(--mili-fg);
}

.roadmap-preview-modal__ext-desc {
    margin: 0 0 1.5rem;
    color: var(--mili-fg-muted);
    font-size: 0.9375rem;
    line-height: 1.7;
    white-space: pre-line;
}

body.is-modal-open {
    overflow: hidden;
}

@media (max-width: 767.98px) {
    .roadmap-preview-modal {
        padding: 0;
    }

    .roadmap-preview-modal__panel {
        width: 100%;
        max-height: 100%;
        height: 100%;
        border: 0;
        border-radius: 0;
    }
}

/* ----- 25-1) VOD 미리보기 표지 — Figma node 1581:12398 고정 그라디언트 -----
   시안 고유값이라 리터럴로 둔다(course-detail.css 상단 주석과 같은 방침). 태그·제목·설명은
   기존 .course-detail-hero__* 를 그대로 재사용하고, 여기서는 배경·모서리·표지 전용 제목 크기만
   덧씌운다. */
.course-preview__hero {
    position: relative;
    padding: 2.5rem 1.875rem;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--mili-radius-card) var(--mili-radius-card) 0 0;
    background: linear-gradient(126.82deg, rgb(7, 140, 117) 0%, rgb(230, 240, 84) 117.85%);
    color: var(--mili-neutral-50);
}

/* 표지 안 .course-detail-hero__* 는 팔레트 배경(--banner) 대신 이 그라디언트 위에 얹히므로
   반투명 상수값이 그대로 맞는다 — 재정의 불필요. 제목만 미리보기 전용 크기(Figma 실측 34px)로
   좁힌다(기존 30px/48px 반응형 크기 대신). */
.course-preview__hero .course-detail-hero__title {
    font-size: 2.125rem;
}

.course-preview__hero-badge--enrolled {
    display: inline-flex;
    align-items: center;
    gap: var(--mili-space-1);
    padding: 0.5rem 0.75rem;
    background: #62b582;
    color: var(--mili-neutral-100);
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    white-space: nowrap;
}

.course-preview__body {
    padding: 1.875rem;
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
}

/* PBL 미리보기는 기존 .detail-hero(과정 팔레트 배경)를 그대로 쓰므로 모서리만 패널에 맞춘다. */
.course-preview > .detail-hero {
    border-radius: var(--mili-radius-card) var(--mili-radius-card) 0 0;
}

/* ----- 25-2) 커리큘럼 번호 원 — Figma 미리보기 전용 -----
   기존 .curriculum__head 는 번호 원 없이 타입 라벨만 쓴다. 미리보기 모달 안에서만 번호 원을
   보여주고 싶다면 .curriculum__item-seq 를 헤더 마크업에 추가해 쓴다(현재 조각 JSP는 기존
   .curriculum 마크업을 그대로 재사용해 번호 원을 그리지 않는다 — 필요해지면 이 클래스를 쓴다). */
.roadmap-preview-modal .curriculum__item-seq {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    margin-right: var(--mili-space-3);
    border-radius: var(--mili-radius-badge);
    background: #24272b;
    color: var(--mili-neutral-50);
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    flex: 0 0 auto;
}

/* ===== 26) 맞춤 학습 추천 =====
   common/_recommend-section.jsp. 온보딩 진단 결과로 고른 스킬과 그 스킬에 걸린 강의·프로젝트를
   보여준다. 여러 화면(onboarding/intro · assessment/intro · mypage/learning · vod/course-list)이
   공유하므로 화면별 CSS 가 아니라 여기에 둔다(CLAUDE.md "재사용 컴포넌트는 theme/components.css 에만").

   "다음 학습 목표" 칩은 §24-1 의 .course-skills__list / .skill-chip 을 그대로 재사용한다 —
   같은 스킬을 같은 모양으로 보여줘야 과정 상세와 어긋나 보이지 않는다. */

.reco-section {
    margin-top: 2.5rem;
}

.reco-section__head {
    margin-bottom: var(--mili-space-5);
}

/* 제목 크기는 화면별로 다시 정의하지 않는다 — 섹션 제목이라 히어로 토큰(--mili-hero-*)과는 별개다. */
.reco-section__title {
    margin: 0 0 0.375rem;
    font-size: var(--mili-heading-h4-size);
    font-weight: var(--mili-font-weight-bold);
    letter-spacing: -0.01em;
    color: var(--mili-ink);
}

.reco-section__lead {
    margin: 0;
    color: var(--mili-muted);
    font-size: var(--mili-body-b3-size);
    line-height: 1.6;
}

.reco-block + .reco-block {
    margin-top: var(--mili-space-6);
}

.reco-block__title {
    margin: 0 0 0.75rem;
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-brand-strong);
}

.reco-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.625rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 768px) {
    .reco-list {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.reco-item__link {
    display: flex;
    align-items: flex-start;
    gap: var(--mili-space-3);
    height: 100%;
    padding: 0.875rem 1rem;
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    background: var(--mili-panel-sunken);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

.reco-item__link:hover,
.reco-item__link:focus-visible {
    border-color: var(--mili-brand);
    transform: translateY(-1px);
}

/* 콘텐츠 종류 태그 — 공용 .tag.vod/.tag.pbl(theme/figma-classes.css, Figma node 2042:215476
   "Tag" 표준)를 그대로 쓴다(2026-08-31 전환 — 이전엔 이 자리 전용 .reco-item__tag--vod/--pbl 가
   색만 이 스펙에서 이식하고 아이콘 배지·테두리·그림자 등 구조는 여전히 달랐다. 사이드바 추천
   _related-content.jsp 가 이미 쓰는 것과 같은 컴포넌트로 통일). 여기서는 가로 카드
   (.reco-item__link, flex-direction:row) 안에서 눌리지 않게 하는 배치값만 얹는다. */
.reco-item__tag {
    flex: 0 0 auto;
    align-self: flex-start;
}

.reco-item__body {
    display: flex;
    flex-direction: column;
    gap: var(--mili-space-1);
    min-width: 0;
    flex: 1 1 auto;
}

.reco-item__title {
    color: var(--mili-ink);
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-semibold);
    line-height: 1.5;
}

.reco-item__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--mili-space-2);
    color: var(--mili-muted);
    font-size: var(--mili-caption-size);
}

/* 메타 줄의 각 항목(난이도·추천 근거 스킬·이수시간) 사이를 가운뎃점으로 잇는다.
   순서는 항목이 있을 때만 그 자리에 들어오므로 :first-child 로 판단한다. */
.reco-item__meta > *:not(:first-child)::before {
    content: "\00b7\0020";
}

/* 추천 프로젝트의 콘텐츠 난이도(tb_learn_level, ProjectCard.levelNo). */
.reco-item__level-no {
    white-space: nowrap;
}

/* VOD 강의의 난이도 배지 — 강의 목록(vod/course-list.css)의 4단 팔레트와 같은 색으로,
   오른쪽 끝에 붙는다(디자인 QA49 실측). */
.reco-item__level {
    flex: 0 0 auto;
    margin-left: auto;
    align-self: flex-start;
    font-size: var(--mili-caption-size);
    font-weight: var(--mili-font-weight-bold);
    white-space: nowrap;
}

.reco-item__level--basic { color: var(--mili-green-500); }
.reco-item__level--intermediate { color: #72b4ff; }
.reco-item__level--advanced { color: #ff996a; }
.reco-item__level--expert { color: #dd6fff; }

/* 빈 상태(.reco-empty)는 두지 않는다 — 보여줄 카드가 없으면 섹션 자체를 그리지 않는다
   (RecommendModel.of 가 hidden 을 돌려준다, 2026-08-13). */

/* 다크 셸 보정(.skill-chip 과 같은 규칙). 추천 섹션을 쓰는 세 화면 모두 다크다. */
.mili-theme-dark .reco-item__link {
    border-color: rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
}

/* ===== 27) 로그인 필요 확인 팝업 =====
   Figma node 1837:4068("공통 - 로그인 팝업"). 로그인이 필요한 액션(수강신청·찜 등)을 비로그인
   상태에서 시도하면, 즉시 /login 으로 보내는 대신 이 팝업으로 먼저 확인한다 — js/common/mili-ajax.js
   의 handleError() 가 열고 닫는다(화면마다 마크업을 두지 않고 .mili-toast-stack 과 같은 방식으로
   최초 필요 시 JS 가 DOM 에 붙인다). 구조·포커스트랩·ESC 닫힘은 §25 로드맵 미리보기 모달과 같다.

   패널 배경·테두리·아이콘 틴트는 시안 실측값을 그대로 쓴다(§25 모달 패널의 #101319 리터럴과 같은
   방침 — "시안이 지정한 표면값은 토큰화하지 않는다"). 다크·라이트 두 시안 모두 아이콘 틴트가
   브랜드 그린(#B4FF39) 고정값이라 --mili-primary(테마별로 값이 달라짐)로 바꾸지 않는다.
   버튼만 예외 — 취소/로그인 두 버튼은 기존 .btn.neutral/.btn.default(2026-08-26 클래스명 정리,
   2026-08-27 .btn-primary → .btn.default 통합,
   값은 그대로) 실측 근사와
   정확히 같은 값(다크 텍스트 #B6B7B8·라이트 #3E4449 등, 위 QA28 주석 참고)이라 그 두 클래스를
   그대로 재사용한다. 시안의 라이트 모드 로그인 버튼(#9FE970/검정 글자)은 다크 모드가 이미
   --mili-primary/--mili-primary-on 과 정확히 일치하는 것과 달리 우리 라이트 프라이머리 토큰과
   전혀 다른 값이라 버튼 색 불일치 표에 남기고, 시스템 전체가 쓰는 테마별 프라이머리 토큰으로
   통일했다(2026-08-15). */

.mili-login-modal {
    position: fixed;
    inset: 0;
    z-index: 1070;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.mili-login-modal[hidden] {
    display: none;
}

/* 배경(scrim) 규칙은 roadmap-preview-modal__scrim 옆의 공유 정본 참고. */

/* 2026-08-25 Figma "Pop-up" 공통 요소 재실측(8qAqeXw50F8GKDGkeW0Pua, node 2128:78887, 2026-08-25
   사용자 확인) — 패널 배경을 최신 실측값으로 갱신(다크 Neutral/870 = 기존 --mili-surface 토큰과
   정확히 일치, 라이트는 순백). 확인 버튼 색(브랜드 그린)은 2026-08-15 별도 결정 사안이라 이번엔
   건드리지 않는다. */
.mili-login-modal__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--mili-space-5);
    width: min(22.5rem, 100%);
    padding: 1.75rem 2.5rem;
    background: var(--mili-surface);
    border: 1px solid rgba(102, 102, 102, 0.25);
    border-radius: var(--mili-radius-card);
    box-shadow: 0 24px 32px rgba(0, 0, 0, 0.5);
}

.mili-light .mili-login-modal__panel {
    background: var(--mili-neutral-50);
}

/* 아이콘 컨테이너 틴트도 같은 재실측으로 반투명 브랜드색 대신 단색(Primary/900 다크, Primary/50
   라이트)으로 갱신. */
.mili-login-modal__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--mili-radius-badge);
    /* 2026-09-18 재개편 — 새 Primary/900 다크와 정확히 일치해 var() 로 참조. */
    background: var(--mili-primary-900);
    /* 2026-09-08 Figma QA(node 3971:90590) — 아이콘 테두리를 Primary/200으로(2026-09-18 재개편으로
       #DEFFA8 → #C5FEC4). */
    border: 1px solid var(--mili-primary-200);
    color: var(--mili-primary-500);
    font-size: var(--mili-body-b1-size);
    flex: 0 0 auto;
}

.mili-light .mili-login-modal__icon {
    /* 2026-09-18 재개편 — 새 Primary/50 라이트와 정확히 일치해 var() 로 참조. */
    background: var(--mili-primary-50);
}

/* 아이콘 글리프(스트로크) 자체는 다크·라이트가 다른 고정값을 쓴다 — 컨테이너 틴트(위 배경·테두리)와
   달리 시안 SVG 실측이 다크 #B4FF39 / 라이트 #9FE970 로 서로 다르다(2026-08-15 사용자 확인,
   Figma node 1839:4876 벡터 stroke 실측). --mili-primary 는 라이트에서 #618F20 로 훨씬 어두워
   대응되지 않으므로 시안 리터럴을 그대로 쓴다.
   2026-09-18 재개편 — 다크는 새 --mili-primary(#85F686), 라이트는 현재 라이트 브랜드값(Primary/200,
   위 --mili-primary 라이트 오버라이드와 동일 스텝)으로 갱신, 둘 다 스케일과 정확히 일치해 var(). */
.mili-light .mili-login-modal__icon {
    color: var(--mili-primary-200);
}

.mili-login-modal__icon > i::before {
    margin-left: -4px;
}

.mili-login-modal__text {
    margin: 0;
    color: var(--mili-fg);
    font-size: var(--mili-body-b1-size);
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.4;
    text-align: center;
}

.mili-login-modal__actions {
    display: flex;
    gap: var(--mili-space-3);
    width: 100%;
}

.mili-login-modal__actions .btn {
    flex: 1 0 0;
}

@media (max-width: 767.98px) {
    .mili-login-modal__panel {
        padding: 1.75rem 1.5rem;
    }
}

/* ===== 28) 안내 확인 모달 =====
   수강신청·수강취소처럼 결과를 확실히 인지시켜야 하는 액션의 완료·실패 안내(2026-08-17 결정).
   js/common/mili-ajax.js 의 notice() 가 열고 닫는다 — 화면마다 마크업을 두지 않고 최초 필요 시
   JS 가 DOM 에 붙인다(§27 로그인 확인 팝업과 같은 방식).

   패널·스크림·버튼은 §27 과 같은 값을 쓴다(같은 계열의 팝업이라 표면값을 따로 만들지 않는다).
   아이콘 틴트만 결과에 따라 갈린다 — 성공은 §27 과 같은 브랜드 그린 고정값, 실패는 --mili-danger.

   2026-08-26 재동기화 — §27 이 Figma "Pop-up" 재실측(node 2128:78887)으로 패널 배경·아이콘 틴트를
   갱신했는데 여기 §28 은 옛 값 그대로였다(위 "§27 과 같은 값을 쓴다"는 선언이 깨진 상태). §27 이
   이미 반영한 값으로 다시 맞춘다 — danger 변형은 그대로 둔다. */

.mili-notice-modal {
    position: fixed;
    inset: 0;
    z-index: 1075;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.mili-notice-modal[hidden] {
    display: none;
}

/* 배경(scrim) 규칙은 roadmap-preview-modal__scrim 옆의 공유 정본 참고. */

.mili-notice-modal__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--mili-space-5);
    width: min(22.5rem, 100%);
    padding: 1.75rem 2.5rem;
    background: var(--mili-surface);
    border: 1px solid rgba(102, 102, 102, 0.25);
    border-radius: var(--mili-radius-card);
    box-shadow: 0 24px 32px rgba(0, 0, 0, 0.5);
}

.mili-light .mili-notice-modal__panel {
    background: var(--mili-neutral-50);
}

.mili-notice-modal__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--mili-radius-badge);
    /* 2026-09-18 재개편 — 새 Primary/900 다크와 정확히 일치해 var() 로 참조. */
    background: var(--mili-primary-900);
    /* 2026-09-08 Figma QA(node 3971:90590) — 아이콘 테두리를 Primary/200으로(2026-09-18 재개편으로
       #DEFFA8 → #C5FEC4). */
    border: 1px solid var(--mili-primary-200);
    color: var(--mili-primary-500);
    font-size: var(--mili-body-b1-size);
    flex: 0 0 auto;
}

.mili-light .mili-notice-modal__icon {
    /* 2026-09-18 재개편 — 배경·글리프 전부 새 스케일과 정확히 일치해(Primary/50·Primary/200,
       로그인 팝업 아이콘과 같은 방침) var() 로 참조. */
    background: var(--mili-primary-50);
    color: var(--mili-primary-200);
}

/* 실패는 그린이 아니다 — 결과를 색으로도 구분한다. */
.mili-notice-modal__panel--danger .mili-notice-modal__icon {
    background: rgba(220, 53, 69, 0.12);
    border-color: rgba(220, 53, 69, 0.3);
    color: var(--mili-danger);
}

.mili-notice-modal__text {
    margin: 0;
    color: var(--mili-fg);
    font-size: var(--mili-body-b1-size);
    font-weight: var(--mili-font-weight-bold);
    line-height: 1.4;
    text-align: center;
    /* 서버 문구는 한 줄이 아닐 수 있다(수강취소 안내 등). */
    white-space: pre-line;
}

.mili-notice-modal__actions {
    display: flex;
    gap: var(--mili-space-3);
    width: 100%;
}

.mili-notice-modal__actions .btn {
    flex: 1 0 0;
}

.mili-notice-modal__actions .btn[hidden] {
    display: none;
}

@media (max-width: 767.98px) {
    .mili-notice-modal__panel {
        padding: 1.75rem 1.5rem;
    }
}

/* ============================================================================
   §29 순번 배지 (.step-seq)

   "순서가 있는 학습 단위" 목록의 번호를 그리는 공용 컴포넌트다. 세 화면이 같은 성격의
   목록인데 각자 다른 배지를 갖고 있어 2026-08-17 일관성 점검(V-01·T-11)에서 지적됐다:
     - 강의 상세 커리큘럼(.curriculum__seq)      — 회색 사각 배지, 2rem
     - 프로젝트 상세 미션 여정(.mission-track__seq) — 브랜드 틴트 pill, 1.75rem
     - 로드맵 상세 학습 단계(.roadmap-step__seq)   — 배지가 아예 없고 "STEP 1" 글자

   발주처 지정대로 미션 여정(브랜드 틴트 pill)을 기준으로 삼아 셋을 하나로 맞춘다.
   화면별 CSS 는 이 클래스를 함께 걸고 위치·간격만 각자 보정한다.
   ============================================================================ */
.step-seq {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--mili-radius-pill);
    background: var(--mili-primary-bg);
    color: var(--mili-primary);
    font-weight: var(--mili-font-weight-bold);
    font-size: 0.875rem;
    line-height: 1;
}

/* ===== 30) 목록 상단 추천 콘텐츠 버튼 + 모달 =====
   강의 목록(vod/course-list.jsp)·프로젝트 목록(pbl/project-list.jsp) 상단 건수 라벨 옆에 붙는
   "추천 콘텐츠" 버튼(매직스틱)과, 클릭 시 뜨는 모달. 카드마다 두지 않고 목록 화면 하나에 딱
   하나뿐이다 — 로그인한 사용자의 맞춤 학습 추천(RecommendationService)에서 이 목록에 맞는
   타입(강의/프로젝트)만 최대 3개 보여준다(2026-08-21 사용자 확정, 개별 콘텐츠의 "같은 스킬
   추천"과는 다른 기능). 두 목록이 공유하는 컴포넌트라 화면별 CSS 대신 여기 둔다
   (CLAUDE.md "재사용 컴포넌트는 theme/components.css 에만").
   모달 셸은 common/_recommend-modal.jsp, 여닫기/포커스트랩은 js/common/recommend-modal.js —
   구조는 §25 로드맵 미리보기 모달을 참고했다(패널만 더 작다). 다만 색은 §25 처럼 리터럴로
   고정하지 않는다 — 로드맵 모달은 항상 다크인 화면(VOD·PBL 상세)에서만 뜨지만, 이 모달이 뜨는
   강의·프로젝트 "목록" 화면은 우측 상단 테마 토글로 라이트 전환이 된다(app-shell.js). 2026-08-21
   사용자 지적: 패널을 리터럴 다크(#101319)로 고정해 라이트 모드에서 검은 박스가 튀었다 —
   --mili-surface/--mili-border/--mili-fg 등 라이트/다크 양쪽을 다 가진 토큰만 쓴다.

   본문 카드는 2026-08-21엔 .detail-related__*(강의/PBL 상세 사이드바 "추천 콘텐츠")를, 2026-08-27엔
   전용 클래스 .reco-modal-card__*(가로 카드)를 새로 만들어 썼으나, 2026-09-14 사용자 요청("추천
   강의 카드 모양을 실제 목록과 똑같이")으로 vod/_course-recommend-picks.jsp·pbl/_project-recommend-picks.jsp
   가 목록(/courses·/pbl)의 정본 카드 common/_list-card.jsp 를 그대로 재사용하도록 바뀌었다 —
   .catalog-card/.pbl-card·.catalog-grid/.pbl-grid(각 화면 CSS)가 그대로 적용되고, 이 카드 전용
   클래스는 더 없다. 패널 폭 28rem→40rem 변경(아래 .recommend-modal__panel)은 그대로 유지한다
   (목록 카드가 3열까지 자연스럽게 줄어들 수 있는 폭). */
.catalog-count-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mili-space-3);
}

/* 2026-08-27 재실측(node 2859:97887, VOD 목록 레벨필터 줄) — 자리도 "전체 N개" 줄이 아니라
   레벨필터/정렬 드롭다운 줄, 정렬 드롭다운 바로 왼쪽으로 옮겼다(호출측 course-list.css
   .catalog-level-filter__actions 참고). 배색은 브랜드 라임 아웃라인이 아니라 뉴트럴 흑백반전
   (.catalog-cta 와 같은 방침, 2026-08-26 사용자 확인된 사이트 전역 Primary CTA 규칙과 합류) —
   border-radius 는 실측치(4.555px)와 가장 가까운 --mili-radius-button, 문구도 "추천 콘텐츠"에서
   "추천 콘텐츠 보기"로 바뀌었다. */
.recommend-trigger-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    padding: 8px 12px;
    border: 1px solid var(--mili-neutral-300);
    border-radius: var(--mili-radius-button);
    background: var(--mili-neutral-50);
    color: var(--mili-neutral-900);
    font-size: 0.75rem;
    font-weight: var(--mili-font-weight-semibold);
    white-space: nowrap;
    cursor: pointer;
}

.mili-light .recommend-trigger-btn {
    background: var(--mili-neutral-950);
    border-color: var(--mili-neutral-850);
    color: var(--mili-neutral-50);
}

.recommend-trigger-btn svg {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
}

/* PBL 목록(pbl/project-list.jsp)은 이번 라운드에서 위치·아이콘·문구를 옮기지 않아 여전히
   Bootstrap 아이콘(<i class="bi bi-magic">)을 쓴다 — 배색만 이 공용 규칙을 함께 물려받으므로
   크기 규칙은 남겨 둔다. */
.recommend-trigger-btn i {
    font-size: 1rem;
}

.recommend-trigger-btn:hover,
.recommend-trigger-btn:focus-visible {
    background: var(--mili-neutral-300);
    border-color: var(--mili-neutral-300);
    color: var(--mili-neutral-950);
}

.mili-light .recommend-trigger-btn:hover,
.mili-light .recommend-trigger-btn:focus-visible {
    background: var(--mili-neutral-850);
    border-color: var(--mili-neutral-850);
    color: var(--mili-neutral-50);
}

.recommend-modal {
    position: fixed;
    inset: 0;
    z-index: 1060;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.recommend-modal[hidden] {
    display: none;
}

/* 배경(scrim) 규칙은 roadmap-preview-modal__scrim 옆의 공유 정본 참고. */

.recommend-modal__panel {
    position: relative;
    width: min(40rem, 100%);
    max-height: min(80vh, 40rem);
    padding: 1.75rem 1.5rem 1.5rem;
    background: var(--mili-surface);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    box-shadow: var(--mili-shadow-lg);
    overflow-y: auto;
}

.recommend-modal__close {
    position: absolute;
    top: 0.875rem;
    right: 0.875rem;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 0;
    border-radius: var(--mili-radius-badge);
    background: rgba(var(--mili-fg-rgb), 0.08);
    color: var(--mili-fg);
    font-size: var(--mili-body-b2-size);
    cursor: pointer;
}

.recommend-modal__close:hover,
.recommend-modal__close:focus-visible {
    background: rgba(var(--mili-fg-rgb), 0.16);
}

.recommend-modal__title {
    display: flex;
    align-items: center;
    gap: var(--mili-space-2);
    margin: 0 0 1rem;
    font-size: var(--mili-body-b2-size);
    font-weight: var(--mili-font-weight-bold);
    color: var(--mili-fg);
}

/* 2026-09-14 PC 영향 재검사 — PC 시안(node 4038:312669/312787)엔 타이틀 앞 wand-sparkles 아이콘이
   있고, 모바일 QA(node 4060:220219/225357)만 삭제를 요구했다. 마크업은 되살리고 모바일에서만
   숨긴다(_recommend-modal.jsp 참고).
   2026-09-22 재실측(node I4038:312680;98:418821 SVG 는 stroke="white" — 브랜드색 고정이 아니라
   타이틀 텍스트와 같은 색을 그대로 따라간다) — 이 아이콘만 브랜드 그린으로 강제하던 색을 걷어내고
   .recommend-modal__title(color: var(--mili-fg))을 상속하게 한다(사용자 확인). */
.recommend-modal__title-icon {
    flex: 0 0 auto;
    color: currentColor;
}

@media (max-width: 767.98px) {
    .recommend-modal__title-icon {
        display: none;
    }
}

.recommend-modal__body {
    display: flex;
    flex-direction: column;
    min-height: 6rem;
}

/* 조각이 도착하기 전(fetch 대기 중) 표시. js 가 텍스트 노드로 넣는다. */
.recommend-modal__loading {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    padding: 2rem 1rem;
    color: var(--mili-fg-muted);
    font-size: 0.9375rem;
}

.recommend-modal__empty {
    margin: 0;
    padding: 1.5rem 0.25rem;
    color: var(--mili-fg-muted);
    font-size: 0.875rem;
    line-height: 1.6;
}

/* 2026-09-14 사용자 요청으로 본문 카드가 목록 정본 common/_list-card.jsp(.catalog-card/.pbl-card,
   .catalog-grid/.pbl-grid)를 그대로 재사용하게 되어, 이 카드 전용이던 .reco-modal-list/
   .reco-modal-card__* 규칙(2026-08-27/28 도입)은 모두 걷어냈었다.
   2026-09-22 재실측(node 4038:312680/4038:312683, get_design_context) — 실제 PC 시안은 목록 카드
   (세로형, 다열 그리드)가 아니라 1열로 세로 스택되는 전용 가로형 행 카드였다(사용자 확인 후
   되살림). 강의 목록(vod/_course-recommend-picks.jsp)만 이 아래 규칙으로 되돌아갔다 — 프로젝트
   목록(pbl/_project-recommend-picks.jsp)은 이번 범위 밖이라 .catalog-grid 재사용 그대로 둔다.
   루트명 reco-modal-card 는 docs/design/component-allowlist.md 에 이미 등록돼 있던 이름을
   그대로 재사용했다(새 컴포넌트 추가가 아니라 기존에 승인된 루트의 부활). */
.reco-modal-list {
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* 카드 전체가 배경·테두리를 가진 하나의 링크다(node I4038:312680;98:418825 "Button" —
   bg Neutral/870, border 1px Neutral/870, radius 12px, padding 13px). 리터럴 표면값 대신
   같은 톤의 다크·라이트 겸용 토큰(.reco-item__link 와 같은 방식, 위 §26 참고)을 쓴다. */
.reco-modal-card__link {
    display: flex;
    gap: var(--mili-space-3);
    padding: var(--mili-space-3);
    border: 1px solid var(--mili-border);
    border-radius: var(--mili-radius-card);
    background: var(--mili-panel-sunken);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

.reco-modal-card__link:hover,
.reco-modal-card__link:focus-visible {
    border-color: var(--mili-brand);
    transform: translateY(-1px);
}

.mili-theme-dark .reco-modal-card__link {
    border-color: rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
}

/* 썸네일 212×119(node 실측) — 목록 카드(.catalog-card__media, 219px 전체폭)와 달리 고정 크기
   작은 매체다. 배경 이미지는 같은 40장 세트(course-card__thumb--vod-N, 위 §1)를 그대로 쓴다. */
.reco-modal-card__media {
    position: relative;
    flex: 0 0 auto;
    width: 212px;
    height: 119px;
    border-radius: var(--mili-radius-control);
    background: var(--mili-neutral-900) var(--card-bg, none) no-repeat center / cover;
    overflow: hidden;
}

.reco-modal-card__media-photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.reco-modal-card__body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--mili-space-2);
    min-width: 0;
    flex: 1 1 auto;
}

.reco-modal-card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

/* 제목 색은 목록 카드(.catalog-card__title)와 같은 토큰(--mili-fg)을 그대로 쓴다(사용자 요청 —
   "텍스트 컬러는 목록에 쓰인 컬러 그대로"). 크기·굵기(Body/B1/Semibold, 18px·600)는 화면 CSS 에
   다시 적지 않고 공용 타이포그래피 카탈로그 .b1_s(theme/figma-classes.css)가 담당한다. */
.reco-modal-card__title {
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    color: var(--mili-fg);
}

/* 메타 줄(제공기관·카테고리·이수시간)은 목록 카드에 정확히 대응하는 한 줄짜리 요소가 없어
   (목록은 정의목록 행별로 나뉜다) 사이트 전역에서 이 자리에 쓰는 공용 톤(--mili-fg-muted —
   .reco-item__meta/.detail-related__meta 와 동일)을 그대로 따른다. 크기(Body/B3/Medium,
   14px·500)는 .b3_m 이 담당한다. */
.reco-modal-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    margin: 0;
    color: var(--mili-fg-muted);
}

.reco-modal-card__meta > *:not(:first-child)::before {
    content: "\00b7\0020";
}

@media (max-width: 480px) {
    .reco-modal-card__link {
        gap: var(--mili-space-2);
    }

    .reco-modal-card__media {
        width: 96px;
        height: 72px;
    }
}

@media (max-width: 480px) {
    .recommend-modal {
        padding: 0;
        align-items: flex-end;
    }

    .recommend-modal__panel {
        width: 100%;
        max-height: 80vh;
        border-radius: var(--mili-radius-shell) var(--mili-radius-shell) 0 0;
    }
}

/* ===================================================================
   폼 검증 실패 강조 (.is-invalid)
   -------------------------------------------------------------------
   서버가 폼을 되돌려줄 때 문제가 된 칸을 붉게 짚어 준다. 항목별 문구(.text-danger)만으로는
   화면이 긴 폼(회원가입·마이페이지 프로필)에서 어디가 걸렸는지 눈에 잘 안 들어온다.

   Bootstrap 에도 .is-invalid 가 있지만 border-color 를 우리 다크 오버라이드
   (.mili-theme-dark .form-control / .mili-style-refresh .form-control — 둘 다 특정성이 같고
   bootstrap.min.css 보다 뒤에 실린다)가 덮어써서 실제로는 테두리가 안 변한다.
   그래서 같은 특정성으로 이 파일 맨 뒤에서 다시 못박는다 — 앞으로 옮기면 그 규칙들에 다시 진다.

   소속·계급은 전송값이 hidden(소속)/스크립트가 채우는 select(계급)라 입력칸에 직접 클래스를
   못 붙인다. 바깥 .mili-selector 에 붙여서 구분 select·검색칸까지 함께 물들인다
   (_mili-selector.jsp 의 spring:bind).

   글로 색을 넣지 않고 테두리만 바꾸는 이유: 입력값 자체는 사용자가 방금 친 정상 텍스트다.
   =================================================================== */

.form-control.is-invalid,
.form-select.is-invalid,
.mili-selector.is-invalid :is(.form-select, .mili-selector__input-wrap) {
    border-color: var(--mili-error);
}

.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
.mili-selector.is-invalid .form-select:focus,
.mili-selector.is-invalid .mili-selector__input-wrap:focus-within {
    border-color: var(--mili-error);
    /* 알파 파생값(--mili-error-rgb)이 없어 --mili-error-bg(테마별 은은한 배경색)로 링을 만든다.
       color-mix() 는 정부망 구형 브라우저에서 색이 통째로 빠져 쓰지 않는다(reference.css 주석). */
    box-shadow: 0 0 0 0.2rem var(--mili-error-bg);
}

/* ===================================================================
   프로모션 배너 캐러셀 (.mili-promo-carousel*) — 강의·PBL·로드맵 목록 공용
   -------------------------------------------------------------------
   마크업은 common/_promo-carousel.jsp 하나다(vod/course-list.jsp · pbl/project-list.jsp ·
   roadmap/list.jsp 가 각각 include, `promoActive` 요청 속성으로 자기 슬라이드만 기본 활성화한다).
   2026-08-31 — 원래는 화면마다 정적 배너 1장(catalog-/pbl-/roadmap-promo-banner)씩 따로
   갖고 있었는데(각각 Figma 8qAqeXw50F8GKDGkeW0Pua 2723:86915·2723:88913·2769:97973 다크
   실측), 사용자 확정으로 세 화면이 점 3개 캐러셀 하나를 공유해 서로를 교차 홍보하게 됐다
   (강의 목록 모바일 시안 node 2804:24930/24941 이 캐러셀 틀의 유일한 실측 근거이고, PBL·로드맵
   슬라이드는 그 화면들의 기존 문구·이미지를 그대로 옮겨 썼다 — 새 배너용 이미지 생성은 범위 밖).
   데스크톱은 인디케이터를 숨기고 Bootstrap 캐러셀이 스와이프·클릭 없이는 안 넘어가므로, 각
   화면에서 사실상 자기 슬라이드만 보이는 예전 정적 배너와 같게 보인다.
   자동재생(data-bs-ride="carousel" + data-bs-interval="false")은 이 프로젝트의 Bootstrap
   5.3.8 번들에서 문자열 "false" 가 boolean 으로 정규화되지 않아 setInterval 에 NaN 이 들어가며
   아주 짧은 간격으로 계속 넘어가는 사고가 있었다 — js/common/promo-carousel.js 가 JS 로 직접
   interval:false 를 넘겨 초기화한다(마크업에는 data-bs-ride/data-bs-interval 을 두지 않는다). */
.mili-promo-carousel {
    position: relative;
    margin: 2rem 0 5.5rem;
}

/* 콘텐츠 부족으로 임시 숨김 */
#sitePromoCarousel { display: none; }

/* 2026-08-27 사용자 지적 반영(강의 목록에서 처음 잡은 값, PBL·로드맵도 같은 시안 계열이라 공유) —
   두 가지를 고쳤다.
   1) align-items:center 로 세로 중앙 정렬했더니 카피 블록 높이가 늘어날 때(좁은 폭에서 줄바꿈)
      위쪽 padding-top(65px)은 그대로 지켜지고 아래쪽만 눌리는 비대칭이 났다 — Figma 실측
      (node 2723:86919: 배너 270px 안에서 카피 블록이 y=65부터 h=138, 즉 아래 여백은
      270-65-138=67px)은 애초에 "중앙 정렬"이 아니라 위 65px·아래 67px 고정 여백의 상단 정렬
      구조였다. align-items:flex-start + 명시적 padding-bottom 으로 정확한 값을 고정한다.
   2) background-position:center 는 폭이 좁아질수록 cover 크롭이 좌우 대칭으로 파고들어
      우측의 일러스트까지 잘라냈다 — 일러스트가 있는 우측을 기준점으로 고정해 크롭이 왼쪽
      (빈 배경)에서만 일어나게 한다. */
.mili-promo-banner {
    position: relative;
    display: flex;
    align-items: flex-start;
    min-height: 270px;
    padding: 65px 60px 67px;
    border-radius: var(--mili-radius-card);
    overflow: hidden;
    background-color: var(--mili-neutral-950);
    background-repeat: no-repeat;
    background-position: right center;
    background-size: cover;
}

.mili-promo-banner--vod { background-image: url("../../images/vod/course-promo-dark.png"); }
.mili-light .mili-promo-banner--vod { background-image: url("../../images/vod/course-promo-light.png"); }

.mili-promo-banner--pbl { background-image: url("../../images/pbl/project-promo-dark.png"); }
.mili-light .mili-promo-banner--pbl { background-image: url("../../images/pbl/project-promo-light.png"); }

.mili-promo-banner--roadmap { background-image: url("../../images/roadmap/roadmap-promo-dark.png"); }
.mili-light .mili-promo-banner--roadmap { background-image: url("../../images/roadmap/roadmap-promo-light.png"); }

/* 2026-08-28 실사용 데이터로 재확인 — 실 DB 문구가 시안 실측 당시 데모 문구보다 길어 3줄로
   접히는 경우가 있다. 고정 height 대신 min-height + 상단 정렬로 둬야 내용이 길어도 위쪽이
   잘리지 않는다(짧은 문구에서는 기존과 동일하게 270px/180px 근처로 보인다). */
.mili-promo-banner__copy {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2.25rem;
    max-width: 630px;
}

/* 제목·설명은 시안에서 한 문단(줄바꿈만, 사이 간격 없음)이라 여기에는 gap 을 주지 않는다 —
   __copy 의 gap 은 이 블록과 버튼 사이에만 적용된다. */
.mili-promo-banner__text {
    display: flex;
    flex-direction: column;
}

.mili-promo-banner__title,
.mili-promo-banner__desc {
    margin: 0;
    font-size: 1.5rem;
    line-height: 1.4;
    color: var(--mili-neutral-100);
}

.mili-promo-banner__title {
    font-weight: var(--mili-font-weight-bold);
}

.mili-promo-banner__desc {
    font-weight: var(--mili-font-weight-regular);
}

.mili-light .mili-promo-banner__title,
.mili-light .mili-promo-banner__desc {
    color: var(--mili-neutral-900);
}

/* CTA — 사이트 전역 Primary CTA 뉴트럴 흑백 반전 규칙(2026-08-26 사용자 확인)을 그대로 쓴다.
   강의·PBL 목록이 각자 갖고 있던 .catalog-cta/.pbl-cta 를 이 배너가 공용 컴포넌트가 되면서
   여기로 합쳤다(로드맵 목록은 원래 버튼이 없던 화면이라 이번에 처음 생긴다 — AI 판단, 재확인 필요). */
.mili-promo-banner__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.375rem 0.75rem;
    border: 1px solid var(--mili-neutral-300);
    border-radius: var(--mili-radius-button);
    background: var(--mili-neutral-50);
    color: var(--mili-neutral-950);
    font-size: var(--mili-body-b3-size);
    font-weight: var(--mili-font-weight-medium);
    text-decoration: none;
    text-align: center;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
    cursor: pointer;
}

.mili-light .mili-promo-banner__cta {
    background: var(--mili-neutral-950);
    border-color: var(--mili-neutral-850);
    color: var(--mili-neutral-50);
}

.mili-light .mili-promo-banner__cta:hover,
.mili-light .mili-promo-banner__cta:focus {
    background: var(--mili-neutral-850);
    border-color: var(--mili-neutral-850);
    color: var(--mili-neutral-50);
}

/* 모바일(≤767px) — Figma 모바일 시안 실측(node 2804:24930/24941, 강의 목록 한 화면에만 있고
   PBL·로드맵은 이 값을 그대로 공유한다). 미디어쿼리는 반드시 위 무조건부 규칙 "뒤"에 둬야
   이긴다(같은 특정성이면 소스 순서가 늦은 쪽이 이긴다). */
@media (max-width: 767px) {
    /* 2026-08-31 사용자 확정 — 좌우 마진을 없애고 뷰포트 끝까지 풀사이즈로 띄운다(node
       2804:24930 실측: 배너 프레임 폭이 390 = 기기 폭과 정확히 같다). 본문 열(.app-page)은
       padding 이 아니라 폭(min(100% - 여백*2, ...))으로 좌우 여백을 만들므로(§8 위), 그
       여백만큼(--mili-page-padding) 음수 마진으로 상쇄해야 진짜 뷰포트 끝까지 번진다. 모서리도
       실측 스크린샷에서 각짐(반경 없음)이라 --mili-radius-card 를 0으로 되돌린다. */
    .mili-promo-carousel {
        margin-top: 12px;
        margin-bottom: 32px;
        margin-inline: calc(var(--mili-page-padding) * -1);
    }

    .mili-promo-banner {
        min-height: 180px;
        height: auto;
        align-items: flex-start;
        padding: 32px 96px 32px 29px;
        border-radius: 0;
    }

    .mili-promo-banner__copy {
        gap: 0.75rem;
        max-width: 200px;
    }

    .mili-promo-banner__title {
        font-size: 16px;
    }

    .mili-promo-banner__desc {
        font-size: 14px;
    }

    /* CTA 실측(배너 버튼): 높이 28px, radius 5px, 12px SemiBold — 색은 화면별
       .catalog-cta/.pbl-cta/.roadmap-cta 가 이미 반전(다크 흰 배경/라이트 검정 배경) 처리해
       두므로 여기서는 치수만 좁힌다. */
    .mili-promo-banner__cta {
        height: 28px;
        padding: 0 0.75rem;
        border-radius: 5px;
        font-size: 12px;
        font-weight: var(--mili-font-weight-semibold);
    }
}

/* ===== 배너 캐러셀 인디케이터(점 3개) — node 2804:24941 실측(모바일) =====
   2026-08-31 사용자 확정 — 롤링(캐러셀 전환)을 PC 에서도 쓸 수 있어야 해서, 인디케이터를
   데스크톱에서 숨기던 규칙을 걷어내고 전 폭에서 공통으로 켠다. 데스크톱 전용 인디케이터 시안은
   없어(모바일 노드 2804:24941 뿐) 같은 작은 원 스타일을 그대로 확대 적용한다 — AI 판단,
   재확인 필요. Bootstrap 기본 인디케이터는 배너 위에 겹치는 얇은 막대(absolute, bottom)인데,
   시안은 배너 "아래" 문서 흐름에 놓이는 작은 원 3개다. position:static 으로 되돌리고 지름
   5.82px 원으로 다시 그린다(색은 이미지 위 오버레이용 고정 리터럴이 아니라 배너 바깥 페이지
   배경 위에 있으므로 테마를 따라가는 --mili-fg-rgb 를 쓴다). */
.mili-promo-carousel__indicators {
    position: static;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 5.82px;
    margin: 8px 0 0;
}

.mili-promo-carousel__indicators [data-bs-target] {
    flex: 0 0 auto;
    width: 5.82px;
    height: 5.82px;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: rgba(var(--mili-fg-rgb), 0.4);
    opacity: 1;
    text-indent: 0;
    cursor: pointer;
}

/* 활성 점 색은 시안 실측(캐러셀 인디케이터가 하나의 평면 SVG 로 내보내져 개별 fill 값을
   읽을 수 없었다)이 아니라 통상적인 캐러셀 관례(활성=불투명 전경색)를 따른 AI 판단이다 —
   재확인 필요. */
.mili-promo-carousel__indicators [data-bs-target].active {
    background-color: var(--mili-fg);
}

/* 마이페이지 "내가 쓴 글"에서 #comment-N 앵커로 바로 들어왔을 때 그 댓글/답글 위치를 알려주는
   플래시 효과. 콘텐츠 댓글(common/_content-comments.jsp)과 갤러리 답글(showcase/detail.jsp)이
   공유한다(js/common/comment-actions.js, js/community/gallery-detail.js). */
.mili-anchor-highlight {
    animation: mili-anchor-flash 2s ease-out;
}

@keyframes mili-anchor-flash {
    0%, 40% {
        background-color: var(--mili-primary-bg);
    }
    100% {
        background-color: transparent;
    }
}

/* ===================================================================
   28) 보상(XP/레벨업) 오버레이 라임 리터럴 갱신 — 2026-09-18 Figma Color 노드 재개편
   -------------------------------------------------------------------
   theme/reference.css 의 .mili-reward-* 는 참고 저장소 1:1 이식본이라 손대지 않는 관행이다
   (reference.css 상단 주석 참고). 그 파일의 리터럴이 전부 옛 라임(#B4FF39 계열, 참고 저장소
   자체 팔레트라 우리 Figma 실측값과 완전히 같지는 않다)이라 여기서 같은 selector 로 덮어쓴다.
   각 값은 참고 리터럴이 옛 스케일의 어느 스텝에 가장 가까웠는지 역산해 같은 스텝의 새 값으로
   옮긴 근사치다 — 스케일과 정확히 일치하는 것은 var(--mili-primary-NNN)으로 참조했다. 다크의
   rgb(133 246 134 / …)는 --mili-brand-rgb(다크 500)와 값이 같아 기존 rgba(var(--mili-brand-rgb),
   alpha) 관례(sidebar-hover-bg 라이트 등)를 그대로 따른다. 라이트 box-shadow(14 107 85)는
   Primary/800 라이트와 값은 같지만 그 스텝 전용 -rgb 짝 토큰이 없어(신규 토큰 신설은 별도 확인
   필요) 리터럴로 남긴다. */
.mili-reward-scan {
    background: linear-gradient(90deg, transparent 0%, rgba(var(--mili-brand-rgb), 0.07) 48%, transparent 52%);
}

.mili-reward-modal {
    box-shadow: 0 0 36px rgba(var(--mili-brand-rgb), 0.16);
}

.mili-reward-item p {
    color: var(--mili-primary-500);
}

.mili-reward-item small {
    background: var(--mili-primary-500);
}

.mili-reward-xp-display {
    background: linear-gradient(to bottom, var(--mili-neutral-900) 0%, var(--mili-primary-800) 100%);
    -webkit-background-clip: text;
    background-clip: text;
}

.mili-reward-confirm {
    background: var(--mili-primary-500);
}

.mili-reward-confirm:hover,
.mili-reward-confirm:focus-visible {
    background: var(--mili-primary-400);
}

.mili-light .mili-reward-modal {
    box-shadow: 0 0 36px rgb(14 107 85 / 14%);
}

.mili-light .mili-reward-item p {
    color: var(--mili-primary-800);
}

.mili-light .mili-reward-xp-display {
    background-image: linear-gradient(to bottom, #f7f8f4 0%, var(--mili-primary-700) 100%);
}
