/* ══════════════════════════════════════════════════════════════════════════
   셸 기하 = **단일 소스** (2026-08-14 신설)
   ──────────────────────────────────────────────────────────────────────────
   ⛔ 왜 만들었나 — 같은 값이 **세 곳에 따로** 선언돼 있었다.
        · app.css        `--stripw/--sh-row/--sh-gap/--sh-pad/--sh-head-gap` (MES·CRM·관리자)
        · login.css      `--hs-stripw/--hs-row/--hs-gap/--hs-pad/--hs-head-gap` (홈, 이름까지 다름)
        · portal_skin.css `--stripw/--sh-row/…` **재선언** (/dash/, "app.css 실측 원값을 옮겨 적음")
      값이 우연히 같아도 선언이 셋이면 **한 곳을 고치는 순간 갈라진다.** 실제로 갈라졌다:
      app.css 는 머리 칸을 44 → `--topbar`(48) 로 올렸는데 옮겨 적은 두 곳은 44 로 남아
      폰에서 첫 아이콘이 **54 / 58 / 58** 로 셋으로 찢어졌다(실측).
   ★ 그래서 값을 **여기 한 곳에서만** 선언하고, 세 파일은 이 이름을 읽기만 한다.
     값을 바꾸려면 이 파일을 고친다 → 다섯 셸이 **같이** 움직인다.

   ⚠⚠ **싣는 순서는 문서 종류에 따라 다르다. 아래가 사실이다** (2026-08-14 정정 — 종전
      머리말은 "항상 가장 마지막" 이라고 적어 놓고 바로 다음 줄에서 "/dash/ 는 토큰이 먼저"
      라고 반대를 말하고 있었다. 실제 주입은 **토큰이 먼저**다):

        · `<link>` 문서(홈·MES·CRM·관리자·데이터·인허가)
            → 이 파일이 app.css·login.css **뒤**에 온다. 순서로 이긴다.
        · `/dash/`  (문서 한 장 + 인라인 주입, deploy_portal._skin_one)
            → **토큰이 먼저**, portal_skin.css 가 뒤다. 그래야 스킨의 `var(--sh-*)` 가
              풀린다(뒤에 두면 값이 비어 띠·레일 기하가 통째로 무너진다).
              _skin_one 의 게이트가 이 순서를 확인한다.

      ★ 그래서 아래 규칙들은 **순서에 기대지 않는다.** 폰 정합 블록은
        `:root[data-shell="v2"]` + `[data-app="dash"]` 로 특이도를 미리 올려 두었다
        (0,3,0~0,4,0) — 스킨이 뒤에 와도 이긴다. 순서는 `<link>` 문서에서만 뜻이 있다.
   ══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── 셸 골격 ── */
  --stripw:64px;        /* 모듈 띠 폭 */
  --railw:216px;        /* 메뉴 레일 폭 */

  /* ── 한 줄·간격 ── */
  --sh-row:44px;        /* 한 줄(아이콘 칸 · 모듈명 칸) 높이 */
  --sh-gap:2px;         /* 줄 사이 */
  --sh-pad:8px;         /* 띠·레일 공통 여백 */
  --sh-head-gap:6px;    /* 머리 줄 ↔ 첫 항목 사이 */

  /* ★ 머리 칸(띠의 날개 · 레일의 워드마크) 높이 = 상단바와 **같은 값**.
     셋이 같아야 날개·워드마크·탭의 세로 중심이 모두 한 줄에 온다(app.css 3306절의 결론).
     ⛔ 이 값을 --sh-row(44)로 되돌리지 말 것 — 그 순간 머리 줄이 상단바보다 4px 낮아지고
       첫 아이콘이 첫 메뉴명보다 내려앉는다(이번에 고친 그 어긋남이다). */
  --sh-head:48px;
  --topbar:48px;

  /* ★ 폰 상단바 **둘째 줄**(열린 화면 탭) 높이. 폰 상단바 = --topbar + --sh-tabrow = 80px.
     ⛔ 이 값을 두 곳에 적지 말 것 — app.css(≤1024, MES·CRM·데이터·인허가)와
       아래 /dash/ 블록(≤980)이 **같은 이 토큰**을 읽는다. 브레이크포인트만 다르고
       높이는 하나다. 종전에는 app.css 안에 32 가 박혀 있어 /dash/ 가 따라올 근거가 없었다. */
  --sh-tabrow:32px;

  /* ── 안전영역(노치·홈 인디케이터) ────────────────────────────────────
     ⚠ `viewport-fit=cover` 를 켠 문서에서만 0 이 아닌 값이 들어온다.
       메타를 안 켜면 전부 0 → 아래 calc() 는 **기존값 그대로**가 된다(무변경 보장).
     ⚠ 그래서 메타와 이 토큰은 **한 쌍**이다. 메타만 켜고 보정을 안 넣으면 콘텐츠가
       상태바 밑으로 들어간다(과거 회귀의 원인). 보정만 넣고 메타를 안 켜면 아무 일도
       안 일어난다. 둘 다 이번 변경에 들어 있다. */
  --safe-t:env(safe-area-inset-top, 0px);
  --safe-b:env(safe-area-inset-bottom, 0px);
  --safe-l:env(safe-area-inset-left, 0px);
  --safe-r:env(safe-area-inset-right, 0px);
}

/* ── 홈(login.css)이 쓰는 `--hs-*` 는 위 값의 **별칭**이다 ──────────────────
   ★ 홈 CSS 의 사용처를 하나도 안 고치고 단일 소스에 붙이는 방법이다
     (login.css 안에서 `var(--hs-row)` 를 쓰는 자리가 수십 곳이라 이름을 바꾸면 그 전부가
      바뀐다 → 변경 면적이 커지고 되돌리기 어려워진다).
   ⚠ login.css 의 원래 `--hs-*` **정의 5줄은 지웠다.** 여기 별칭이 그 자리를 잇는다.
   ⚠ `--hs-col-pad`·`--hs-block`·`--hs-row-m` 은 홈 **고유** 값이라 login.css 에 그대로 둔다
     (셸 기하가 아니라 홈 화면의 단 배치 값이다). */
:root{
  --hs-stripw:var(--stripw);
  --hs-row:var(--sh-row);
  --hs-gap:var(--sh-gap);
  --hs-pad:var(--sh-pad);
  --hs-head-gap:var(--sh-head-gap);
}


/* ══════════════════════════════════════════════════════════════════════════
   폰 정합 — 다섯 셸의 **첫 아이콘 top = 첫 메뉴명 top** 을 한 식으로 못 박는다
   ──────────────────────────────────────────────────────────────────────────
   사용자 지적(5차 반려): "좌측 아이콘 스트립의 첫 아이콘과 우측 메뉴 서랍의 첫 메뉴명이
   메뉴마다 제각각이다. 대시보드는 아이콘이 너무 위에 붙어 있다."

   ★ 두 줄의 식으로 환원된다 — 둘 다 여기서만 정의한다:
       띠   첫 아이콘 top = safe-t + 0(위 여백) + --sh-head(48) + (--sh-head-gap − --sh-gap)(4) + --sh-gap(2)
       레일 첫 항목 top = safe-t + 0(위 여백) + --sh-head(48) + --sh-head-gap(6)
     → 둘 다 **safe-t + 54**. 다섯 셸이 같은 선택자를 공유하므로 갈라질 자리가 없다.
     (띠 쪽이 gap 을 미리 빼는 이유: flex `gap` 이 로고 칸 **뒤에도** 들어가기 때문이다.
      레일에는 gap 이 없어 --sh-head-gap 을 그대로 쓴다.)

   ⚠⚠ **데스크톱은 건드리지 않는다.** 전부 max-width 미디어쿼리 안이다.
   ⚠⚠ 브레이크포인트가 앱마다 다르다 — /dash/ 는 **980px**, 나머지는 **1024px** 이다.
      통일하면 981~1024 구간에서 /dash/ 가 갑자기 드로어로 바뀐다(그 폭 사용자에게는
      데스크톱 레이아웃이 사라지는 변경이다) → **이번에는 통일하지 않고** 각자의 값을
      그대로 쓴다. 통일 여부는 별도 결정 사항으로 보고했다.
      그래서 `[data-app="dash"]` 로 /dash/ 만 갈라 놓는다.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ① 앱 셸(MES · CRM · 관리자 · 홈) — ≤1024px ─────────────────────────── */
@media (max-width:1024px){

  /* ★★ 서랍·배경이 **위아래로 꽉 차게** 한다 (사용자 지적: "메뉴를 접고 업무화면에서도
     위아래 화이트 배경이 꽉 차있지 않은 경우가 있음").
     ⛔ `height:100%` 는 주소창이 접혔다 펴지며 **변하는 높이를 안 따라간다** → 아래가 남는다.
     ⚠ app.css 는 이미 `100dvh` 로 고쳤지만(2026-08-14 다른 세션), **login.css(홈)와
       portal_skin.css(/dash/)에는 없었다** — 그 둘만 잘려 보인 이유다(실측: login.css 49행이
       `height:100%` 단독). 값이 세 곳에 흩어져 또 갈라진 것이라, 고치는 자리도 여기가 맞다.
     ⚠ `100%` 를 먼저 두고 `100dvh` 를 뒤에 둔다 — dvh 를 모르는 구형 브라우저는 앞 값을 쓴다.
     ⚠ 데스크톱에는 넣지 않는다(이 블록 안이다). 데스크톱은 주소창이 없어 어차피 같은 값이고,
       "데스크톱 무변경"을 값으로 보장하려면 아예 안 건드리는 쪽이 확실하다. */
  html,body{height:100%;height:100dvh}

  /* 안전영역: 본문이 상태바·좌우 노치 밑으로 들어가지 않게.
     ⚠ 이 셋의 기존 padding 은 **전부 0 이다**(실측: MES·CRM·관리자·대시보드 .main pt/pl/pr=0)
       → safe 가 0 인 환경에서는 값이 그대로 0 이라 **무변경이 보장된다.**
       기존값이 0 이 아닌 자리에는 이 문법을 쓰지 않았다(아래 띠 하단 참조). */
  :root[data-shell="v2"]:not([data-app="dash"]) .main{
    padding-top:var(--safe-t);
    padding-left:var(--safe-l);
    padding-right:var(--safe-r);
  }

  /* 띠 — 위는 안전영역만, 아래는 **기존 8px + 홈 인디케이터**.
     ⚠ 아래는 기존값이 8px 이라 `calc(--sh-pad + --safe-b)` 로 더한다(덮어쓰면 8 이 사라진다). */
  :root[data-shell="v2"]:not([data-app="dash"]) .mstrip,
  :root[data-shell="v2"] body.hubon .hstrip{
    padding-top:var(--safe-t);
    padding-bottom:calc(var(--sh-pad) + var(--safe-b));
    padding-left:var(--safe-l);
  }

  /* 레일 — 위 여백 0 + 안전영역.
     ⚠ 레일 **아래**는 건드리지 않는다: 앱마다 기존값이 다르고(0 / 32px) 스크롤 영역이라
       덮어쓰면 마지막 항목 여백이 사라진다. 하단 안전영역은 띠가 대표로 흡수한다. */
  :root[data-shell="v2"]:not([data-app="dash"]) .rail,
  :root[data-shell="v2"] body.hubon .hrail,
  :root[data-shell="v2"] body.hubon.rail-collapsed .hrail{
    padding-top:var(--safe-t);
  }

  /* 머리 칸 = 띠의 날개 / 레일의 워드마크 — **같은 높이·같은 아래 간격** */
  :root[data-shell="v2"]:not([data-app="dash"]) .mstrip-logo,
  :root[data-shell="v2"] body.hubon .hstrip-logo{
    height:var(--sh-head);
    margin-bottom:calc(var(--sh-head-gap) - var(--sh-gap));
  }
  :root[data-shell="v2"]:not([data-app="dash"]) .railbrand,
  :root[data-shell="v2"] body.hubon .hbrand{
    height:var(--sh-head);
    margin-bottom:var(--sh-head-gap);
    padding-top:0;
  }
  /* 스크롤 영역이 위 여백을 또 얹으면 첫 항목만 내려앉는다 */
  :root[data-shell="v2"]:not([data-app="dash"]) .rail-scroll{ padding-top:0 }

  /* ★★★ 첫 줄은 **중심**이 맞아야 한다 — top 만 맞추면 눈에는 어긋나 보인다.
     ⛔⛔ 내가 1차에서 틀린 지점이다. top 을 54 로 통일해 놓고 "다섯 값 일치"라고 보고했는데,
       사용자가 보는 것은 **줄이 나란한가**(중심)다. 두 요소는 높이가 다르다:
         띠 첫 아이콘 = 44px(터치 타깃)  →  중심 54 + 22 = 76
         레일 첫 줄(그룹 제목 h3) = 32px →  중심 54 + 16 = 70
       → top 은 같은데 **중심이 6px 어긋난다**(실배포본 실측: MES·CRM·관리자 6px, /dash/ 2px).
       실기기에서 "아이콘 줄과 메뉴명 줄이 안 맞는다"고 지적된 것이 정확히 이 6px 이다.
     ★ 그래서 **첫 줄의 행 높이를 --sh-row 로 맞춘다.** 그러면 중심이 저절로 같아진다
       (54 + 22 = 76, 양쪽 동일). 값을 새로 정하지 않고 이미 있는 토큰을 쓴다 —
       app.css 의 `--sh-row` 주석이 원래 "아이콘 칸·모듈명 칸 높이"라고 선언했는데
       정작 `h3` 에는 적용돼 있지 않았다(32px 하드코딩). 그 의도를 완성하는 것이다.
     ⚠ **첫 줄에만** 건다(`:first-child`). 목록 전체를 44 로 키우면 레일이 길어지고
       스크롤이 늘어난다 — 맞춰야 하는 것은 띠와 마주 보는 첫 줄뿐이다.
     ⚠ `height` 로 덮는다 — app.css 502행이 `height:32px` 고정이라 min-height 로는 진다.
     ⚠⚠ **`:first-child`·`:first-of-type` 은 쓸 수 없다** (실측으로 걸렀다):
        · MES·CRM·/dash/ 는 레일 안 **첫 자식이 `.rail-pill`**(활성 알약)이다 → `:first-child` 가
          알약을 잡아 규칙이 통째로 빗나간다(관리자만 우연히 맞았다 — 거기엔 알약이 없다).
        · `.rail-grp:first-of-type` 도 안 된다 — `:first-of-type` 은 **태그** 기준이라
          `DIV.rail-pill` 이 이미 '첫 DIV' 를 차지한다.
        · /dash/ 는 알약·워드마크·닫기버튼이 더 끼어 있어 인접 결합자(`+`)도 못 쓴다.
     ★ 그래서 **"앞에 다른 .rail-grp 이 없는 .rail-grp"** 으로 첫 그룹을 잡는다.
       사이에 무엇이 몇 개 끼어 있든 동작한다 = 세 구조를 한 선택자로 덮는다.
     ⚠ 첫 그룹에 헤딩이 없는 경우(/dash/ 의 `.rail-grp.solo`)는 첫 항목(`.nav`)이 곧 첫 줄이다
       → 둘 다 적는다. 헤딩이 있으면 `.nav:first-child` 는 매치되지 않으므로 서로 간섭하지 않는다. */
  :root[data-shell="v2"]:not([data-app="dash"]) .rail-grp:not(.rail-grp ~ .rail-grp) > h3:first-child,
  :root[data-shell="v2"]:not([data-app="dash"]) .rail-grp:not(.rail-grp ~ .rail-grp) > .nav:first-child{
    height:var(--sh-row);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   열린 화면 탭 칩 — 다섯 셸이 **같은 정의 하나**를 읽는다 (2026-08-14 이동)
   ──────────────────────────────────────────────────────────────────────────
   ⛔ 무엇이 문제였나 (사용자 지적): "/dash/ 만 **선택되지 않은 탭에 테두리가 없다.**
      글자와 ✕ 만 떠 있어 칩으로 안 보인다."
   ★★ 원인 = 이 네 줄이 `app.css` 에 있었고 그 앱은 그 파일을 **싣지 않는다.**
      (그 문서는 대시보드 산출물 + 스킨 인라인 구조라 app.css 가 금지돼 있다 —
       deploy_portal 이 실려 있으면 배포를 중단시킨다.)
      실측으로 확인했다: MES·CRM 은 `.vtab` 에 border 를 주는 규칙이 **4개 전부 app.css** 인데,
      /dash/ 는 산출물 어댑터의 `.topbar .vtab`(테두리 투명) + 스킨의 `.vtab.on` 뿐이라
      **비선택 상태에 색을 주는 규칙이 하나도 없었다.**
   ★ 그래서 app.css 에서 **옮겨 왔다**(복사가 아니다 — 그쪽에는 옮겼다는 주석만 남겼다).
     이 파일은 여덟 문서가 모두 읽는 유일한 파일이라 정의가 하나로 유지된다.
   ⚠ 특이도 확인(실측): `:root[data-shell="v2"] .vtab`(0,3,0)이 산출물 `.topbar .vtab`(0,2,0)을
     이기고, `.vtab.on`(0,4,0)이 스킨 `body .topbar .vtab.on`(0,3,1)을 이긴다.
   ⚠ 값은 옮기면서 한 글자도 바꾸지 않았다 → MES·CRM·관리자 데스크톱은 **무변경**이다.
   ⛔ `box-shadow:inset 0 2px 0` 표시선을 되살리지 말 것 — 사용자가 지목한 "위쪽 라인"이다.
      활성은 면·윤곽·굵기로만 말한다.
   ══════════════════════════════════════════════════════════════════════════ */
:root[data-shell="v2"] .vtab{background:transparent;border-color:var(--shell-line)}
:root[data-shell="v2"] .vtab:hover{background:var(--shell-hover);color:var(--shell-txt-hi)}
:root[data-shell="v2"] .vtab.on{
  background:var(--shell-active);color:var(--shell-active-ink);font-weight:600;
  border-color:var(--v2-pill-line);box-shadow:none
}


/* ══════════════════════════════════════════════════════════════════════════
   노치 보정 **소유표** — 한 요소에 두 곳이 보정하지 않는다 (2026-08-14 확정)
   ──────────────────────────────────────────────────────────────────────────
   ⚠ /dash/ 의 실제 문서는 **대시보드 산출물**이고, 그 문서는 자기 `env(safe-area-inset-*)`
     보정을 13곳 갖고 있다(별도 저장소 빌드물이라 여기서 고쳐도 다음 빌드가 되살린다).
     그래서 요소마다 소유자를 하나로 못 박는다.

     요소            소유자    근거
     ─────────────  ───────  ─────────────────────────────────────────────
     .topbar        포털      MES 와 같은 여백(0)으로 통일해야 한다. 산출물의
                              `calc(13px + env(top))` 은 이 규칙이 통째로 덮는다.
     #side / .side  포털      띠(.mstrip)와 **같은 값**을 써야 첫 줄이 맞는다.
                              산출물의 `calc(14px + env(top))` 을 쓰면 14px 어긋난다.
     .mstrip        포털      포털이 만든 요소다 — 산출물에는 존재하지 않는다.
     .main          포털      본문 상단. 산출물에 같은 보정이 없다.
     그 밖(.modal·.ptr-i 등)  산출물   포털이 손대지 않는다.

   ⛔⛔ **좌표로 이중을 판별하지 말 것.** 산출물은 CSS 변수가 아니라 날것의 `env()` 를 쓰는데,
      데스크톱 렌더러에서 그 값은 0 이고 **변수 주입으로 덮이지 않는다** → 내 층만 움직여
      "한 겹"으로 보인다. 2026-08-14 이 함정 때문에 이중을 두 번 놓쳤다.
      ★ 판별은 **선언을 세는 것**이다(CSSOM 순회 + 요소별 매칭).
      ★ 재현이 필요하면 배포본의 `env(...)` 를 **상수로 치환**한다. 폴백만 바꾸면 안 된다 —
        크로미움은 env() 를 지원하고 0 을 주므로 폴백을 아예 쓰지 않는다(실측 확인).
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ② /dash/ — ≤980px (이 앱의 드로어 임계값) ──────────────────────────
   ⛔ 여기 있는 값들은 `@media (min-width:981px)` 안의 zoom 보정판과 **짝**이다.
     그쪽(portal_skin.css 6-2절)은 `.app{zoom:.9}` 좌표계라 48.15px·9.97px 같은 보정값을
     쓰는데, **폰은 zoom:1 이라 보정이 필요 없다** → 여기서는 토큰을 그대로 쓴다.
   ★★ 이번 어긋남의 직접 원인이 바로 그 짝이 **한쪽만 있었다**는 것이다:
      정렬 보정이 `min-width:981px` 안에만 있어서 폰에서는 통째로 풀렸고,
      기본값(위 여백 8 + 로고 44)으로 되돌아가 첫 아이콘이 58 로 내려앉았다(실측). */
@media (max-width:980px){
  :root[data-shell="v2"][data-app="dash"] .main{
    padding-top:var(--safe-t);
    padding-left:var(--safe-l);
    padding-right:var(--safe-r);
  }
  :root[data-shell="v2"][data-app="dash"] .mstrip{
    padding-top:var(--safe-t);
    padding-bottom:calc(var(--sh-pad) + var(--safe-b));
    padding-left:var(--safe-l);
  }
  :root[data-shell="v2"][data-app="dash"] .mstrip-logo{
    height:var(--sh-head);
    margin-bottom:calc(var(--sh-head-gap) - var(--sh-gap));
  }
  /* 레일 = 산출물의 `#side`(id 라 특이도가 높다 — 여기도 id 로 받는다) */
  :root[data-shell="v2"][data-app="dash"] #side{
    padding-top:var(--safe-t);
  }
  /* 워드마크 = `.sidebrand`. 데스크톱에서는 이 줄의 48px 이 수평 구조선을 지고 있어
     높이를 못 건드리지만(portal_skin 6-2 머리말), **폰에는 그 선이 없다** → 안전하다.

     ⛔⛔ `padding-bottom:0` 을 지우지 말 것 (2026-08-14 실측 · 어긋남 11px).
       portal_skin.css 의 `.sidebrand{padding:10px var(--s2) 22px}` 는 위·아래 여백을 **둘 다**
       갖는데 종전에는 위(10)만 걷었다. 이 요소는 `box-sizing:border-box` 라
       높이 48 − 아래 22 = **내용 상자 26** 이 되고, `align-items:center` 가 그 26 의 한가운데,
       즉 y=13 에 워드마크를 놓았다. 띠의 날개는 y=24 다 → 폰에서 로고 락업이 11px 어긋났다.
       아래까지 0 으로 걷으면 내용 상자가 48 로 돌아와 중심이 24 로 맞는다.
     ⚠ 같은 형태가 다른 셸에도 있는지 확인했다(2026-08-14 실배포본 실측):
       `.railbrand`(MES·CRM·관리자·데이터·인허가)는 `padding:0 14px`,
       `.hbrand`(홈)는 `padding:0 8px` 로 **세로 여백이 애초에 0** 이다
       → 다섯 셸의 워드마크 중심이 전부 24 로 확인됐다. 처방이 갈릴 자리가 없다.
       ⛔ 앞으로 저 두 선택자에 세로 padding 을 넣으면 여기와 **같은 증상**이 난다 —
         넣을 일이 생기면 이 블록에 `padding-bottom:0` 형제 규칙을 함께 추가할 것. */
  :root[data-shell="v2"][data-app="dash"] .sidebrand{
    height:var(--sh-head);
    min-height:var(--sh-head);
    padding-top:0;
    padding-bottom:0;
    /* ★ 첫 줄 **중심** 맞춤은 줄 높이가 아니라 **여기 아래 여백**이 흡수한다
       (2026-08-14, 사용자 지적: "폰 레일 첫 항목만 44px, 나머지 40px 이라 한 줄만 튄다").
       ⛔ 종전 처방 = 첫 `.nav` 에 `height:var(--sh-row)`(44). 중심은 맞았지만 그 줄만
         4px 크고 눌렀을 때 알약도 그만큼 커져 목록에서 튀었다 — 눈에 보이는 부작용이다.
       ★ 지금 = **줄 높이는 전부 40 그대로 두고** 머리 칸 아래 여백에서 차이를 흡수한다.
         띠 첫 아이콘 중심 = 48(머리) + 4(gap 보정) + 2(gap) + 44/2 = 76
         레일 첫 줄 중심   = 48(머리) + 아래여백 + 40/2
         → 아래여백 = 6(--sh-head-gap) + (44 − 40)/2 = 8 이면 76 으로 같아진다.
       ⚠ 40 은 이 앱 폰 레일의 항목 높이다(산출물 `.nav{padding:11px 13px}` + --fs-nav 로
         정해지는 값이라 토큰이 없다) → 아래 `--sh-nav-h` 에 **실측값**으로 적어 둔다.
         레일 글자 크기·여백을 바꾸면 이 값도 같이 재서 고칠 것(안 고치면 2px 어긋난다). */
    --sh-nav-h:40px;
    margin-bottom:calc(var(--sh-head-gap) + (var(--sh-row) - var(--sh-nav-h)) / 2);
  }

  /* ★★ 상단바의 **순수 여백**을 걷는다 (2026-08-14 · 실기기 지적: "손익 현황 상단 여백이
     다른 메뉴와 확연히 다르다").
     ⛔ 이중 safe-area 가 아니었다 — 실측으로 배제했다(safe 44 주입 시 다섯 다 `top:44`,
       정확히 한 겹). 진짜 원인은 이 상단바만 **순수 여백 36px** 을 더 갖고 있던 것이다:
         padding 13(위)+13(아래) = 26  ·  row-gap 10  →  높이 130.5 (MES 는 80)
       그래서 첫 조작 요소(☰)가 13px 내려앉고 본문 시작도 그만큼 밀렸다.
     ★ MES 는 같은 자리를 `padding:0 · row-gap:0` 으로 쓴다(app.css ≤1024 블록) — 그 규칙이
       `.topbar` 도 선택자에 넣고 있지만 이 앱은 app.css 를 **싣지 않아** 닿지 못했다.
     ⛔⛔ 위 줄에 원래 `**` + `/dash/` 를 붙여 썼다가 **주석이 거기서 끊겼다.**
        볼드 표시 `**` 뒤에 경로 `/` 가 오면 마지막 두 글자가 `*` `/` = **주석 종료 기호**가 된다.
        이 파일은 강조 `**…**` 와 경로 `/dash/` 를 둘 다 자주 쓰므로 **언제든 재발한다.**
        → 주석 안에서 `**` 바로 뒤에 `/` 를 두지 말 것(앞에 한 칸 띄우거나 백틱으로 감쌀 것).
       → 같은 값을 여기서 준다. 새 수치를 정한 것이 아니라 **MES 값을 그대로** 쓴다.
     ⚠ 아래 여백을 0 으로 둬도 본문이 붙지 않는다 — `.content` 가 이미 padding-top:20px 을
       갖고 있다(실측).

     ★★★ **높이도 MES 와 같은 80px 으로 맞춘다** (2026-08-14 · 사용자 4회째 지적:
       "실적 대시보드 아직도 여백 많고 그냥 다른 메뉴랑 똑같이 만들라니까?").
       ⛔ 종전 이 자리에는 "높이 자체는 맞추지 않는다 — 남는 14.5px 은 여백이 아니라
         내용(제목 28.5 + 기준일 18)이다" 라고 적혀 있었다. 사실 관계는 맞았지만 **결론이
         틀렸다.** 다른 메뉴도 그 내용을 갖고 있고, 다만 **크롬에 두지 않을 뿐**이다 —
         MES 는 화면 제목을 상단바가 아니라 작업면 첫 줄(`.p-head`)에 둔다.
         즉 문제는 "내용이 많다"가 아니라 **내용을 크롬에 얹었다**는 구조 차이였다.
       ★ 그래서 구조를 MES 와 같게 만든다:
           1줄(48) = ☰ · 도구 · 계정          ← 그대로
           2줄(32) = **열린 화면 탭**          ← 아래에서 되살린다(app.css ⑥과 같은 형태)
           제목·기준일(.tb-t) = **작업면 첫 줄로 이동**  ← shell_dash.js ⑨ 가 옮긴다
         → 48 + 32 = 80. MES·CRM·데이터·인허가와 **같은 값·같은 구성**이다.
       ⚠ 「데이터 기준 마감 · 출고 · 단위」 정보는 **사라지지 않는다.** 본문 최상단에서
         제목과 한 줄로 나란히 보이고, 스크롤하면 다른 메뉴의 화면 제목과 똑같이 올라간다.
         (크롬에 붙박여 있어야 할 이유가 없는 정보다 — 화면을 옮기면 함께 바뀌는 값이라
          '지금 무엇을 보고 있나'의 일부이지 '어디에 있나'가 아니다.)
       ⚠ `height:auto` 를 함께 준다 — 산출물이 고정 높이를 쓰면 둘째 줄이 넘쳐 잘린다.
       ⚠ `row-gap:0` 은 위에서 이미 준다. `gap` 은 **행 간격에도** 걸려 두 줄 사이가
         벌어진다(그러면 80 이 아니라 90 이 된다).
       ⚠ shell_dash.js 가 죽어 `.tb-t` 가 상단바에 남으면 셋째 줄이 되어 높이가 다시
         늘어난다 — **화면은 뜨고 기능도 산다**(조용한 실패가 아니라 눈에 보이는 회귀다).
         data-app 표지 자체는 이제 문서 앞 인라인이 세우므로 이 블록은 그와 무관하게 산다. */
  :root[data-shell="v2"][data-app="dash"] .topbar{
    padding-top:0;
    padding-bottom:0;
    row-gap:0;
    flex-wrap:wrap;
    height:auto;
    min-height:calc(var(--topbar) + var(--sh-tabrow));
  }

  /* ★★ 열린 화면 탭 줄 **부활** — 다른 네 앱과 같은 방식으로 **접어서 살린다**.
     ⛔ 종전에는 /dash/ 만 폰에서 탭이 **통째로 사라졌다.** 산출물이
       `@media(max-width:1024px){ .topbar .vtabs{display:none} }` 로 감추는데,
       다른 앱은 app.css ⑥(≤1024)이 그 자리를 **둘째 줄로 접어** 살려 두는 반면
       이 앱은 app.css 를 싣지 않아 감춘 규칙만 남았다.
       저장소 규율("폰에서는 접되 없애지 않는다")을 어기는 자리였고, 탭 칩 테두리 같은
       손질도 /dash/ 폰에서는 칠할 대상이 아예 없었다.
     ⚠ 특이도(실측): 산출물 `.topbar .vtabs`(0,2,0) < 여기(0,4,0) → 순서와 무관하게 이긴다.
     ⚠ 값은 app.css ⑥과 **같다.** 높이만 `--sh-tabrow` 토큰으로 묶어 한 곳에서 움직인다.
     ⚠ 981~1024 구간은 건드리지 않는다 — 이 앱은 그 폭이 아직 **데스크톱 레이아웃**이고
       (드로어 임계값 980), 산출물의 감춤이 그대로 남는다. portal_skin.css 의 그 구간
       처방(`.spacer{flex:1 1 auto}`)도 그래서 그대로 유효하다. */
  :root[data-shell="v2"][data-app="dash"] .topbar .vtabs{
    display:flex;
    order:9;
    flex:1 0 100%;
    min-width:0;
    height:var(--sh-tabrow);
    border-top:1px solid var(--shell-line);
    /* 스와이프 어포던스 — 탭이 넘칠 때 가느다란 손잡이를 보여 준다(app.css ⑥과 같은 값) */
    scrollbar-width:thin;
    scrollbar-color:var(--shell-2) transparent;
  }
  :root[data-shell="v2"][data-app="dash"] .topbar .vtabs::-webkit-scrollbar{
    display:block; height:3px;
  }
  :root[data-shell="v2"][data-app="dash"] .topbar .vtabs::-webkit-scrollbar-track{
    background:transparent;
  }
  :root[data-shell="v2"][data-app="dash"] .topbar .vtabs::-webkit-scrollbar-thumb{
    background:var(--shell-2); border-radius:var(--r1);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   ④ 타이포·모션·초점 공통 토큰 — 2026-08-20 신설 (Apple HIG 정합)
   ──────────────────────────────────────────────────────────────────────────
   왜 이 파일인가: 이 파일은 이미 **모든 문서가 링크**하고 배포 게이트(gate_shell_tokens)가
   그 링크를 강제한다. 새 CSS 를 만들면 링크 8곳과 게이트를 함께 늘려야 하고, 한 곳만 빠져도
   그 화면만 토큰이 미정의가 되는 '조용한 실패'가 또 생긴다.

   무엇이 문제였나 (진단: talent/디자이너/articles/portal-hig-audit-2608.md)
     · app.css 에서 font-size 를 토큰으로 쓴 곳 **0회 / 직접 값 169회**.
       화면별 고유 크기가 최대 29종까지 벌어져 위계가 아니라 잡음이 됐다.
     · 폰 폭에서도 데스크톱 크기(12~13px)가 그대로 내려간다.
       HIG 기본 크기는 macOS 13pt / iOS 17pt 로 **맥락마다 다르다** — 한 벌로 두 맥락을 못 덮는다.
     · :focus-visible 과 prefers-reduced-motion 이 앱마다 0~24 로 들쭉날쭉했다.

   왜 두 층인가 (원시 + 역할)
     · 원시(--fs-13 처럼 값이 이름인 것)는 `20_토큰_매핑스펙_v1.md` 가 정한 규율이다.
       "이름이 값을 감추면 아무도 안 쓴다"는 그때의 진단은 지금도 유효하므로 그대로 둔다.
     · 그러나 값 이름 토큰은 **폭에 따라 값을 바꿀 수 없다**(--fs-13 이 15px 이 되면 거짓말이다).
       그래서 역할 이름(--t-body)을 한 겹 얹고, 폰에서는 역할이 **다른 원시를 가리키게** 한다.

   ⚠ 이 블록은 **선언만 한다.** 기존 화면의 픽셀은 하나도 바뀌지 않는다.
     화면이 직접값을 이 토큰으로 갈아타는 일은 화면별로 폭 검증을 거쳐 다음 단계에서 한다.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  /* ── 원시 타입 스케일 ─────────────────────────────────────────────────────
     기준 8단은 `20_토큰_매핑스펙_v1.md` §1 과 같은 값이다(10·11·12·13·15·18·24·32).
     16·20 두 단은 2026-08-20 에 늘렸다 — 폰에서 한 단 올릴 때 15→18 은 너무 크게 뛴다.
     ★ 14·22·28 은 2026-08-23 에 늘렸다. 없어서 늘린 것이 아니라 **셸이 이미 쓰고 있는데
       스케일에 빠져 있었다**: app.css 실측 14px 18자리(문서 기본 크기가 여기다) ·
       22px 1자리 · 28px 2자리. 스케일에 없는 값이 화면에 있으면 그 자리는 영영 토큰으로
       못 옮긴다(옮기는 순간 픽셀이 바뀌므로) → 먼저 사실을 스케일에 반영한다. */
  --fs-10:10px;   /* 마이크로 표식 전용(1~2글자 칩·첨자). 문장 금지 */
  --fs-11:11px;   /* 표 헤더·라벨·메타 */
  --fs-12:12px;   /* 표 본문 */
  --fs-13:13px;   /* 폼 입력값·본문 */
  --fs-14:14px;   /* (신규) 문서 기본 · 패널 제목 · 내비 — 셸의 실제 기본 크기 */
  --fs-15:15px;   /* KRDS body-small — 「업무 등급」의 본문 */
  --fs-16:16px;   /* 폰 본문 승격용 · 폰 입력칸(iOS 확대 방지) */
  --fs-18:18px;   /* 화면 제목 */
  --fs-20:20px;   /* 폰 제목 승격용 */
  --fs-22:22px;   /* (신규) 폰에서의 지표값 */
  --fs-24:24px;   /* 지표값(소) */
  --fs-28:28px;   /* (신규) 지표값(대) · 히어로 제목 */
  --fs-32:32px;   /* 지표값(특대) */

  /* ── 역할 토큰 ────────────────────────────────────────────────────────────
     화면 코드는 **이 이름만** 쓴다. 새 역할이 필요하면 여기 한 줄을 늘리고,
     화면에는 값을 박지 않는다.

     ★ 2026-08-23 재정의 — 종전 값은 **아무 화면도 쓰지 않는 상태**(app.css 채택 0회)
       였고, 그 사이 셸의 실제 크기와 어긋나 있었다(예: --t-lead 15 인데 셸의 패널
       제목은 전부 14). 채택률 0 이므로 되돌릴 픽셀이 없다 → 지금 **실측에 맞춰**
       고쳐 두고 화면을 여기로 갈아탄다. 이 순서를 뒤집으면(값을 안 맞추고 갈아타면)
       180자리가 한꺼번에 1~2px 씩 움직여 무엇이 회귀인지 못 가린다.

     ★ 아래는 **현행 셸 값(compact)** 이다 — 갈아타도 픽셀이 하나도 안 바뀐다.
       KRDS 「업무 등급」 값은 이 블록 다음의 `[data-density="krds"]` 에 있다. */
  /* ⛔ 2026-09-10 — 이 두 줄이 **하한 12px 위반의 뿌리**였다. 화면을 다시 전수로 재 보니
     10px 258곳 · 11px 146곳이었고, 개별 화면이 잘못 쓴 게 아니라 **토큰이 하한 밑**이라
     그 토큰을 쓰는 모든 자리가 한꺼번에 걸린 것이었다(CRM 칸반 245곳 · RA 간트 · 데이터
     건수 · 라이브러리 캡션 …). 화면마다 고치면 400곳이고 새로 쓰는 자리에서 또 난다.
     하한 근거는 우리 검사기(design_audit.py FS_FLOOR)에 이미 적혀 있다 — KRDS
     body-xsmall 하한이고, 09-03·09-04 실제 조치도 전부 11 → 12 였다.
     ⚠ 이름은 남긴다(참조 수백 곳). micro·caption·small 이 같은 12px 이 되므로
       **12px 밑 단계는 이제 없다**는 뜻이다 — 더 작게 만들고 싶으면 크기가 아니라
       색(--ink-3)·굵기로 물러나게 한다. */
  --t-micro:var(--fs-12);      /* 칩·첨자 — 09-10 하한 12px */
  --t-caption:var(--fs-12);    /* 표 머리·라벨·메타 — 09-10 하한 12px */
  --t-small:var(--fs-12);      /* 표 본문 */
  --t-body:var(--fs-13);       /* 컨트롤·입력값·버튼 라벨 */
  --t-lead:var(--fs-14);       /* 문서 기본 · 패널 제목 · 내비 */
  --t-mid:var(--fs-15);        /* 판 머리 제목 · KRDS body-small(업무 등급 본문) */
  --t-strong:var(--fs-16);     /* 강조 본문 · 모듈명 · 아이콘 글리프 */
  --t-title:var(--fs-18);      /* 화면 제목 */
  --t-display:var(--fs-20);    /* 안내 화면 제목 */
  --t-metric:var(--fs-24);     /* 지표값(소) */
  --t-metric-lg:var(--fs-28);  /* 지표값(대) */
  --t-metric-xl:var(--fs-32);  /* 지표값(특대) */

  /* 두께 — 웹 서체(Pretendard)가 가진 3단만 쓴다. HIG 도 두께는 4단 이하를 권한다.
     light·thin 계열은 쓰지 않는다(작은 글자에서 사라진다).
     ⚠ KRDS 표준형은 **400/700 두 종**이고 우리 결정도 그것이다
       (`talent/디자이너/articles/krds-viol-adaptation.md` §5 결정 3).
       그런데 셸에는 600 이 49자리 박혀 있었다 → 먼저 **이 토큰으로 옮기고**,
       옮긴 뒤에 여기 한 줄(600 → 700)로 두께 정책을 바꾼다. 순서를 뒤집으면
       한 줄을 고쳐도 화면이 안 바뀐다(직접값이 이긴다).
     ⚠ 500 은 쓰지 않는다 — 서브셋에 500 웨이트가 없어 브라우저가 400 으로 떨어뜨린다
       (실측: 셸에 1자리 있었고 렌더는 400 이었다). 그 자리는 --fw-regular 로 옮겼다. */
  --fw-regular:400;
  /* ★ 2026-08-23 사용자 결정 — **400/700 두 종**(KRDS 표준형). 종전 600 이었다.
     이 한 줄이 뜻을 가지려면 화면이 직접값을 안 써야 한다 → app.css 의 500·600·700
     직접값 63자리를 먼저 이 토큰으로 옮긴 뒤 바꿨다(순서를 뒤집으면 화면이 안 바뀐다).
     ⛔ 되돌리려면 이 한 줄만 600 으로. 화면은 손대지 않는다. */
  --fw-strong:700;
  --fw-bold:700;
  /* ★★ 2026-09-02 신설 — 중간 굵기(유준현 대리 승인) ═══════════════════════════════
     왜: 2026-08-25 에 SemiBold 얼굴 등록이 빠지면서 화면 굵기가 **400/700 두 단**이 됐다.
       두 단만으로는 「표 머리 · 소제목 · 합계행」을 본문과 가를 수단이 색밖에 안 남고,
       실제로 중립 회색이 6단계까지 늘어나 있었다(2026-09-02 전수조사, 렌더 글자색 30종).
     ★ 얼굴은 design_tokens.css 에 리터럴 600 으로 다시 등록했다(그 파일 주석 참조).
     ⚠ 쓰는 자리 — 「본문보다 또렷하되 제목은 아닌 것」: 표 머리글, 판 안 소제목,
       합계·소계 행, 선택된 세그먼트 라벨. **제목·강조 수치는 그대로 --fw-strong(700)** 이다.
     ⛔ 화면 CSS 에 600 을 직접 쓰지 말 것 — 이 토큰을 쓴다(직접값이 토큰을 이겨
       "한 줄 고쳐도 화면이 안 바뀌는" 상태가 되는 것이 이 파일이 이미 두 번 겪은 사고다). */
  --fw-mid:600;

  /* ── 모션 ────────────────────────────────────────────────────────────────
     기존 코드가 쓰던 값(.11~.22s)의 중앙을 잡아 셋으로 묶었다.
     HIG: 자주 일어나는 상호작용에는 커스텀 모션을 넣지 않고, 넣더라도 짧고 정확하게. */
  --mo-fast:120ms;   /* hover·상태 전환 */
  --mo-base:160ms;   /* 패널 열림·탭 전환 */
  --mo-slow:240ms;   /* 드로어·시트 */
  --mo-ease:cubic-bezier(.2,.7,.3,1);

  /* ── 초점 ────────────────────────────────────────────────────────────────
     한 벌로 통일한다. 앱이 자기 표현을 갖고 있으면 그쪽이 이긴다(아래 규칙은 :where 라 특정도 0). */
  --focus-color:var(--ci-blue,#00A4EB);
  --focus-w:2px;
  --focus-offset:2px;

  /* ── 터치 타깃 (HIG: iOS 44pt / macOS 28pt, 간격도 크기만큼 중요) ─────────
     값만 정의한다. 적용은 컴포넌트별 폭 검증 뒤에 한다 — 지금 일괄로 키우면 조밀 툴바가
     폰에서 넘칠 수 있고, 그걸 확인할 실측이 아직 없다(진단 §6). */
  --touch-min:44px;
  --touch-min-desktop:28px;
}

/* ══ KRDS 「업무 등급」 밀도 ═════════════════════════════════════════════════
   `<html data-density="krds">` 한 자리로 켠다. 끄면(속성이 없으면) 위 compact 값 그대로다.

   왜 스위치인가: 이 값들은 **전 화면의 글자와 컨트롤 높이를 동시에** 움직인다.
   30개가 넘는 화면을 한 번에 바꾸는 일이라 "좋아 보이니 넣었다"로 올릴 수 없다 →
   테스트 서버에 켠 채로 올려 메인(현행)과 나란히 놓고 사람이 고른다.

   값의 출처: `talent/디자이너/articles/krds-viol-adaptation.md` §2·§5 「업무 등급」
     · 컨트롤 40 · 본문 15(KRDS body-small) · 표 행 33 · 라벨 13(body-xsmall)
   근거: KRDS 기본값(컨트롤 56·본문 17)은 하루 한 번 쓰는 국민 서비스 기준이고,
   우리 화면은 하루 종일 훑는 업무 그리드다 → KRDS 가 이미 가진 small 변형을 기본으로 쓴다.
   초조밀(32·13)은 KRDS 가 "신중히" 라고 적은 구간이라 예외 승인 화면만 쓴다.
   ══════════════════════════════════════════════════════════════════════════ */
:root[data-density="krds"]{
  --t-caption:var(--fs-13);    /* 11 → 13 : KRDS body-xsmall 하한. 11px 라벨은 그 밑이었다 */
  --t-small:var(--fs-13);      /* 12 → 13 : 표 본문 */
  --t-body:var(--fs-15);       /* 13 → 15 : KRDS body-small = 업무 등급 본문 */
  --t-lead:var(--fs-15);       /* 14 → 15 : 문서 기본·패널 제목 */
  --t-mid:var(--fs-16);        /* 15 → 16 : 판 머리 제목 */
  --t-strong:var(--fs-16);     /* 유지 */
  --t-title:var(--fs-20);      /* 18 → 20 : 화면 제목(heading 과 body 차 1.25~1.5배) */
  --ctl-h:40px;                /* 28 → 40 : KRDS small 변형(컨트롤) */
  --ctl-h-sm:32px;             /* 22 → 32 : KRDS xsmall 변형 */
  --row-h:34px;                /* 30 → 34 : 표 행(8+15+8 + 선) */
}

/* ── 컨트롤 높이 (compact 기본) ───────────────────────────────────────────
   버튼·입력·셀렉트가 **같은 값**을 읽어야 한 줄에 섰을 때 밑선이 맞는다.
   종전에는 app.css 안에 28 이 자리마다 박혀 있어서(버튼 28 · 필드 28 · 칩 22 · 표 행 30)
   한 곳만 고치면 그 줄만 어긋났다. */
:root{
  /* ⚠ 값은 **지금 화면의 실측값**이다(추측 아님): app.css 의 KRDS 레이어가 컨트롤을
     28 → 32 로 올려 뒀고 칩은 22, 표 행은 30 이다. 토큰으로 옮기면서 픽셀을 바꾸지 않는다 —
     바꾸는 것은 위 `[data-density="krds"]` 를 켤 때뿐이다. */
  --ctl-h:32px;      /* 버튼·입력·셀렉트 기본 (KRDS 컨트롤 하한) */
  --ctl-h-sm:22px;   /* 칩·xs 버튼 */
  --row-h:30px;      /* 표 행 — app.css 의 --row 가 이 값을 읽는다 */
}

/* ── 폰에서는 컨트롤을 키운다 (2026-08-23) ─────────────────────────────────
   근거: KRDS 접근성 공통 "터치 44×44 권장" · HIG iOS 44pt. 데스크톱은 마우스라 32 로 충분하지만
   폰에서 32px 버튼은 손가락으로 짚기 어렵다(사용자가 "손으로 짚기 어렵다"고 지적한 그 자리를
   28 → 32 로 올린 것이 데스크톱 기준이었다).
   ★ 40 을 고른 이유: KRDS 컨트롤 스케일의 small 값이고, 44 로 올리면 폰 조회 조건판이
     한 줄 더 늘어난다(실측으로 확인하며 올린다). 실측에서 넘침이 나오면 되돌린다.
   ⚠ 데스크톱은 이 블록 밖이라 무변경이다. */
@media (max-width:720px){
  :root{ --ctl-h:40px }
}

/* ── 폰 폭 타이포 승격 — 지금은 켜지 않는다 ─────────────────────────────────
   2026-08-20 에 이 블록이 들어왔으나 화면이 역할 토큰을 안 써서 **한 번도 동작한 적이
   없었다**(채택 0회). 이번에 화면이 역할 토큰을 쓰기 시작하므로, 그대로 두면 180자리가
   폰에서 동시에 한 단 올라간다 — 그 결과를 잴 수단이 지금 없다
   (verify_no_clipping.py 는 로그인 뒤 화면을 열지 못한다: 앵커 0개로 3화면 '검사 못 함').
   ⛔ 그래서 **측정 없이 켜지 않는다.** 화면별로 390px 실측을 붙이며 켠다.
   켜는 방법: `<html data-typo-mobile="step">`. 승격 방향은 KRDS 「본문 최소 16px」이다. */
@media (max-width:719px){
  :root[data-typo-mobile="step"]{
    --t-caption:var(--fs-12);
    --t-small:var(--fs-13);
    --t-body:var(--fs-15);
    --t-lead:var(--fs-16);
    --t-title:var(--fs-20);
    --t-metric:var(--fs-24);
    --t-metric-lg:var(--fs-32);
  }
}

/* ── 공통 초점 표시 ──────────────────────────────────────────────────────────
   특정도 0(:where)이라 앱의 기존 초점 규칙이 항상 이긴다. 규칙이 아예 없던 화면
   (데이터·인허가·CRM)에만 실제로 나타난다 = 접근성 공백만 메운다. */
:where(a[href],button,[role="button"],input,select,textarea,summary,[tabindex]):focus-visible{
  outline:var(--focus-w) solid var(--focus-color);
  outline-offset:var(--focus-offset);
}

/* ── 동작 줄이기 ────────────────────────────────────────────────────────────
   HIG: 모션은 선택 사항이어야 한다. 이동·확대는 없애고 필요하면 페이드로 남긴다.
   ⚠ 전환 종료 이벤트에 기대는 코드가 멈추지 않도록 0 이 아니라 아주 짧은 값을 준다.
   ⚠ 움직임 자체가 정보인 요소(진행 표시기 등)는 data-motion="keep" 으로 제외한다. */
@media (prefers-reduced-motion:reduce){
  :root{ --mo-fast:1ms; --mo-base:1ms; --mo-slow:1ms; }
  *:not([data-motion="keep"]):not([data-motion="keep"] *){
    animation-duration:1ms !important;
    animation-iteration-count:1 !important;
    transition-duration:1ms !important;
    scroll-behavior:auto !important;
  }
}
