/* LEWS 디자인 시스템 — 웹 구현
 *
 * 명세: ../../docs/specs/design-system.md · ../../docs/specs/media/web-and-software.md
 * 이 파일은 명세의 구현이지 명세가 아니다. 값을 여기서 바꾸지 말고 명세를 먼저 고친다.
 */

/* ─────────────────────────────────────────── 서체
 *
 * 자체 호스팅한다. 설치본에 기대면 워드마크가 깨진다 — 로고 SVG 의 tspan 은 x 좌표가
 * 박혀 있어서, 폰트가 바뀌면 글자 폭만 달라지고 위치는 그대로라 글자가 겹친다.
 * 실제로 겪었다(LE 실측 64.03 vs 원본 오프셋 56.67).
 *
 * Inter 는 라틴과 숫자, Pretendard 는 한글. Pretendard 는 동적 서브셋이라
 * unicode-range 로 필요한 것만 내려온다. 연결은 <link> 로 한다 — @import 는 렌더링을 막는다.
 */

@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 원본 로고가 지정한 옵티컬 사이즈 이름들을 같은 파일로 받아 준다.
   이렇게 해야 로고 SVG 를 손대지 않고도 올바른 자폭으로 그려진다. */
@font-face {
  font-family: "Inter 24pt";
  font-style: normal; font-display: swap; font-weight: 100 900;
  src: url("fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
}
@font-face {
  font-family: Inter24pt-ExtraBold;
  font-style: normal; font-display: swap; font-weight: 100 900;
  src: url("fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
}

/* 중문 — Noto Sans SC 를 **실제 쓰는 글자로만** 서브셋해 자체 호스팅한다.
 *
 * 전체 15 MB → 708 자 165 KB. 가변이라 이 한 파일이 300·600·800 을 다 덮는다.
 * `unicode-range` 로 묶어 두므로 **한국어·영문 페이지는 내려받지 않는다.**
 *
 * 굽는 도구: tools/subset-cjk.py · 근거: ADR 0024
 * **서브셋 표류가 이 구조의 유일한 위험이다** — 중문에 글자를 더하고 다시 굽지 않으면
 * 그 글자만 시스템 폰트로 떨어지는데 빌드는 성공한다.
 * `./tools/subset-cjk.py --check` 가 그걸 잡는다.
 *
 * SIL Open Font License 1.1. 서브셋 재배포는 허용되나 **라이선스 사본 동봉이 의무**다 —
 * `fonts/NotoSansSC-OFL.txt` 를 지우면 위반이다.
 */
@font-face {
  font-family: "Noto Sans SC Subset";
  font-style: normal;
  font-display: swap;
  font-weight: 300 800;
  src: url("fonts/noto-sans-sc-subset.woff2") format("woff2-variations");
  unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+FF00-FFEF;
}

/* ─────────────────────────────────────────── 토큰 */

:root {
  /* 잉크와 종이. 이 둘이 전부다. 액센트 컬러는 없다. */
  --ink: #000;
  --paper: #fff;

  /* 구조용 회색. 순수 무채색 — R=G=B. 위계에 쓰지 않는다. */
  --line: #e5e5e5;
  --line-strong: #c9c9c9;
  --disabled: #9a9a9a;

  /* 웨이트 셋만. 400·500·700 은 쓰지 않는다. */
  --w-display: 800;
  --w-strong: 600;
  --w-body: 300;

  /* 크기·트래킹·행간. 20–28px, 40–48px 은 금지 구간이다. */
  --t-display-1: 96px;  --ls-display-1: -.045em;  --lh-display-1: 1;
  --t-display-2: 56px;  --ls-display-2: -.035em;  --lh-display-2: 1.05;
  --t-title: 32px;      --ls-title: -.015em;      --lh-title: 1.15;
  --t-body: 16px;       --ls-body: 0;             --lh-body: 1.6;
  --t-caption: 12px;    --ls-caption: .02em;      --lh-caption: 1.4;

  /* 여백 — 2배 단계. 중간값을 만들지 않는다. */
  --s-1: 4px;  --s-2: 8px;   --s-3: 16px;
  --s-4: 32px; --s-5: 64px;  --s-6: 128px;

  /* 벤또 */
  --grid-cols: 12;
  --gap: var(--s-3);
  --radius: 0;    /* 직각이다. 이 값을 바꾸지 않는다. */
  --cut: 16px;    /* 모따기. 갭과 같게 유지한다. */
  --hairline: 1px;

  /* 모션 — 이징 하나, 층위 넷 */
  --ease: cubic-bezier(.2, 0, 0, 1);
  --t-micro: 120ms;
  --t-element: 300ms;
  --t-view: 600ms;
  --stagger: 60ms;

  --pad: var(--s-3);
  --max: 1440px;

  /* 헤더 띠 높이 — 로고 36 + 로고 패딩 12 + 헤더 패딩 32 + 헤어라인 1.
     히어로를 헤더 아래까지 끌어올리는 데 쓴다. site.js 가 실측으로 갱신한다. */
  --bar-h: 81px;
}

@media (min-width: 600px)  { :root { --pad: var(--s-4); } }
@media (min-width: 1024px) { :root { --pad: var(--s-5); } }

/* ─────────────────────────────────────────── 기초 */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; border-radius: var(--radius); }

/* 스티키 헤더 아래로 앵커가 숨지 않게 한다. */
html { -webkit-text-size-adjust: 100%; scroll-padding-top: calc(var(--bar-h) + var(--s-3)); }

body {
  background: var(--paper);
  color: var(--ink);
  /* 설치본을 먼저 쓴다. 운영에서는 자체 호스팅으로 바꾼다 — README 참조. */
  /* 중문 서체는 **목록 뒤에 잇는다 — 덮어쓰지 않는다.**
     `:lang(zh)` 로 font-family 를 재정의했더니 로고 SVG 의 워드마크까지 덮여
     폴백 폰트로 떨어졌고, tspan x 좌표가 고정이라 자간이 벌어졌다(실제로 겪었다).
     뒤에 잇기만 하면 Inter·Pretendard 가 못 그리는 글자에서만 쓰인다.
     자체 호스팅 서브셋을 먼저 두고, 그래도 없는 글자만 시스템 폰트로 간다. */
  font-family: "Inter Variable", Inter, "Pretendard Variable", Pretendard,
               -apple-system, "Apple SD Gothic Neo",
               "Noto Sans SC Subset",
               "PingFang SC", "Source Han Sans SC", "Noto Sans CJK SC", sans-serif;

  /* **명세의 본문 행을 여기서 건다** — §타이포 크기 표의 `Body 16 px / 300 / 0 / 1.6`.
   *
   * 이게 빠져 있었다. 토큰은 처음부터 있었는데 거는 규칙이 없어서, 본문이 브라우저
   * 기본값(16 px / **400** / `normal` ≈ 1.2)으로 그려졌다. 제목은 `.display-*`·`.title` 이
   * 제 값을 명시해 멀쩡했고 본문만 조용히 규칙 밖에 있었다.
   *
   * 그래서 둘이 한꺼번에 어긋나 있었다 —
   *   · 웨이트 400 은 800/600/300 셋 밖이다 (§타이포는 400·500·700 을 금지한다)
   *   · 행간 `normal` 은 1.6 보다 훨씬 좁다. 한글 본문에서 특히 답답하다
   *
   * Light 하한(화면 16 px)을 본문이 정확히 만족하므로 300 을 쓸 수 있다. */
  font-size: var(--t-body);
  font-weight: var(--w-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-body);
}

img, svg { max-width: 100%; display: block; }

/* 링크는 밑줄로 구분한다. 색이 없으므로 유일한 표시다. */
a { color: inherit; text-decoration: underline; text-underline-offset: .2em; }
a:hover { text-decoration-thickness: 2px; }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* **국문은 어절 안에서 끊지 않는다.**
 *
 * 브라우저 기본값은 한글을 CJK 로 보고 **글자 단위**로 접는다. 그래서 낱말 한복판이
 * 갈렸다 — 히어로에서 `재현되지 않는 출력은 우연입` / `니다.`, 방침 페이지에서
 * `보내신 문` / `의 메일뿐입니다.` 가 실제로 그렇게 나왔다.
 * `keep-all` 이면 띄어쓰기에서만 접힌다.
 *
 * `overflow-wrap: break-word` 를 **함께** 건다. `keep-all` 만 두면 칸보다 긴 낱말
 * (`개인정보분쟁조정위원회` 같은 것)이 통째로 칸을 넘는다. 둘을 같이 두면
 * 평소에는 띄어쓰기에서 접히고, 정말 안 들어갈 때만 낱말 안에서 접힌다.
 *
 * **중문에는 걸지 않는다** — 띄어쓰기가 없어서 한 문장이 통째로 한 덩어리가 된다.
 * 그래서 `:lang()` 이 아니라 `html[lang]` 으로 국문·영문에만 건다.
 * 영문은 원래 띄어쓰기에서 접히므로 이 값이 바꾸는 것이 없다. */
html[lang="ko"] body, html[lang="en"] body {
  word-break: keep-all;
  overflow-wrap: break-word;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--paper);
  padding: var(--s-2) var(--s-3);
}
.skip:focus { left: var(--pad); top: var(--s-2); z-index: 10; }

/* ─────────────────────────────────────────── 타이포 */

.display-1, .display-2, .title { font-weight: var(--w-display); }

.display-1 { font-size: var(--t-display-1); letter-spacing: var(--ls-display-1); line-height: var(--lh-display-1); }
.display-2 { font-size: var(--t-display-2); letter-spacing: var(--ls-display-2); line-height: var(--lh-display-2); }
.title     { font-size: var(--t-title); letter-spacing: var(--ls-title); line-height: var(--lh-title); font-weight: var(--w-strong); }

/* 아이브로 — 대문자 라벨. // 로 층위를 나눈다. */
.eyebrow {
  font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption); line-height: var(--lh-caption);
  text-transform: uppercase;
}

/* 브라우저 기본 `bolder` 는 이 가변 폰트에서 700 으로 떨어진다 — 웨이트 셋(800/600/300)
   밖이다. 600 으로 내려 묶는다. 색이 없는 시스템에서 강조 수단이 웨이트뿐이라
   `<strong>` 자체는 쓴다 — 없애면 낭독기에서 강조가 사라진다. */
strong, b { font-weight: var(--w-strong); }

.body { max-width: 34em; }         /* 줄길이 70자 / 한글 40자 */
.muted { color: var(--disabled); } /* 비활성·플레이스홀더 전용. 위계에 쓰지 않는다. */

/* Display 1 아래 단계는 없다. 좁아지면 Display 2 로 내린다 — 중간 크기를 만들지 않는다.
   한글은 자소 밀도가 높아 96px 이 라틴보다 훨씬 넓게 자리를 먹는다. */
@media (max-width: 899px) {
  .display-1 { font-size: var(--t-display-2); letter-spacing: var(--ls-display-2); line-height: var(--lh-display-2); }
}

/* ─────────────────────────────────────────── 레이아웃 */

.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--pad); }

/* **섹션 구분선을 긋지 않는다** — 여백만으로 나눈다 (사용자 지시 2026-07-31).
 *
 * 파운데이션 §4 가 셀 구분을 괘선보다 여백으로 먼저 하라고 하고, `--s-6` 이 이미 128 px 이라
 * 선 없이도 끊긴다. 전에는 메인에서만 껐고(`[data-page="index"]`) 다른 페이지는
 * "글이 이어지므로" 선을 남겼는데, 그 예외가 이제 전 페이지의 기본이 됐다.
 *
 * 섹션 머리마다 아이브로 라벨(`제품 // PRODUCT`)이 서 있어 그것이 경계를 맡는다 —
 * 라벨과 선이 같은 일을 두 번 하고 있었다. */
section { padding-block: var(--s-6); }

.section-head { margin-bottom: var(--s-5); }
.section-head .title { margin-top: var(--s-2); }

.bento { display: grid; grid-template-columns: repeat(var(--grid-cols), 1fr); gap: var(--gap); }
.c3 { grid-column: span 3; } .c4 { grid-column: span 4; }
.c6 { grid-column: span 6; } .c8 { grid-column: span 8; } .c12 { grid-column: span 12; }

/* 층은 셋이다 — 데스크탑(기본) · 태블릿(600–899) · 스마트폰(≤599).
 *
 * 전에는 899 px 아래를 통째로 한 열로 접었다. 그러면 **태블릿이 폰과 같은 판**이 되고,
 * 768 px 에서 셀 폭이 689 px 하나뿐이라 절반이 빈 채로 쌓인다(실측).
 * 태블릿은 12 열을 **6 열로 줄여** 쓴다 — 열을 없애는 것이 아니라 반으로 접는 것이다.
 *
 *   c3 · c4 · c6  →  절반 (한 줄에 둘)
 *   c8            →  전폭  (8 은 6 을 넘으므로 접을 자리가 없다)
 *
 * 폰에서만 전부 한 열이다. 그래야 줄길이가 34em 아래로 내려가지 않는다. */
@media (min-width: 600px) and (max-width: 899px) {
  .c3, .c4, .c6 { grid-column: span 6; }
  .c8 { grid-column: span 12; }
}
@media (max-width: 599px) { .c3, .c4, .c6, .c8 { grid-column: span 12; } }

/* 셀 구분은 여백으로 먼저 한다. 헤어라인은 여백만으로 안 읽힐 때. */
.cell { border: var(--hairline) solid var(--line); padding: var(--s-4); }

/* 모따기 — 45° 컷, 우상단 한 모서리, 컨테이너 전용.
 * clip-path 는 테두리도 자른다. 그래서 잉크로 채우고 테두리 없이 간다. */
.chamfer {
  clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
  background: var(--ink); color: var(--paper); border: 0;
}
.chamfer .muted { color: var(--line-strong); }

/* 셀 호버 — 면을 가진 덩어리는 반전이다 (media/web-and-software.md §인터랙션).
 *
 * **색을 보간하지 않는다.** `background`·`color` 에 트랜지션을 걸면 그 사이 프레임에서
 * 잉크와 종이가 회색을 지난다. §9 가 금지하는 것이고, 글래스 헤더가 상태 전환을
 * 컷으로 둔 이유와 같다. 셀은 행보다 면적이 커서 그 회색이 더 오래 보인다.
 *
 * 대신 **흰 면을 차분(difference)으로 얹고 마스크로 올린다.** 차분은 픽셀마다 1 - v 를
 * 내므로 종이(1)는 잉크(0)가 되고 잉크는 종이가 된다. 중간값이 없다 — 마스크 경계의
 * 아래는 완전히 반전된 면, 위는 손대지 않은 면이다. 회색이 생길 자리가 없다.
 * 아이콘 획과 인라인 SVG 도 같이 뒤집힌다. `currentColor` 를 따로 손볼 필요가 없다.
 *
 * `#fff` 는 색을 고른 것이 아니라 차분의 피연산자 1.0 이다. 토큰으로 바꾸지 않는다 —
 * 종이 값이 언젠가 달라지면 반전이 조용히 죽는다 (검은 면에 차분을 걸면 아무 일도
 * 일어나지 않는다).
 *
 * 셀에 배경을 명시하는 것도 이 때문이다. `isolation` 이 만든 그룹 안에서 차분이 섞을
 * 상대가 셀 자신의 배경뿐인데, 투명하면 섞을 것이 없어 흰 면이 그대로 덮인다.
 * 몸통이 이미 종이색이라 눈에 보이는 변화는 없다.
 *
 * 마스크 방향은 셀 리빌과 같다 — 아래에서 위로 올라온다.
 * 모따기 셀은 제외한다. 이미 반전 블록이고, 호버로 되뒤집으면 "여기가 제일 중요하다"는
 * 표시가 도리어 풀린다 (§1 — 반전은 강조 수단이지 장식이 아니다).
 *
 * 포인터가 없는 기기는 아예 붙이지 않는다. 탭하면 반전이 눌어붙는다.
 * 모션을 끄면 전역 규칙이 지속을 0 으로 만들어 컷으로 떨어진다 — 반전 자체는 남는다.
 */
@media (hover: hover) {
  .cell:not(.chamfer) { position: relative; isolation: isolate; background: var(--paper); }
  .cell:not(.chamfer)::after {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--hairline));   /* 테두리까지 덮는다. 셀이 한 덩어리로 뒤집힌다 */
    background: #fff;
    mix-blend-mode: difference;
    pointer-events: none;
    clip-path: inset(100% 0 0 0);
    transition: clip-path var(--t-micro) var(--ease);
  }
  .cell:not(.chamfer):hover::after { clip-path: inset(0 0 0 0); }
}

/* 강제 색상 모드에서는 사용자가 정한 색이 이긴다. 그 위에 차분을 얹으면
   본문이 통째로 사라질 수 있다. 반전을 포기하고 내용을 지킨다. */
@media (forced-colors: active) {
  .cell:not(.chamfer)::after { content: none; }
}

/* ─────────────────────────────────────────── 헤더 */

/* 스티키 글래스 바 → ADR 0010.
 *
 * §1 은 반투명과 글래스를 금지한다. 반투명이 잉크와 종이 사이에 회색을 만들기 때문이다.
 * 여기서만 예외를 열되, **회색이 생기지 않게** 만든다 —
 * 뒤가 어두우면 헤더도 함께 반전한다. 그러면 글자는 늘 21:1 이다.
 */
.bar {
  position: sticky; top: 0; z-index: 20;
  /* 상태 전환에 트랜지션을 걸지 않는다 — 컷이다.
     색을 보간하면 그 사이 프레임에서 글자가 회색을 지나간다(실측 rgb(203,203,203)).
     페이드를 금지한 것과 같은 이유다. 경계에서 딱 끊는 편이 규칙에도 맞고 기계적으로 읽힌다. */
}

/* 유리는 띠 자신이 아니라 **가짜 요소**가 쓴다.
 * backdrop-filter 를 가진 요소는 그 안의 자손에게 배경 루트가 되고, 그러면 자손의
 * backdrop-filter 는 부모 안쪽만 보게 되어 아무 것도 흐리지 않는다.
 * 메가 메뉴 패널이 .bar 의 자손이라 실제로 그랬다 — 값은 blur(20px) 인데 화면은 선명했다.
 * 유리를 형제(::before)로 내리면 .bar 는 배경 루트가 아니고 패널이 제 몫의 블러를 갖는다. */
.bar::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(255, 255, 255, .72);
  /* 구분선 없음. 블러와 명도차만으로 띠를 구분한다. */
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  /* 유리가 **돌아오는** 시점만 늦춘다 (아래 `:has()`). 메뉴를 닫는 동안 패널은 아직
     띠를 덮고 있으므로, 여기서 곧바로 유리를 켜면 그 300 ms 동안 두 장이 겹쳐
     띠만 밝아진다. 패널이 사라지는 순간에 맞춰 켠다. 값 자체는 보간하지 않는다 — 컷이다. */
  transition: background 0s linear var(--t-element),
              backdrop-filter 0s linear var(--t-element),
              -webkit-backdrop-filter 0s linear var(--t-element);
}

/* 메가 메뉴가 열려 있는 동안 띠의 유리는 접는다 — 패널의 유리가 띠까지 덮으므로,
   두 장이 겹치면 .72 가 두 번 얹혀 그 구간만 밝아진다. 전환은 컷이다.
   `:has()` 를 모르는 브라우저는 예전처럼 두 장이 되고 경계가 남는다 — 글자는 읽힌다. */
.bar:has(.nav-item:hover)::before,
.bar:has(.nav-item:focus-within)::before {
  background: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  transition-delay: 0s;             /* 접는 것은 즉시, 돌아오는 것만 늦다 */
}

/* 뒤가 잉크면 헤더 글자는 종이색이 된다.
 *
 * **검은 틴트를 두지 않는다** (사용자 지시 2026-07-31). 예전에는 `rgba(0,0,0,.6)` 을 깔아
 * 띠를 눌렀는데, 그 반투명 검정이 곧 회색이라 §1 이 금지한 스크림과 다르지 않았다.
 * 지금은 **블러만** 남긴다 — 뒤의 영상이 뭉개져 띠가 구분되고, 잉크와 종이 사이에
 * 회색 면이 끼지 않는다.
 *
 * 대가는 대비다. 글자 뒤가 밝은 프레임이면 헤더가 읽기 어려워진다.
 * 히어로 문구의 잉크 면을 걷은 것과 같은 판단이다 → ADR 0029. */
.bar.on-dark { color: var(--paper); }
.bar.on-dark::before { background: none; }
.bar.on-dark:has(.nav-item:hover)::before,
.bar.on-dark:has(.nav-item:focus-within)::before { background: none; }
.bar.on-dark .brand { color: var(--paper); }
.bar.on-dark .search { border-bottom-color: rgba(255, 255, 255, .45); }
.bar.on-dark .search:hover,
.bar.on-dark .search:focus-within { border-bottom-color: var(--paper); }
.bar.on-dark .search input::placeholder { color: rgba(255, 255, 255, .55); }

/* 블러를 못 쓰면 불투명으로 떨어진다. 반투명만 남으면 글자가 읽히지 않는다. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bar::before { background: var(--paper); }
  .bar.on-dark::before { background: var(--ink); }
}
/* 투명도를 줄여 달라는 설정은 존중한다. */
@media (prefers-reduced-transparency: reduce) {
  .bar::before { background: var(--paper); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .bar.on-dark::before { background: var(--ink); }
}

/* 세 칸 그리드 — 로고 왼쪽, 내비 가운데, 검색 오른쪽.
   양쪽 칸을 1fr 로 같게 두어야 내비가 화면 정중앙에 온다. */
.masthead {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: var(--s-4);
  padding-block: var(--s-3);
}

.brand { display: block; text-decoration: none; color: var(--ink); justify-self: start; }
.brand:hover { text-decoration: none; }
/* 보호 여백 D 는 파일에 굽지 않고 여기서 준다. D = 아트워크 높이의 0.4927 배.
   로고 36px → D 17.7px. 자체 padding 6 + masthead padding 16 = 22px 이라 충족한다. */
.logo { height: 36px; width: auto; padding-block: 6px; box-sizing: content-box; display: block; }

/* 간격은 단계 안에서 올린다 — 16 → 32. 중간값(20·24)을 만들지 않는다. */
.nav { display: flex; gap: var(--s-4); flex-wrap: wrap; justify-self: center; }

/* 호버는 밑줄이 왼쪽에서 차오른다. 마스크다 — 페이드가 아니다.
   반전 블록은 카드·행처럼 덩어리에 쓰고, 글줄 링크에는 쓰지 않는다.
   네모 상자가 글자를 덮으면 내비가 무거워진다. */
.nav-item > a {
  position: relative;
  text-decoration: none;
  font-weight: var(--w-strong);
  padding: var(--s-1) 0;
}
.nav-item > a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: currentColor;
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--t-micro) var(--ease);
}
.nav-item > a:hover::after { clip-path: inset(0 0 0 0); }
/* 현재 페이지는 밑줄이 계속 켜져 있다. 호버와 같은 언어다. */
.nav-item > a[aria-current="page"]::after { clip-path: inset(0 0 0 0); }

/* ─────────────────────────────────────────── 메가 메뉴
 *
 * 내비 버튼에 얹으면 헤더가 아래로 연장되고, 그 버튼에 딸린 페이지만 가로로 늘어선다.
 *
 * 절대 위치다. 흐름에 두면 헤더가 커지는 만큼 본문이 아래로 밀려서, 버튼 위를 지나가기만
 * 해도 페이지가 출렁인다. 헤더는 이미 sticky 로 본문 위에 떠 있으니 눈에는 같은 연장이다.
 * 덤으로 site.js 가 재는 --bar-h 가 흔들리지 않는다.
 *
 * 패널도 헤더와 같은 글래스다 → ADR 0010 의 예외를 "헤더 띠"에서 "띠와 그 연장"으로
 * 넓혔다. 조건은 그대로다 — 뒤가 어두우면 패널도 함께 반전해서 회색을 만들지 않는다. */

/* 버튼 상자를 헤더 위아래 패딩까지 늘린다. 안 늘리면 버튼에서 패널로 내려가는 길에
   16px 짜리 빈 구간이 생겨 메뉴가 깜빡이며 닫힌다. 음수 마진으로 레이아웃은 그대로 둔다. */
.nav { align-self: stretch; }       /* 버튼 상자가 헤더 행 높이를 다 쓰게 한다 */
.nav-item {
  position: static;                 /* 패널의 기준은 .bar 다 */
  display: flex; align-items: center;
  padding-block: var(--s-3);
  margin-block: calc(-1 * var(--s-3));
}

/* **유리는 하나다 — 패널이 띠까지 덮는다.**
 *
 * 전에는 띠와 패널이 각각 `rgba(255,255,255,.72)` + `blur(20px)` 로 **같은 값**을 썼는데도
 * 그 사이에 선이 보였다. 값이 같아도 유리가 둘이면 한 면이 되지 않는다:
 *   - 각자 제 뒤에 있는 것만 흐린다. 띠 뒤는 히어로 위쪽이고 패널 뒤는 그 아래 내용이다
 *   - 블러는 제 상자 가장자리에서 표본이 끊긴다(edge clamp). 맞닿은 두 면은 경계에서
 *     서로 다른 값으로 끝나므로, 선을 그은 적이 없어도 선이 보인다
 *
 * 그래서 패널을 **띠 위끝(top: 0)에서 시작**시키고 띠 높이만큼 패딩으로 비운다.
 * 유리를 쓰는 상자가 하나뿐이니 이음매가 생길 자리가 없다. 열려 있는 동안 띠 자신의
 * 유리는 접는다(아래 `:has()`) — 겹치면 .72 가 두 번 얹혀 그 구간만 밝아진다.
 * 값이 같으므로 바뀌는 순간에 보이는 것은 이음매가 사라지는 것뿐이다.
 *
 * 열고 닫는 것은 **마스크**다 — `clip-path` 로 잘린 면을 내린다. 배경도 요소의 일부라
 * 유리와 내용이 같은 마스크로 함께 드러난다.
 *
 * **상자 크기를 애니메이션하지 않는다.** 한 번 `grid-template-rows: 0fr → 1fr` 로 짰다가
 * 되돌렸다. 두 가지가 깨졌다:
 *   - 보간이 중간에 취소되면 그 높이로 **굳는다.** 실제로 145 px 에서 멈춘 채 남았다.
 *     열리지도 닫히지도 않고, 그 자리에 패널 아래 선만 남는다
 *   - 상자가 자라는 동안 **닿는 면적도 같이 자란다.** 커서가 여는 속도보다 빨리 내려가면
 *     아직 자라지 않은 자리를 지나 뒤쪽 본문에 닿고, 그 순간 호버가 풀려 닫히기 시작한다.
 *     다시 닿으면 열린다 — 그래서 중간에서 떨린다
 * `clip-path` 는 그리는 면만 자른다. 상자는 처음부터 끝까지 제 크기라 닿는 면적이
 * 흔들리지 않고, 취소돼도 남는 기하가 없다.
 *
 * 닫힌 모양이 `0` 이 아니라 **띠 한 줄**인 것이 요점이다. 열려 있는 동안 띠 자신의 유리는
 * 접히므로(아래 `:has()`), 패널이 띠를 덮은 채로 시작해야 첫 프레임에 띠가 비지 않는다.
 *
 * `z-index: -1` — 패널이 띠 영역까지 올라오므로 그냥 두면 로고와 내비를 덮는다.
 * 음수 층은 흐름 안의 글자보다 아래, `.bar::before` 보다는 위에 깔린다. */
.menu {
  position: absolute; left: 0; right: 0; top: 0; z-index: -1;
  padding-top: var(--bar-h);        /* 띠 자리. 유리는 여기까지 덮고 카드는 그 아래부터다 */
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border-bottom: var(--hairline) solid var(--ink);
  clip-path: inset(0 0 calc(100% - var(--bar-h)) 0);   /* 닫힘 = 띠 한 줄만 */
  visibility: hidden;               /* 닫혀 있으면 초점도 낭독도 닿지 않는다 */
  transition: clip-path var(--t-element) var(--ease),
              visibility 0s linear var(--t-element);
  /* 띠 위에서는 포인터를 받지 않는다. 받으면 로고 위에 커서를 둔 것도 그 갈래에 머무는
     것이 되어 메뉴가 닫히지 않고, 다른 갈래로 넘어갈 때 둘이 동시에 열린다. */
  pointer-events: none;
}
.menu > * { pointer-events: auto; }

.nav-item:hover > .menu,
.nav-item:focus-within > .menu {
  clip-path: inset(0 0 0 0);
  visibility: visible;
  transition-delay: 0s;
}

/* 항목 폭은 내용이 정한다. 12열에 맞춰 벌리면 두 글자짜리 이름이 300px 을 차지해
   메뉴가 아니라 섹션처럼 읽힌다. 첫 항목의 왼쪽 끝만 wrap 패딩으로 격자에 맞는다. */
.menu-grid {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  /* 카드 높이를 맞춘다 — 이름이 두 줄로 흐르는 카드가 섞여도 반전 블록의 크기가 같다. */
  align-items: stretch;
  /* 헤더 띠의 축을 따른다 — 내비가 화면 정중앙이므로 그 아래도 정중앙이다.
     본문의 좌측 정렬과 섞이는 것이 아니라, 헤더가 하나의 독립된 조판이다. */
  justify-content: center;
  padding-block: var(--s-4);
}

/* 테두리 없이 간다 — 호버에 반전하는 면이 곧 경계다. 정지 상태에서는 아무 것도 두지 않는다.
 *
 * **폭이 모든 카드에서 같다.** 내용이 폭을 정하게 두면 반전 블록의 크기가 이름 길이를 따라
 * 들쭉날쭉해지고, 색이 없는 시스템에서 유일한 강조 수단인 반전이 그때마다 다른 덩어리로
 * 보인다. `연혁`(2 자)과 `미세가공 레이저`(7 자)가 나란히 있으면 특히 눈에 띈다.
 *
 * 값은 **가장 긴 이름에 맞추지 않았다.** 영문 `Micromachining lasers` 는 176 px 이라
 * 거기 맞추면 카드 7 장이 1456 px 이 되어 메뉴가 아니라 섹션이 된다.
 * 대신 폭을 고정하고 긴 이름만 두 줄로 흘린다 — 높이는 `align-items: stretch` 가 맞춘다.
 * 128 px 내용 폭이면 한국어·중국어 최장(101·84 px)은 한 줄에 들어가고
 * 영문 최장 단어 `Micromachining`(118 px)도 잘리지 않는다.
 */
.menu-card {
  display: block; text-decoration: none;
  padding: var(--s-3);
  width: calc(var(--s-6) + var(--s-4));   /* 128 + 32 = 160. 내용 폭 128 */
  text-align: center;               /* 묶음도 가운데, 카드 속도 가운데 — 축이 하나다 */
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
/* 32px 단계. 획은 아이콘 체계 표대로 2px 이어야 하므로 24 그리드에서 1.5 단위로 그린다
   (2 단위를 그대로 키우면 2.67px 이 되어 다른 아이콘보다 굵어진다). */
/* svg 는 전역에서 display:block 이라 text-align 이 닿지 않는다. 마진으로 가운데 둔다. */
.menu-card .ico { width: 32px; height: 32px; stroke-width: 1.5; margin-inline: auto; }
.menu-card:hover { background: var(--ink); color: var(--paper); text-decoration: none; }
/* 아이콘과 글자 사이는 8px. 아이콘 체계의 값이고 늘리지 않는다. */
.menu-name { display: block; margin-top: var(--s-2); font-weight: var(--w-strong); }
.menu-card[aria-current="page"] { background: var(--ink); color: var(--paper); }

/* 뒤가 어두우면 헤더를 따라 패널도 뒤집힌다 — 글자는 종이색이 된다.
   헤어라인은 잉크 위에서 안 보이므로 종이색으로 바꾼다.

   **틴트를 두지 않는다.** 띠에서 `rgba(0,0,0,.6)` 을 걷었는데 패널에는 남아 있었다.
   패널은 열릴 때 띠까지 덮고, 그동안 띠는 제 유리를 접는다(아래 규칙) — 그래서
   투명하던 헤더가 호버하는 순간 **60% 검은 판으로 바뀌었다.** 실제로 그렇게 보였다.
   지금은 패널도 블러만 남긴다. 띠와 패널이 같은 값이라 겹치는 구간이 생기지 않는다. */
.bar.on-dark .menu { background: none; color: var(--paper); border-bottom-color: var(--paper); }
.bar.on-dark .menu-card { border-color: var(--disabled); }
.bar.on-dark .menu-card:hover,
.bar.on-dark .menu-card[aria-current="page"] { background: var(--paper); color: var(--ink); }

/* 헤더와 같은 물러설 자리. 반투명만 남으면 메뉴 글자가 읽히지 않는다.
   패널이 띠까지 덮으므로 불투명으로 떨어져도 여전히 한 면이다. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .menu { background: var(--paper); }
  .bar.on-dark .menu { background: var(--ink); }
}
@media (prefers-reduced-transparency: reduce) {
  .menu { background: var(--paper); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .bar.on-dark .menu { background: var(--ink); }
}

/* 좁은 화면에서는 열지 않는다. 내비가 두 줄로 접히고 포인터도 없어서 패널이 설 자리가 없다.
   같은 링크가 푸터 사이트맵에 전부 있으므로 내용을 잃지 않는다. */
@media (max-width: 899px) {
  .nav-item { padding-block: 0; margin-block: 0; }
  .menu { display: none; }
}

/* 검색 — 아래쪽 한 줄. 박스로 감싸지 않는다. */
.search {
  display: flex; align-items: center; gap: var(--s-2);
  justify-self: end; width: 100%; max-width: 28ch;
  border-bottom: var(--hairline) solid var(--line-strong);
  transition: border-color var(--t-micro) var(--ease);
}
.search:hover { border-bottom-color: var(--ink); }
.search input {
  font: inherit; font-weight: var(--w-body); color: inherit;
  background: none; border: 0;
  padding: var(--s-1) 0; width: 100%; min-width: 0;
  -webkit-appearance: none; appearance: none;
}
.search input:focus { outline: none; }        /* 윤곽선 대신 아래 선이 굵어진다 */
.search input::placeholder { color: var(--disabled); }
.search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.search button {
  background: none; border: 0; color: inherit; cursor: pointer;
  padding: var(--s-1); display: flex;
}
/* 포커스는 밑선이 잉크로 굵어진다 — 상자를 덧그리지 않는다. */
.search:focus-within { border-bottom-color: var(--ink); border-bottom-width: 2px; }

/* ── 검색 결과 패널 — 헤더 띠의 연장이다 (ADR 0010 의 예외 안).
 *
 * 메가 메뉴와 **같은 유리**를 쓴다. `.bar::before` 가 유리를 들고 있으므로 이 패널은
 * 제 몫의 블러를 갖는다 — 유리를 `.bar` 자신이 쓰면 자손이 배경 루트에 갇혀 흐려지지 않는다.
 *
 * §1 의 예외 판정 기준은 "회색을 만드는가" 이고, 통과하는 방법은 **뒤를 따라 반전하는 것**이다.
 * 한때 불투명 종이로 도망친 적이 있다 — 히어로 위에서 갈래 라벨이 묻혀서였는데,
 * 원인은 유리가 아니라 글자색이 `.bar.on-dark` 를 상속하지 않은 것이었다.
 * 메뉴가 이미 푼 문제를 다시 푼 셈이라 되돌렸다.
 *
 * **띠를 덮지 않는다.** 메뉴는 화면 폭을 다 쓰므로 띠까지 덮고 띠의 유리를 접었지만,
 * 이 패널은 띠 아래에서 시작한다. 겹치는 구간이 없으니 접을 것도 없다.
 *
 * 여는 것은 `hidden` 토글이다. 마스크로 열지 않는다 — 결과가 칠 때마다 바뀌므로
 * 300 ms 짜리 전환이 매 글자마다 다시 시작되어 도리어 덜컹거린다.
 * 페이드가 아니라 컷이므로 §9 의 회색 문제는 없다. */
.search-panel {
  /* **`absolute` 가 아니라 `fixed` 다.** 띠가 `sticky` 라 그 기준으로 절대 배치하면
   * 패널이 문서의 **스크롤 가능 영역에 기여한다** — 열릴 때마다 문서가 57 px 자라고
   * 브라우저가 그만큼 스크롤을 보정해, 글자를 칠 때마다 화면이 위로 튀었다(실측).
   * `fixed` 는 흐름에도 스크롤 영역에도 전혀 기여하지 않는다.
   *
   * 띠가 언제나 뷰포트 상단에 붙어 있으므로 `top: var(--bar-h)` 가 곧 띠 아래다.
   * 그 값은 site.js 가 실측해 덮어쓴다. 조상에 `transform`·`filter` 가 없어야
   * 기준이 뷰포트로 남는데, 헤더가 유리를 `::before` 로 내려 둔 덕에 그 조건이 지켜진다. */
  position: fixed; left: 0; right: 0; top: var(--bar-h);
  z-index: 19;                      /* 띠(20) 아래 — 띠가 패널 위에 온다 */
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  /* **글자색을 명시한다.** 패널은 `.bar` 의 자손이라 히어로 위에서
     `.bar.on-dark { color: var(--paper) }` 를 상속한다. 밝은 유리 위 흰 글자가 되어
     `<mark>` 로 반전된 자리만 남고 나머지가 사라진다 — 실제로 그랬다.
     아래 `on-dark` 규칙이 이 값을 다시 뒤집는다. */
  color: var(--ink);
  border-bottom: var(--hairline) solid var(--ink);
  padding-block: var(--s-3) var(--s-4);
  max-height: 70vh; overflow-y: auto;
}

/* 뒤가 어두우면 패널도 따라간다 — 메뉴와 같은 규칙이다(`.bar.on-dark .menu`).
   **틴트를 걷는다.** 흰 반투명을 어두운 그림 위에 얹으면 그것이 곧 회색 스크림이고,
   §1 이 금지한 것이다. 블러만 남기고 글자를 종이색으로 뒤집으면 대비가 21:1 로 돌아온다. */
.bar.on-dark .search-panel {
  background: none;
  color: var(--paper);
  border-bottom-color: var(--paper);
}

/* 보조 문구(`결과 없음`)는 어두운 배경에서 `--disabled` 로 두지 않는다.
   종이 위에서도 2.8:1 인 값이라 잉크 위에서는 더 읽히지 않는다 — 읽혀야 하는 글이다. */
.bar.on-dark .search-panel .muted { color: var(--paper); }
.search-panel[hidden] { display: none; }
.search-panel .rows { border-top: 0; }
.search-note { padding: var(--s-3) var(--s-2); }

/* 맞은 자리 — **반전이다.** `<mark>` 의 브라우저 기본은 노란 배경이라 그대로 두면
   모노톤에 색이 하나 들어온다. 잉크와 종이를 뒤집어 강조를 만든다(§1). */
.search-panel mark {
  background: var(--ink); color: var(--paper);
  /* 글자 사이가 벌어지지 않게 — 인라인 배경만 바꾼다 */
  padding: 0;
}

/* 행이 반전될 때 그 안의 `mark` 는 되돌아와야 한다. 안 그러면 잉크 위 잉크가 되어
   맞은 자리만 사라진다 — 강조하려던 것이 도리어 지워진다. */
.search-panel .row:hover mark,
.search-panel .row:focus-visible mark { background: var(--paper); color: var(--ink); }

/* 어두운 배경에서는 패널 글자가 이미 종이색이다 — 맞은 자리는 **반대로** 뒤집힌다.
 * 행에 호버가 걸려도 마찬가지다(행이 잉크로 채워지고 글자가 종이색이 되므로 값이 같다).
 * 그래서 호버 규칙과 같은 특정도로 두고 **뒤에** 놓아 이기게 한다. */
.bar.on-dark .search-panel mark,
.bar.on-dark .search-panel .row:hover mark,
.bar.on-dark .search-panel .row:focus-visible mark {
  background: var(--paper); color: var(--ink);
}

/* 블러를 못 쓰거나 투명도를 줄여 달라고 하면 불투명으로 떨어진다 — 헤더와 같은 처리다.
   반투명만 남으면 글자가 읽히지 않는다. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .search-panel { background: var(--paper); }
  .bar.on-dark .search-panel { background: var(--ink); }
}
@media (prefers-reduced-transparency: reduce) {
  .search-panel {
    background: var(--paper);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .bar.on-dark .search-panel { background: var(--ink); }
}

/* 화면에서만 감춘다. 스크린리더는 읽는다. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* 햄버거 — 스마트폰 전용. 600 px 이상에서는 내비가 그대로 서므로 버튼이 필요 없다.
 * 아이콘은 아이콘 체계 그대로다: 수평선 셋(열림) · 45° 둘(닫힘).
 * `hidden` 으로 나가고 JS 가 벗긴다 — 스크립트가 죽으면 내비가 펼쳐진 채로 남아
 * 링크를 잃지 않는다. */
.burger {
  display: none;
  align-items: center; justify-content: center;
  width: 44px; height: 44px;          /* 손가락 자리. 44 는 줄이지 않는다 */
  margin-left: calc(-1 * var(--s-2)); /* 아이콘 여백만큼 당겨 로고와 왼끝을 맞춘다 */
  background: none; border: 0; padding: 0;
  color: inherit; cursor: pointer;
}
.bar.on-dark .burger { color: var(--paper); }
.burger .ico-close { display: none; }
.burger[aria-expanded="true"] .ico-open { display: none; }
.burger[aria-expanded="true"] .ico-close { display: inline-block; }

/* 스마트폰 — 머리띠를 두 줄로 접는다.
 *
 * ⚠ 전에는 `flex-wrap: wrap` 과 `order: 3` 으로 접으려 했는데 **`.masthead` 는 그리드다.**
 * `flex-wrap` 은 그리드에서 아무 일도 하지 않아 규칙이 통째로 죽어 있었고, 375 px 에서
 * `1fr auto 1fr` 의 두 `1fr` 이 0 과 16 px 로 짜부라져 **로고가 사라지고 검색이 16 px** 이 됐다.
 * 실측으로 확인했다(로고 width 0).
 *
 * 1 행은 버튼 · 로고 · 검색, 2 행은 내비다. 내비는 **버튼이 열 때만** 나온다 —
 * 펼친 채로 두면 헤더가 130~179 px 로 화면의 16~22 % 를 sticky 로 먹었다(실측).
 * 닫힌 상태는 80 px 이다. */
@media (max-width: 599px) {
  .masthead { grid-template-columns: auto auto minmax(0, 1fr); gap: var(--s-3); }
  .js-nav .burger { display: inline-flex; }
  .burger { grid-area: 1 / 1; }
  .brand  { grid-area: 1 / 2; }
  .search { grid-area: 1 / 3; justify-self: end; max-width: 14ch; }
  .nav {
    grid-area: 2 / 1 / 2 / -1;
    justify-self: stretch;
    flex-direction: column;
    gap: 0;
    padding-top: var(--s-3);
    border-top: var(--hairline) solid var(--line);
  }
  /* 손가락 자리를 44 px 로 벌린다. 글자 크기는 내리지 않는다. */
  .nav .nav-item > a { display: block; padding-block: var(--s-3); }
  /* **JS 가 살아 있을 때만 접는다.** `.js-nav` 가 없으면 `.open` 을 붙일 사람이
     없어 내비가 영원히 숨는다 — 링크를 잃는 쪽으로 실패하면 안 된다. */
  .js-nav .masthead:not(.open) .nav { display: none; }
}

/* ─────────────────────────────────────────── 히어로 — 슬라이드 4 장
 *
 * 좌측 문구 + 우측 3D 렌더. 캔버스 애니메이션은 걷어냈다.
 * 화면을 가득 채운다. 헤더는 sticky 라 흐름에 자리를 차지하므로 그 높이만큼
 * 끌어올려 헤더 아래까지 이어지게 한다.
 */
/* 히어로는 **어둡다** — 배경이 필름이기 때문이다 → ADR 0027.
 *
 * 글자가 영상 위에 올라가는데, §1 은 반투명 스크림(회색)을 금지한다. 대비를 확보하는
 * 길은 하나뿐이다 — 면을 잉크로 두고 글자를 종이색으로 뒤집는 것. 그러면 흰 글자와
 * 검은 화면 사이에 회색이 낄 자리가 없다.
 * `data-dark` 가 붙어 있어 헤더도 따라 반전한다 (site.js).
 */
.hero {
  position: relative;
  margin-top: calc(-1 * var(--bar-h));
  height: 100svh;
  min-height: 560px;
  overflow: hidden;
  background: var(--ink);
  color: var(--paper);
}

/* 배경 영상 넷. **층마다 하나**다 → ADR 0029.
   `object-fit: cover` — 잘려도 좋다. 값을 읽는 그림이 아니라 면이다.

   **`filter: grayscale(1)` 이 규칙의 핵심이다.** 생성 영상은 무채색을 네 번 지시해도
   미세한 색조가 남는다. 채택한 넷 중 하나는 채널 편차가 **31** 이었다(푸른 쪽).
   재인코딩할 도구가 이 환경에 없으므로 렌더 단계에서 자른다 — 실측으로 편차가
   31 → **0** 이 되고 평균 RGB 가 정확히 일치한다. 영상을 갈아 끼워도 이 보장은 남는다. */
.hero-films { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.hero-film {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  /* 꺼진 영상은 잘라 둔다. `opacity` 로 숨기면 페이드가 되고, 그건 §모션이 금지한다.
     `visibility` 는 전환이 없어 회색을 지나지 않는다. */
  visibility: hidden;
}
.hero-film.is-on { visibility: visible; }

.slides { position: absolute; inset: 0; z-index: 1; }

/* 전환은 마스크 와이프다 — 페이드를 쓰지 않는다.
   들어오는 장면이 나가는 장면을 덮는다. 회색이 생길 구간이 없다. */
.slide {
  position: absolute; inset: 0;
  display: flex; align-items: center;
  /* **위아래 패딩을 같게 둔다.** 달라 두면 `align-items: center` 가 히어로 중앙이 아니라
     패딩 안쪽의 중앙을 잡는다. 예전에는 위 80(띠) · 아래 144(컨트롤)라 문구 중심이
     328 에 앉았고, 화살표는 히어로 정중앙 360 이라 **32 px 어긋나 있었다**(실측).
     같은 값으로 두면 문구 중심이 360 이 되어 화살표와 한 줄에 선다.
     아래 값이 띠 높이보다 크므로 헤더를 피하는 몫도 함께 만족한다. */
  padding-block: calc(var(--s-6) + var(--s-3));
  clip-path: inset(0 0 0 100%);
  z-index: 1;
  /* **배경을 두지 않는다.** 예전에는 종이색으로 채워 아래 장면을 가렸지만, 이제 아래에
     있는 것은 다른 장면이 아니라 필름이다. 채우면 필름이 가려진다.
     장면끼리 겹쳐 보이지 않는 것은 마스크가 이미 잘라내기 때문이다. */
}
.slide.is-active { clip-path: inset(0); z-index: 3; }
.slide.is-leaving { clip-path: inset(0); z-index: 2; }
.slide.is-moving { transition: clip-path var(--t-view) var(--ease); }

/* 좌 문구 / 우 3D. 벤또와 같은 12열 위에 얹는다. */
.slide-grid {
  width: 100%;
  display: grid; grid-template-columns: repeat(var(--grid-cols), 1fr);
  gap: var(--gap); align-items: center;
}
/* 문구는 필름 **위에 바로 앉는다.** 잉크 면을 깔지 않는다.
 *
 * 예전에는 불투명한 잉크 면을 깔았다. 그때 필름은 문구 자리의 **최대 휘도가 240** 이라
 * 광선 한 줄기가 글자를 지나면 흰 바탕에 흰 글자가 됐기 때문이다.
 *
 * 영상이 바뀌면서 그 전제가 사라졌다. 새 필름은 프롬프트에 "좌측 3 분의 1 은 조용하고
 * 어둡게"를 넣어 뽑았고, 문구 자리 최대 휘도가 **36~57** 로 내려왔다.
 * 흰 글자 기준 대비가 11.6:1 · 14.4:1 이다 — 면이 없어도 AAA(7:1)를 넘는다.
 *
 * **사용자가 예외로 정했다 (2026-07-31)** — 대비가 깨지더라도 면을 없앤다.
 * 그래서 이 규칙은 대비 계산이 아니라 **지시**로 서 있다. 영상을 갈아 끼울 때
 * 대비가 다시 무너져도 면은 돌아오지 않는다. 그때는 영상 쪽을 고친다.
 */
.slide-copy {
  grid-column: span 5;
  padding: var(--s-4);
  margin-left: calc(-1 * var(--s-4));   /* 글자 왼끝은 격자 그대로 */
}
.slide-copy .display-2, .slide-copy .body { margin-top: var(--s-3); }

/* 슬라이드는 문구만 얹는다 — 우측 스테이지(3D 자리)는 배경 필름이 대신한다 → ADR 0027.
   `.stage` · `.orb` · `.stage-note` 규칙을 걷어냈다. 2D 브랜드 오브젝트는 비전 섹션에 있다. */

@media (max-width: 899px) {
  .slide { align-items: flex-start; padding-block: calc(var(--bar-h) + var(--s-4)) calc(var(--s-6) + var(--s-3)); }
  .slide-copy { grid-column: span 12; }
}

/* ── 좌우 화살표 */
.arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 5;
  background: none; border: 0; color: var(--ink); cursor: pointer;
  padding: var(--s-3);
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
/* 화살표는 면을 가진 단추다 — 호버에 반전한다 (글줄 링크와 다르다). */
.arrow:hover { background: var(--ink); color: var(--paper); }
.arrow.prev { left: 0; }
.arrow.next { right: 0; }
@media (min-width: 600px) { .arrow.prev { left: var(--s-2); } .arrow.next { right: var(--s-2); } }

/* ── 하단 컨트롤 */
.controls {
  position: absolute; z-index: 5; color: inherit;   /* 어두운 히어로에서는 종이색을 물려받는다 */
  left: 0; right: 0; bottom: var(--s-5);
  display: flex; align-items: center; justify-content: center; gap: var(--s-4);
}

/* 점 인터페이스 — 디스플레이와 같은 언어다.
   작은 원이 꺼짐, 큰 원이 켜짐. 4 칸짜리 픽셀 디스플레이인 셈이다. */
.dots { display: flex; align-items: center; gap: var(--s-3); }
.dot { color: inherit; }

/* 뒤가 어두우면 컨트롤도 뒤집힌다 — 헤더와 같은 규칙이다. 낱개로 판정한다. */
.arrow.on-dark, .dot.on-dark, .toggle.on-dark { color: var(--paper); }
.arrow.on-dark:hover, .toggle.on-dark:hover { background: var(--paper); color: var(--ink); }
.dot {
  width: 32px; height: 32px;              /* 손가락이 닿는 크기 */
  background: none; border: 0; padding: 0; cursor: pointer;
  display: grid; place-items: center;
}
.dot::before {
  content: ""; display: block;
  width: 6px; height: 6px;
  border-radius: 50%;                      /* 원은 어휘 안이다 */
  background: currentColor;
  transition: width var(--t-element) var(--ease), height var(--t-element) var(--ease);
}
.dot[aria-current="true"]::before { width: 14px; height: 14px; }

.toggle {
  background: none; border: 0; color: var(--ink); cursor: pointer;
  padding: var(--s-2); display: flex;
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
.toggle:hover { background: var(--ink); color: var(--paper); }

@media (prefers-reduced-motion: reduce) {
  .slide.is-moving { transition: none; }
  .dot::before { transition: none; }
}

/* ─────────────────────────────────────────── CI 페이지
 *
 * 로고 셀 — 보호 여백은 **원 지름 D** 만큼이다. 로고 뷰박스에서 D 는 폭의 15.4%
 * (34.56 / 224.53) 이므로, 로고를 셀 폭의 66% 로 두면 좌우에 D 가 넉넉히 남는다.
 * 여백을 눈대중으로 주지 않고 로고 자체의 값에서 끌어온다.
 */
/* 로고 자리에는 테두리를 두르지 않는다 — 파운데이션 §4 는 셀 구분을 여백으로 먼저 하고
   헤어라인은 여백만으로 구조가 안 읽힐 때만 쓰라고 한다. 로고 둘레에 선을 그으면
   그 선이 보호 여백의 경계처럼 읽혀서, 보호 여백이 D 라는 규칙과 어긋나 보인다. */
.ci-logo { display: grid; place-items: center; padding: var(--s-5) var(--s-4); }
.ci-meta { padding-block: var(--s-5); }   /* 테두리를 걷은 만큼 위아래 여백으로 세운다 */
.ci-logo .ci-mark { width: 66%; max-width: 640px; height: auto; display: block; color: var(--ink); }

/* 오시는 길 약도 — tools/fetch-map.py 가 구운 SVG 를 인라인으로 받는다.
 *
 * 선으로만 그린다. 위성 영상이 없으므로 유채색이 들어올 자리가 없다.
 * `currentColor` 를 쓰므로 반전 블록 안에 넣어도 저절로 뒤집힌다.
 * 테두리를 두르는 이유는 그림의 범위가 어디까지인지 보이지 않기 때문이다 —
 * 여백만으로는 지도가 페이지에 녹아 버린다.
 */
.map { border: var(--hairline) solid var(--line); }
.map > svg { width: 100%; height: auto; color: var(--ink); }
.map figcaption { padding: 0 var(--s-4) var(--s-4); max-width: none; }

/* 범례 — 기호와 이름을 나란히. 아이콘 옆 간격 8px 은 아이콘 체계의 값이다. */
.map-key {
  list-style: none;
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-4);
  padding: var(--s-3) var(--s-4) 0;
  font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption);
}
.map-key li { display: flex; align-items: center; gap: var(--s-2); }
.map-key svg { width: 16px; height: 16px; flex: none; color: var(--ink); }

/* ─────────────────────────────────────────── 번호 매긴 규칙선 행 */

.rows { border-top: var(--hairline) solid var(--ink); }
.row {
  display: grid; grid-template-columns: 1fr auto;
  align-items: baseline; gap: var(--s-3);
  padding: var(--s-3) var(--s-2);
  border-bottom: var(--hairline) solid var(--line);
  text-decoration: none;
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
.row:hover { background: var(--ink); color: var(--paper); text-decoration: none; }
.row:hover .muted { color: var(--line-strong); }
.row .idx { font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption); }
.row .name { font-weight: var(--w-strong); }

/* 행의 부제 — 계열 설명과 출력 범위가 들어간다.
   **`.muted` 를 쓰지 않는다.** 회색(#9a9a9a)은 종이 위 2.8:1 이고 비활성·플레이스홀더
   전용이다. 여기 들어가는 것은 읽혀야 하는 제품 정보다.
   위계는 웨이트로 만든다 — 이름이 600, 부제가 300 이다.
   색을 물려받으므로 호버 반전에서 저절로 종이색이 된다. */
.row .meta { font-weight: var(--w-body); }

/* ─────────────────────────────────────────── 인포그래픽
 *
 * 체계는 docs/specs/infographic-system.md. 첫 사례가 회사 소개의 출력 스펙트럼이다.
 *
 * 색이 없으므로 부호는 위치·길이·밀도 셋만 쓴다. 계열 구분에 회색을 쓰지 않는다 —
 * 걸러진 막대는 흐려지는 게 아니라 **윤곽만 남는다.** 정보가 사라지지 않고 밀도만 바뀐다.
 *
 * 좌표(--x1 · --x2)는 빌드 때 계산해 인라인으로 들어온다. 여기서 재지 않는다.
 */

/* 세 열의 폭을 **여기 한 곳에서** 정한다. 축과 막대가 같은 자 위에 서야 한다.
 *
 * 처음에 값 열을 `auto` 로 뒀다가 그림이 거짓말을 했다. 행마다 grid 가 따로라
 * 값 문자열이 길수록(`1 kW ~ 5 kW · 중심빔 출력 기준`) 그 행의 트랙만 짧아지고,
 * 같은 1 kW 가 행마다 다른 자리에 찍혔다. 축 눈금은 또 다른 자리에 있었다.
 * 폭이 고정이어야 열일곱 줄이 한 자를 쓴다. */
.ig { --ig-code: 8.5rem; --ig-val: 15rem; margin-top: var(--s-4); }

/* 필터는 JS 가 켜질 때만 나타난다. 없으면 전체가 그대로 보이므로 잃는 것이 없다. */
.ig-filter { display: none; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-4); }
.ig-live .ig-filter { display: flex; }

/* 버튼 상태는 **컷이다.** 색을 보간하면 그 사이에 회색이 지나간다.
   면이 이만큼 작으면 120 ms 짜리 전환을 얻자고 회색을 만들 이유가 없다. */
.ig-btn {
  font: inherit; font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption);
  background: none; color: var(--ink);
  border: var(--hairline) solid var(--ink); border-radius: var(--radius);
  padding: var(--s-1) var(--s-2); cursor: pointer;
}
.ig-btn:hover,
.ig-btn[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

.ig-sum { font-weight: var(--w-strong); margin-bottom: var(--s-3); }

.ig-rows { list-style: none; border-top: var(--hairline) solid var(--ink); }
.ig-row {
  display: grid; grid-template-columns: var(--ig-code) 1fr var(--ig-val);
  align-items: center; gap: var(--s-3);
  padding: var(--s-2);
  border-bottom: var(--hairline) solid var(--line);
}
.ig-code { font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption); }
.ig-val  {
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
  text-align: right;
}
/* 기준이 다른 계열에만 붙는다. 읽혀야 하는 정보라 회색을 쓰지 않고 웨이트로 낮춘다. */
.ig-note { font-weight: var(--w-body); }

/* 눈금선 — 10 의 거듭제곱마다 하나. 색의 단계가 아니라 선이므로 하드 스톱으로 끊는다.
   그래디언트가 아니다: 1 px 잉크선과 투명 사이에 중간값이 없다. */
.ig-track {
  position: relative; height: var(--s-3);
  background-image: repeating-linear-gradient(to right,
    var(--line) 0 var(--hairline), transparent var(--hairline) 16.6667%);
  /* 마지막 눈금(축 오른쪽 끝)은 반복이 닿지 않는다. 테두리 대신 그림자로 긋는다 —
     테두리는 폭을 1 px 늘려 퍼센트 좌표를 어긋나게 만든다. */
  box-shadow: inset calc(-1 * var(--hairline)) 0 0 var(--line);
}

.ig-bar {
  position: absolute; top: 0; bottom: 0;
  left: var(--x1); width: calc(var(--x2) - var(--x1));
  /* 윤곽은 그림자로 그린다. 테두리로 그리면 걸러질 때 폭이 1 px 씩 움직인다. */
  box-shadow: inset 0 0 0 var(--hairline) var(--ink);
}
/* 단일 값은 막대가 아니라 점이다 — 폭을 지어내지 않는다. 라운드 0 이므로 정사각이다. */
.ig-dot { width: var(--s-2); margin-left: calc(var(--s-2) / -2); }

/* 채움은 따로 한 겹. 걸러질 때 이 층만 마스크로 걷힌다 —
   배경색을 바꾸지 않으므로 잉크와 종이 사이에 회색이 생기지 않는다. */
.ig-fill {
  position: absolute; inset: 0; background: var(--ink);
  clip-path: inset(0 0 0 0);
  transition: clip-path var(--t-micro) var(--ease);
}
.ig-row[data-off] .ig-fill { clip-path: inset(0 100% 0 0); }

/* 축 — 행의 가운데 열과 정확히 같은 자리에 놓는다. 좌우 여백은 행의 패딩·갭·열 폭의 합이다. */
.ig-axis {
  display: block; position: relative; height: var(--s-4);
  margin-left: calc(var(--ig-code) + var(--s-3) + var(--s-2));
  margin-right: calc(var(--ig-val) + var(--s-3) + var(--s-2));
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}
.ig-axis span {
  position: absolute; top: var(--s-2); left: var(--x1);
  transform: translateX(-50%); white-space: nowrap;   /* `100 kW` 가 두 줄로 접히면 눈금이 아니다 */
}
.ig-axis span:first-child { transform: none; }
.ig-axis span:last-child  { transform: translateX(-100%); }

.ig-cap { margin-top: var(--s-3); max-width: 68ch; }

/* ── 파이프라인 — 순서·단계·경로 (§파이프라인)
 *
 * 노드는 컨테이너다. 라운드 0, 테두리는 헤어라인.
 * 연결선은 **수평 한 줄과 45° 화살촉**뿐이다 — 곡선을 쓰지 않는다.
 * 좁아지면 세로로 접히고 화살촉도 같이 90° 돈다 (정지 상태의 회전이라 모션 규칙과 무관).
 */
.flow {
  list-style: none;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--s-5);
  margin-top: var(--s-4);
}
.flow-step {
  position: relative;
  border: var(--hairline) solid var(--line);
  padding: var(--s-3);
}
.flow-idx {
  display: block;
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}
.flow-name { display: block; margin-top: var(--s-2); font-weight: var(--w-strong); }
.flow-note { display: block; margin-top: var(--s-2); font-weight: var(--w-body); }

/* 연결선은 칸 사이의 여백(--s-5 = 64px)에 놓인다. 선은 잉크, 굵기는 헤어라인.
 *
 * **화살촉이 다음 칸을 파고들지 않게 한다.** 예전에는 촉을 담은 16px 상자를
 * `right: -8px` 에 뒀는데, 촉 끝이 상자 안 12.7px 지점이라 여백 끝을 4.7px 넘어
 * 다음 칸 테두리 위에 얹혔다. 지금은 선을 `--s-2` 만큼 짧게 끊고 촉을 그 끝에 세운다 —
 * 촉과 테두리 사이에 종이가 남는다. */
/* 선은 지금 칸의 오른끝에서 시작해 **다음 칸 앞에서 멈춘다.**
   `right` 도 함께 줄여야 한다 — 폭만 줄이면 오른끝은 그대로라 반대쪽이 잘린다. */
/* 화살촉을 걷었으므로 상자를 다시 선 굵기로 둔다. 촉이 있을 때는 마스크가 촉을
   자르지 않게 24px 로 키워야 했지만, 지금은 잘릴 것이 없다.
   선은 `::before` 로 긋는다 — 마스크가 상자를 기준으로 열리게 하려면 상자가 따로 있어야 한다. */
.flow-edge {
  position: absolute; top: 50%; transform: translateY(-50%);
  right: calc(-1 * (var(--s-5) - var(--s-2)));
  /* **양끝 모두 칸에 닿지 않는다** (사용자 지시 2026-07-31). 여백 64 에서 앞뒤로
     8 씩 뗀 48 만 쓴다. 선이 칸에 닿으면 칸의 테두리와 한 획으로 읽혀,
     칸이 상자가 아니라 관처럼 보인다. */
  width: calc(var(--s-5) - var(--s-2) * 2);
  height: var(--hairline);
}
.flow-edge::before {
  content: ""; position: absolute;
  left: 0; right: 0; top: 50%;
  height: var(--hairline); background: var(--ink);
}
/* 화살표는 **그려지며 나타난다.** 페이드가 아니라 마스크다 (§모션).
 * 한 번만 그린다 — 계속 흐르게 하면 WCAG 2.2.2 의 멈춤 장치가 필요해지고,
 * 읽는 내내 움직이는 장식이 된다. 단계 순서대로 60 ms 씩 밀어 흐름의 방향을 준다.
 * 모션을 끄면 처음부터 다 그려진 상태다 — 정보를 잃지 않는다. */
.js-motion .flow-edge {
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--t-view) var(--ease);
}
.js-motion .flow.in .flow-edge { clip-path: inset(0 0 0 0); }
.js-motion .flow.in .flow-step:nth-child(2) .flow-edge { transition-delay: var(--stagger); }
.js-motion .flow.in .flow-step:nth-child(3) .flow-edge { transition-delay: calc(2 * var(--stagger)); }
.js-motion .flow.in .flow-step:nth-child(4) .flow-edge { transition-delay: calc(3 * var(--stagger)); }

@media (prefers-reduced-motion: reduce) {
  .js-motion .flow-edge { clip-path: none; }
}
/* 선 위에 앉는 라벨. `.flow-edge` 가 더는 flex 가 아니므로 가운데는 직접 잡는다. */
.flow-edge-label {
  position: absolute; bottom: var(--s-2);
  left: 50%; transform: translateX(-50%);
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
  white-space: nowrap; background: var(--paper); padding-inline: var(--s-1);
}

/* 두 노드짜리 경로는 가운데 거리 라벨이 주인공이라 칸을 더 벌린다. */
.flow-route { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }

/* 지원 절의 큰 판. 메인의 요약 그림 중 이것만 글자가 셋뿐이라 작게 두면 빈 띠로 읽힌다.
   단계 번호를 디스플레이 크기로 올려 **번호가 그림 노릇**을 하게 했다 —
   아이콘을 덧붙이지 않는다. 여기서 아이콘은 문장을 대체하지 못하고 장식이 된다. */
.flow-lg { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--s-5); }
.flow-lg .flow-step {
  padding: var(--s-4);
  min-height: 13rem;
  display: flex; flex-direction: column;
}
.flow-lg .flow-idx {
  font-size: var(--t-display-2); font-weight: var(--w-display);
  letter-spacing: var(--ls-display-2); line-height: 1;
}
.flow-lg .flow-name {
  margin-top: auto;                    /* 이름은 칸 바닥에 앉는다 — 번호와 멀수록 번호가 산다 */
  font-size: var(--t-title); letter-spacing: var(--ls-title);
}
.flow-lg .flow-note {
  margin-top: var(--s-2);
  /* **비어도 한 줄을 잡는다.** 부제가 없는 칸만 이름이 바닥까지 밀려 혼자 내려앉는다 —
     `설치` 가 그랬다. 자리를 잡아 두면 이름이 칸끼리 한 줄에 선다. */
  min-height: calc(var(--t-body) * var(--lh-body));
}

/* 지원 **본편**의 상세. 메인과 같은 세 단계에 이것이 붙는다 — 티저와 본편의 밀도 차이다.
   변형 클래스를 따로 두지 않는다: `.flow-lg` 가 판을 그대로 주고, 달라지는 것은
   이 줄이 하나 더 붙는 것뿐이다.

   **최소 높이를 올리지 않는다.** 상세가 붙는 것은 /01 뿐이라 거기 맞춰 21rem 을
   줬다가 되돌렸다. 넓은 화면에서는 grid 가 이미 stretch 로 세 칸을 맞추므로 값이 하는
   일이 없고, 좁아져 한 칸씩 쌓일 때는 슬롯 한 줄뿐인 /02·/03 이 그 높이로 서서
   빈 띠가 된다. `.flow-lg` 의 13rem 이 하한이고 나머지는 내용이 민다. */
.flow-detail {
  display: block; margin-top: var(--s-3);
  font-size: var(--t-body); font-weight: var(--w-body); line-height: var(--lh-body);
}

/* 단계 칸도 면을 가진 덩어리다 — 셀과 같은 방식으로 반전한다 (§인터랙션).
 * `background`·`color` 를 보간하지 않는다. 흰 면을 차분으로 얹고 마스크로 올린다 —
 * 중간값이 없어 잉크와 종이 사이에 회색이 생기지 않는다.
 *
 * **연결선은 칸 밖에 있으므로 같이 뒤집히지 않는다.** 덮개를 칸 테두리까지만
 * (`inset: -1px`) 두었기 때문이다. 선까지 반전하면 종이 위 종이가 되어 사라진다. */
@media (hover: hover) {
  .flow-lg .flow-step { isolation: isolate; background: var(--paper); }
  .flow-lg .flow-step::after {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--hairline));
    background: #fff;
    mix-blend-mode: difference;
    pointer-events: none;
    clip-path: inset(100% 0 0 0);
    transition: clip-path var(--t-micro) var(--ease);
  }
  .flow-lg .flow-step:hover::after { clip-path: inset(0 0 0 0); }
}

/* 강제 색상 모드에서는 사용자가 정한 색이 이긴다 — 셀과 같은 이유로 반전을 포기한다. */
@media (forced-colors: active) {
  .flow-lg .flow-step::after { content: none; }
}

@media (max-width: 599px) {
  .flow-lg .flow-step { min-height: 0; padding: var(--s-3); }
  .flow-lg .flow-idx { font-size: var(--t-title); letter-spacing: var(--ls-title); }
  .flow-lg .flow-name { margin-top: var(--s-3); font-size: var(--t-body); letter-spacing: 0; }
}

@media (max-width: 899px) {
  .flow { grid-template-columns: 1fr; gap: var(--s-4); }
  /* 세로로 눕는다. 가로와 같은 이유로 아래 칸 앞에서 끊는다. */
  .flow-edge {
    top: auto; right: auto; left: 50%;
    bottom: calc(-1 * (var(--s-4) - var(--s-2)));
    transform: translateX(-50%);
    width: var(--hairline); height: calc(var(--s-4) - var(--s-2) * 2);
  }
  .flow-edge::before {
    left: 50%; right: auto; top: 0; bottom: 0;
    width: var(--hairline); height: auto;
    transform: translateX(-50%);
  }
  .flow-edge-label { bottom: auto; top: 50%; left: var(--s-3); transform: translateY(-50%); }
  /* 마스크도 방향을 바꾼다 — 위에서 아래로 그려진다. */
  .js-motion .flow-edge { clip-path: inset(0 0 100% 0); }
  .js-motion .flow.in .flow-edge { clip-path: inset(0 0 0 0); }
}

/* ── 브랜드 오브젝트 (2D) — 두 원이 자리를 바꾼다
 *
 * 한 원은 두 원을 잇는 **축을 따라 곧게** 내려가고, 다른 원은 **호를 그리며** 그 자리로
 * 올라간다. 반주기마다 역할이 바뀌므로 한 바퀴를 돌면 정본 배치로 정확히 돌아온다 —
 * **정지 상태는 언제나 로고 그대로다.**
 *
 * 두 변환을 한 목록(`rotate() translate()`)으로 쓴다. 목록의 구조가 키프레임마다 같아야
 * 항목별로 보간된다. 순서는 오른쪽부터 적용된다 — 옮긴 다음 돌린다.
 *   회전 중심 = 두 원의 중점 (50.625, 68.185). 180° 돌면 한 원이 다른 원 자리로 간다
 *   이동 벡터 = 위 원 → 아래 원 (-25.87, 33.55). 실측 좌표에서 그대로 뺀 값이다
 * 원은 회전 불변이라 주기가 끝날 때 변환값이 항등이 아니어도 화면은 시작과 같다.
 * 그래서 이음매 없이 무한히 돈다.
 *
 * **§9 는 회전을 금지한다.** 여기는 브랜드 오브젝트의 예외다 → ADR 0015.
 * 다만 0015 는 "배치를 바꾸는 것"도 위반으로 적어 뒀다. 이 오브젝트는 그 선을 넘는다 —
 * 사용자 지시(2026-07-30)로 만든 것이고, 정지 상태가 정본 배치라는 조건을 달았다.
 */
.orb-stage { display: grid; place-items: center; padding: var(--s-4); }
.orb-loop { width: min(100%, 22rem); height: auto; display: block; fill: var(--ink); }

/* 이름을 같은 규칙에서 준다. `animation` 축약형을 부모 선택자에 두고 이름만 따로
   낮은 특정성으로 주면 축약형이 `animation-name: none` 으로 덮어쓴다 — 한 번 겪었다. */
.orb-loop circle {
  transform-box: view-box;
  transform-origin: 50.625px 68.185px;   /* 두 원의 중점 */
  animation-duration: 4800ms;
  animation-iteration-count: infinite;
  animation-timing-function: var(--ease);
}
.orb-loop .orb-slide { animation-name: orb-slide; }
.orb-loop .orb-arc   { animation-name: orb-arc; }

/* 위 원 — 반주기는 곧게 내려가고, 다음 반주기는 호를 그리며 돌아온다 */
@keyframes orb-slide {
  0%,   8% { transform: rotate(0deg)   translate(0px, 0px); }
  42%, 58% { transform: rotate(0deg)   translate(-25.87px, 33.55px); }
  92%, 100%{ transform: rotate(180deg) translate(-25.87px, 33.55px); }
}
/* 아래 원 — 반대다. 먼저 호를 그리며 올라가고, 다음 반주기에 곧게 내려간다 */
@keyframes orb-arc {
  0%,   8% { transform: rotate(0deg)   translate(0px, 0px); }
  42%, 58% { transform: rotate(180deg) translate(0px, 0px); }
  92%, 100%{ transform: rotate(180deg) translate(25.87px, -33.55px); }
}

/* 모션을 끄면 정본 배치로 선다. 잃는 정보가 없다 — 값을 나르는 그림이 아니다. */
@media (prefers-reduced-motion: reduce) {
  .orb-loop circle { animation: none; }
}

/* ── 유닛 차트 — 글리프 하나가 계열 하나다 (§픽토그램)
 *
 * 여기서 글리프는 아이콘이 아니라 **눈금**이다. 그래서 같은 도형이 반복된다 —
 * 아이콘 체계의 "한 뜻에 하나" 조항은 여기 걸리지 않는다 (icon-system.md 예외 절).
 * 셀 수 있는 수(열일곱)라서 길이 대신 개수로 그린다.
 */
/* 제품 요약 — 도넛 하나와 값 목록.
 *
 * 조각은 **잉크 하나로 채운다.** 밀도(채움·윤곽·해칭·점묘)는 네 단계뿐인데 조각은
 * 여섯이라, 밀도로 여섯을 가르면 없는 묶음이 있는 것처럼 읽힌다.
 * 대신 조각마다 번호를 얹고 오른쪽 목록이 같은 번호로 값을 준다 — 짝짓는 것은 숫자다.
 *
 * `.cell` 을 쓰지 않는다. 셀 호버 반전이 데이터 마크와 겹친다. */
.donut {
  margin-top: var(--s-4);
  display: grid; grid-template-columns: 280px 1fr;
  gap: var(--s-5); align-items: center;
}
.dn { width: 280px; height: 280px; flex: none; }
.dn-seg { fill: var(--ink); }
/* 조각 사이 종이색 틈은 각도로 벌려 뒀다(build-site.py 의 DONUT_GAP). 획을 쓰지 않는다 —
   획으로 만들면 잉크가 줄어드는 게 아니라 잉크 위에 종이를 덧칠하는 셈이 된다. */
.dn-num {
  fill: var(--paper); font-size: 13px; font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption);
  text-anchor: middle; dominant-baseline: middle;
}
.dn-total {
  fill: var(--ink); font-size: 56px; font-weight: var(--w-display);
  letter-spacing: var(--ls-display-2);
  text-anchor: middle; dominant-baseline: middle;
}
.dn-unit {
  fill: var(--ink); font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption); text-anchor: middle; dominant-baseline: middle;
}

.dn-rows { list-style: none; border-top: var(--hairline) solid var(--ink); }
/* 행 전체가 앵커다 — `.row` 와 같은 규칙으로 덩어리째 반전한다.
   호버에 `outline` 을 쓰지 않는다. 윤곽선은 키보드 포커스의 표시다. */
.dn-row {
  display: grid; grid-template-columns: 3rem 1fr auto;
  align-items: baseline; gap: var(--s-3);
  padding: var(--s-3) var(--s-2);
  border-bottom: var(--hairline) solid var(--line);
  text-decoration: none;
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
.dn-row:hover { background: var(--ink); color: var(--paper); text-decoration: none; }
.dn-idx {
  font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption);
}
.dn-name { font-weight: var(--w-strong); }
.dn-val {
  font-size: var(--t-caption); font-weight: var(--w-body);
  letter-spacing: var(--ls-caption); text-align: right;
}
.dn-val b { font-weight: var(--w-strong); }

/* 태블릿 — 그림과 목록을 **나란히 둔다.** 도넛은 전체 대비 몫을 보는 그림이라
   옆에 값이 붙어 있어야 읽힌다. 원만 280 → 200 으로 한 단계 줄여 자리를 만든다. */
@media (min-width: 600px) and (max-width: 899px) {
  .donut { grid-template-columns: 200px 1fr; gap: var(--s-4); }
  .dn { width: 200px; height: 200px; }
}
/* 폰 — 나란히 둘 폭이 없다. 원을 위, 목록을 아래로 내린다. */
@media (max-width: 599px) {
  .donut { grid-template-columns: 1fr; justify-items: center; gap: var(--s-4); }
  .dn-rows { width: 100%; }
  .dn-row { grid-template-columns: 3rem 1fr; }
  .dn-val { grid-column: 2; text-align: left; }
}

/* 좁은 화면 — 코드와 값이 한 줄, 트랙이 그 아래로 내려가 폭을 다 쓴다.
   벤또가 한 열로 접히는 지점(899px)과 같이 간다. 그보다 넓게 버티면
   세 열 사이에서 트랙만 짓눌려 막대 길이 비교가 안 된다. */
@media (max-width: 899px) {
  .ig-row {
    grid-template-columns: var(--ig-code) 1fr;
    gap: var(--s-2) var(--s-3); padding: var(--s-3) var(--s-2);
  }
  .ig-track { grid-column: 1 / -1; order: 2; }
  .ig-val { order: 1; }
  .ig-axis { margin-left: var(--s-2); margin-right: var(--s-2); }
}

/* ─────────────────────────────────────────── 슬롯 — 아직 정해지지 않은 자리 */

/* 지어내지 않는다. 비어 있다는 사실을 그대로 보여준다.
 * 회색은 플레이스홀더 용도이므로 규칙 위반이 아니다. */
.slot {
  display: inline-flex; align-items: center; gap: var(--s-2);
  width: fit-content;          /* 그리드 셀을 채우면 빈 입력창처럼 보인다 */
  justify-self: start;
  border: var(--hairline) solid var(--line-strong);
  color: var(--disabled);
  padding: var(--s-1) var(--s-2);
  font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption);
}

/* ─────────────────────────────────────────── 아이콘 */

.ico {
  width: 24px; height: 24px;
  stroke: currentColor; stroke-width: 2; fill: none;
  stroke-linecap: butt; stroke-linejoin: miter;
}

/* 글줄 안에 놓이는 아이콘. 16px 은 획 1.5 (아이콘 체계 표) */
.ico-inline, .slot .ico {
  width: 16px; height: 16px; stroke-width: 1.5;
  display: inline-block; vertical-align: -.15em;
}

/* ─────────────────────────────────────────── 사양표 — 밀도 예외 (파운데이션 §8) */

/* 계측 장비는 사양을 보고 산다. 여기서는 미니멀리즘을 적용하지 않는다.
 * 가로 괘선만, 세로 괘선 없이, 헤어라인 굵기로. */
.spec { width: 100%; border-collapse: collapse; }
.spec caption { text-align: left; margin-bottom: var(--s-3); }
/* 굵은 선은 표 머리에만. tbody 의 행 헤더까지 잡으면 첫 열에만 밑줄이 그어져
   표가 깨져 보인다 — 실제로 겪었다. */
.spec thead th {
  text-align: left; font-weight: var(--w-strong);
  border-bottom: 2px solid var(--ink);
  padding: var(--s-2) var(--s-3) var(--s-2) 0;
}
.spec tbody th {
  text-align: left; font-weight: var(--w-strong);
}
.spec td, .spec tbody th {
  border-bottom: var(--hairline) solid var(--line);
  padding: var(--s-2) var(--s-3) var(--s-2) 0;
  vertical-align: baseline;
}
.spec tbody tr:hover { background: var(--ink); color: var(--paper); }
.spec tbody tr:hover .muted { color: var(--line-strong); }

/* ─────────────────────────────────────────── 계열 제품 프레임 (빔 프로파일) */

/* 계열 페이지 히어로의 우측 자리다. **제품 사진이 아직 없다** — 그래서 외형을 그리지 않고
 * 확인된 사양(빔 구성 · 파장)을 그린다. 근거는 build-site.py 의 `beam_figure_html` 주석에.
 *
 * 프레임을 두르는 이유는 **여기가 제품 이미지 자리**여서다. 사진이 오면 이 안을 갈아끼운다.
 * 그때 이 도형은 사라지는 것이 아니라 사양 절로 내려갈 수 있다.
 *
 * `.cell` 을 쓰지 않는다 — 인포그래픽은 셀 호버 반전 대상이 아니다
 * (infographic-system.md §조판). 값을 읽는 그림을 통째로 뒤집으면 무엇이 눌린 것인지 알 수 없다. */
.beam {
  border: var(--hairline) solid var(--line);
  padding: var(--s-4);
  margin-bottom: var(--gap);
}
.beam svg { display: block; width: 100%; height: auto; }

/* 빔 단면과 축은 선으로만 그린다 — 아이콘 체계와 같은 어휘(원 · 수평 · 수직 · 45°). */
.beam-core { fill: none; stroke: var(--ink); stroke-width: 2; }
.beam-axis { stroke: var(--ink); stroke-width: 1; }

/* 파장 점은 **채운다.** 확인된 값이라는 뜻이다 — 밀도 규칙 그대로
   (채움 = 확인된 값 · 윤곽 = 같은 층위인데 선택 밖). */
.beam-pt { fill: var(--ink); }

.beam figcaption {
  margin-top: var(--s-3);
  display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3);
}
.beam-val {
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}

/* ─────────────────────────────────────────── 이름·값 쌍 */

/* 제조사 정보처럼 짧은 항목이 여럿 설 때 쓴다.
 *
 * **제 격자를 만들지 않는다.** 푸터가 같은 문제를 먼저 풀었다(`.foot-info dl`) —
 * `dl` 이 `max-content` 로 제 열을 잡으면 값 열이 페이지 12 열 어디와도 맞지 않아,
 * 바로 위 벤또와 선이 어긋난다. 여기서도 12 열에 얹는다: 이름 3 열, 값 9 열.
 *
 * `dd` 의 브라우저 기본 들여쓰기(40px)를 반드시 지운다. 여백 단계(4·8·16·32) 밖의
 * 값이고, 두면 값 열만 격자에서 밀려난다. 이 클래스가 없던 동안 대리점 페이지가
 * 정확히 그 모양이었다 — 이름과 값이 여섯 줄로 쌓이고 값만 들여쓰기됐다. */
.pair {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  gap: 0 var(--gap);
  margin-top: var(--s-4);
  border-top: var(--hairline) solid var(--ink);
}
.pair dt, .pair dd {
  padding: var(--s-3) var(--s-2);
  border-bottom: var(--hairline) solid var(--line);
}
.pair dt {
  grid-column: 1 / span 3;
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}
.pair dd { grid-column: 4 / span 9; margin: 0; }

@media (max-width: 899px) {
  .pair { grid-template-columns: 1fr; }
  .pair dt { grid-column: 1; border-bottom: 0; padding-bottom: 0; }
  .pair dd { grid-column: 1; padding-top: var(--s-2); }
}

/* ─────────────────────────────────────────── 대리점 제품 사진 · 도면 */

/* 파운데이션 §8 의 예외 구역이다 — 제품 사진에는 극한의 미니멀리즘을 적용하지 않는다.
 *
 * **컬러를 그대로 둔다.** 기기의 도장색도, 거기 박힌 제조사 로고도 손대지 않는다.
 * 그레이스케일을 걸면 제조사 자료를 바꾸는 것이 되어 ADR 0028 과 정면으로 어긋난다.
 * 히어로 영상에 `grayscale(1)` 을 건 것과 다르다 — 그쪽은 우리가 만든 생성물이다.
 *
 * ⚠ **`.cell` 안에 넣지 않는다.** 셀 호버는 흰 면을 차분으로 얹어 통째로 반전시킨다.
 * 사진이 그 안에 있으면 파란 기기가 주황으로 뒤집힌다 — 색을 바꾸지 않는다는 규칙이
 * 호버 한 번에 깨진다. 열 클래스(`c4`·`c8`)만 쓰고 셀은 쓰지 않는다.
 *
 * 모따기도 쓰지 않는다. `clip-path` 는 테두리까지 자르므로 배경을 잉크로 채워야 하는데,
 * 사진은 제 그림이 배경이라 그 방법을 쓸 수 없다. 프레임은 헤어라인 하나로 끝낸다. */
.shot { border: var(--hairline) solid var(--line); background: var(--paper); }
.shot img { display: block; width: 100%; height: auto; }
.shot figcaption {
  padding: var(--s-2) var(--s-3);
  border-top: var(--hairline) solid var(--line);
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}

/* 목록의 사진 카드 — 블록 전체가 링크다. 밑줄을 쓰지 않고 **화살표가 어포던스를 맡는다**
 * (media/web-and-software.md §접근성).
 *
 * **호버 반전을 걸지 않는다** (사용자 지시 2026-07-31). 카드째 뒤집는 길은 사진 색이
 * 바뀌므로 처음부터 닫혀 있었고(제조사 자료를 손대는 일이다), 글자 부분만 뒤집는
 * 절충안도 걷었다 — 사진 아래에서 글자 두 줄만 반전하는 것이 사진과 따로 놀았다.
 *
 * 그래서 이 카드는 §인터랙션의 "면을 가진 것은 호버에 반전" 을 따르지 않는다.
 * 대신 정지 상태의 화살표가 링크임을 말하고, 포커스 링은 그대로 둔다 —
 * 키보드로 어디 있는지 알 수 없게 되면 그건 취향이 아니라 접근성 문제다. */
a.shot-card { display: flex; flex-direction: column; text-decoration: none; color: inherit; }
a.shot-card .shot-name { margin-top: var(--s-3); font-weight: var(--w-strong); }
a.shot-card .shot-model {
  margin-top: var(--s-2);
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}

/* **비율은 CSS 가 아니라 파생본이 맞춘다** (`tools/agency-images.py`).
 *
 * 처음에는 여기서 `aspect-ratio: 4/3` + `object-fit: contain` 으로 틀을 고정했다.
 * 틀 크기는 같아졌지만 **사진이 차지하는 크기가 제각각**이었다 — 정사각 원본은 좌우가,
 * 2:1 원본은 위아래가 남아 카드마다 제품이 다른 크기로 보였다. 틀을 맞춘 것이지
 * 사진을 맞춘 것이 아니었다.
 *
 * 지금은 굽는 단계에서 흰 배경 정사각으로 앉힌다. 원본 38 장 중 26 장이 이미 1:1 이라
 * 대부분은 손대지 않은 것과 같고, 자르지 않고 캔버스만 넓히므로 사진 내용도 그대로다.
 * 그래서 여기에는 규칙이 없다 — 없는 편이 안전하다. 비정사각 자산이 나중에 들어와도
 * 조용히 잘리거나 늘어나지 않고 제 비율로 서고, 그러면 눈에 띄어 파생본을 고치게 된다.
 *
 * 치수 도면만 정사각이 아니다. 상세 페이지에서 혼자 서므로 나란히 비교되지 않는다. */

/* 모델 줄은 카드 바닥에 앉는다 — 이름이 한 줄인 카드와 두 줄인 카드가 섞여도
   화살표가 한 선에 선다. */
a.shot-card .shot-model { margin-top: auto; padding-top: var(--s-2); }
a.shot-card:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ─────────────────────────────────────────── 푸터 */

.foot { border-top: var(--hairline) solid var(--ink); padding-block: var(--s-5); }

/* 사이트맵 — 12열 벤또를 그대로 쓴다. 셀 하나에 한 갈래.
 * 구분은 괘선이 아니라 갭으로 한다. */
.foot-map { margin-bottom: var(--s-5); }
.foot-map ul { list-style: none; margin-top: var(--s-3); display: grid; gap: var(--s-2); }

/* 글줄 링크다 — 헤더 내비와 같은 언어로 간다.
 * 밑줄이 왼쪽에서 차오르는 마스크. 덩어리가 아니므로 반전을 쓰지 않는다. */
.foot-map ul a {
  position: relative; display: inline-block;
  text-decoration: none;
  padding: var(--s-1) 0;
}
.foot-map ul a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: currentColor;
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--t-micro) var(--ease);
}
.foot-map ul a:hover::after { clip-path: inset(0 0 0 0); }
.foot-map ul a[aria-current="page"]::after { clip-path: inset(0 0 0 0); }

/* 로고 — 보호 여백 D 를 CSS 로 준다 (파운데이션 §6).
 * D = 원 지름 33.56 단위. viewBox 높이 69.11 이므로 48px 로 쓰면 D ≈ 23px 이다.
 * 오른쪽·아래로 32px 을 비워 D 를 넘긴다. 위·왼쪽은 푸터 패딩과 wrap 패딩이 맡는다. */
.foot-brand { padding: 0 var(--s-4) var(--s-4) 0; }
.foot-brand a { display: inline-block; color: var(--ink); text-decoration: none; }
/* 헤더의 .logo 는 띠 높이를 맞추려 세로 패딩을 품고 있다. 푸터는 그 계산이 없으므로
   같은 클래스를 재사용하지 않는다. */
.foot-logo { height: 48px; width: auto; display: block; }

/* 회사 정보 + 슬로건 — 사이트맵과 같은 12열 위에 얹는다.
 * dl 이 제 격자(max-content 1fr)를 따로 만들면 값 열이 위 칸 어디와도 맞지 않는다.
 * 아래 6열 격자는 부모의 1·2·3…6 트랙과 정확히 같은 자리에 떨어진다 —
 * 열이 전부 1fr 이고 갭이 같으면 (6t+5g) 를 6 등분한 값이 t 이기 때문이다.
 * 격자를 새로 만드는 것이 아니라 같은 격자를 이어 쓰는 것이다. */
.foot-info {
  display: grid; grid-template-columns: repeat(var(--grid-cols), 1fr);
  gap: var(--s-4) var(--gap);
}
.foot-info dl {
  grid-column: 1 / span 6;
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: var(--s-2) var(--gap);
  align-items: baseline;   /* 12px 라벨과 16px 값의 첫 베이스라인을 맞춘다 */
}
.foot-info dt { grid-column: 1 / span 3; }   /* 로고 열에 맞는다 */
.foot-info dd { grid-column: 4 / span 3; }   /* 회사 // COMPANY 열에 맞는다 */
.foot dt { font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption); }

/* 슬로건 — 제품 // PRODUCT 열에서 시작해 오른쪽 끝까지. Display 1 로 세운다. */
.foot-slogan { grid-column: 7 / -1; align-self: start; }

/* 96px 은 6열 안에서 가장 긴 줄(ENGINEERED · 579px)이 설 수 있을 때만 선다.
   1300px 아래에서는 칸이 모자라 Display 2 로 한 단계 내린다 —
   .display-1 이 900px 에서 내려가는 것과 같은 규칙이고, 중간 크기를 만들지 않는다. */
@media (max-width: 1299px) {
  .foot-slogan {
    font-size: var(--t-display-2);
    letter-spacing: var(--ls-display-2);
    line-height: var(--lh-display-2);
  }
}

/* ─────────────────────────────────────────── 언어 전환
 *
 * 저작권 줄 옆에 나란히 둔다. **JS 를 쓰지 않는다** — 링크 셋이면 충분하고,
 * 스크립트가 죽어도 언어를 바꿀 수 있어야 한다.
 * 현재 언어는 링크가 아니라 글자로 두고 `aria-current` 로 표시한다 —
 * 자기 자신으로 가는 링크는 어포던스가 거짓이다.
 * 구분자는 가운뎃점이 아니라 여백이다. 선을 하나 더 긋지 않는다.
 */
.foot-legal {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--s-3) var(--s-4);
}
.lang { display: flex; gap: var(--s-3); }
.lang a, .lang span {
  font-size: var(--t-caption); font-weight: var(--w-strong);
  letter-spacing: var(--ls-caption);
}
/* 현재 언어는 밑줄이 켜져 있다 — 내비의 현재 페이지와 같은 언어다. */
.lang [aria-current="true"] { text-decoration: underline; text-underline-offset: .2em; }
.lang a { text-decoration: none; }
.lang a:hover { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: .2em; }

.foot-legal { margin-top: var(--s-5); }

/* 좁은 화면에서는 c3 이 12열로 펴진다(위 .c3 규칙). 로고 아래 여백만 다시 잡는다. */
/* 태블릿 — 사이트맵이 2 × 2 로 선다(`.c3` 이 span 6 이 되므로 자동이다).
   회사 정보는 6 열 안에서 라벨·값이 여전히 짝을 이루므로 그대로 둔다.
   슬로건만 아래로 내린다 — 7 열부터라는 지정이 6 열 판에서는 설 자리가 없다. */
@media (min-width: 600px) and (max-width: 899px) {
  .foot-brand { padding: 0 0 var(--s-4) 0; }
  .foot-slogan { grid-column: 1 / -1; margin-top: var(--s-4); }
}

/* 폰 — 전부 한 열로 쌓인다. */
@media (max-width: 599px) {
  .foot-brand { padding: 0 0 var(--s-4) 0; }
  /* 갈래가 세로로 쌓이면 갭 하나로는 목록 안팎이 구분되지 않는다.
     갈래 사이 32, 항목 사이 8 로 벌린다. */
  .foot-map > div + div { margin-top: var(--s-3); }
  /* 12열이 사라지므로 열 지정도 함께 푼다. 안 풀면 암묵 열이 생겨 값이 오른쪽으로 밀린다. */
  .foot-info { display: block; }
  .foot-info dl { grid-template-columns: 1fr; gap: 0; }
  .foot-info dt, .foot-info dd { grid-column: auto; }
  .foot dt { margin-top: var(--s-3); }
  .foot-slogan { grid-column: auto; margin-top: var(--s-5); }
}

/* ─────────────────────────────────────────── 모션 */

/* 페이드를 쓰지 않는다. opacity 전환은 잉크와 종이 사이에 회색을 만든다.
 * clip-path 로 잘라 놓고 잘린 면을 옮긴다 — 잉크는 끝까지 잉크다.
 *
 * animation 이 아니라 transition 을 쓴다. animation + fill-mode:both 는 애니메이션이
 * 돌지 않는 환경에서 시작 키프레임에 영원히 멈춘다 — 내용이 사라진다. 실제로 겪었다.
 * transition 은 최악의 경우 즉시 최종 상태로 확정된다. 모션은 잃어도 내용은 남는다. */

/* 숨기는 것은 JS 가 켜지고 모션이 허용될 때뿐이다.
 * 그래야 JS 없이도, 모션을 꺼도 내용을 잃지 않는다. */
.js-motion [data-reveal] {
  clip-path: inset(100% 0 0 0);
  transition: clip-path var(--t-view) var(--ease);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}
.js-motion [data-reveal].in { clip-path: inset(0 0 0 0); }

/* 화면 전환은 브라우저에 맡기지 않는다.
 *
 * `@view-transition { navigation: auto }` 을 켜 뒀었다. 크로스 도큐먼트 뷰 트랜지션의
 * **기본 애니메이션은 크로스페이드**다 — 두 페이지가 겹쳐 녹는 동안 잉크와 종이 사이에
 * 회색이 생긴다. §모션 첫 번째 규칙을 이동할 때마다 어기고 있었다.
 *
 * 마스크 와이프로 바꿔 다는 길도 있지만(`::view-transition-old/new` 에 clip-path),
 * 그러면 이동마다 600 ms 가 붙는다. 도착한 뒤 본문이 [data-reveal] 로 다시 쓸려 들어오므로
 * 같은 동작이 두 번 겹친다. 지금은 걷어 두고, 필요해지면 그때 설계한다. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .js-motion [data-reveal] { clip-path: none; }

  /* 지속만 0 으로 만들면 **지연은 남는다.** 메뉴는 즉시 닫히는데 띠의 유리는 300 ms 뒤에
     돌아와, 그 사이 헤더가 맨얼굴로 남는다. 인수인계 시점도 같이 당긴다. */
  .menu, .bar::before { transition-delay: 0s !important; }
}

/* ─────────────────────────────────────────────── 세로 연표 (인포그래픽)
 *
 * 연혁 페이지의 그림 → docs/specs/infographic-system.md §형태는 열려 있다 · 타임라인.
 * 가로 `.flow` 와 어휘는 같고 축만 돌렸다 — **세로로 쌓이는 것이 값**이라서다.
 * 가로로 늘어놓으면 항목이 늘 때마다 칸이 좁아지는데, 연표는 늘어나는 쪽이다.
 *
 * 축은 잉크 수직선 하나, 마디는 채운 사각, 가지는 수평선이다.
 * **선은 수평·수직만 쓴다** (아이콘 체계와 같은 어휘). 곡선 연결선은 없다.
 *
 * 시점이 확인된 마디(`.vtl-fix`)는 채우고, **미확인은 윤곽으로 비운다** —
 * 미정을 0 으로 그리지 않는다는 조항이 여기서는 "채운 사각으로 그리지 않는다"가 된다.
 */
.vtl { list-style: none; position: relative; margin-top: var(--s-4); padding-left: var(--s-5); }

/* 축 — 첫 마디 중심에서 마지막 마디 중심까지만 긋는다.
   끝까지 늘이면 아무 데도 닿지 않는 선이 위아래로 삐져나온다. */
.vtl::before {
  content: ""; position: absolute; left: var(--s-3);
  top: var(--s-3); bottom: var(--s-3);
  width: var(--hairline); background: var(--ink);
}
.vtl-item { position: relative; padding-bottom: var(--s-5); }
.vtl-item:last-child { padding-bottom: 0; }

/* 마디 — 축 위에 앉는 사각. 라운드 0 은 컨테이너 규칙이지만 여기도 그대로 간다.
 *
 * ⚠ `left` 는 **음수여야 한다.** `li` 의 기준점은 `ol` 의 패딩 **안쪽**(64px)이라
 * 양수로 두면 마디가 글자 위에 얹힌다. 축은 패딩 상자 기준 16px 에 있으므로
 * 그 자리로 되돌리려면 `--s-3 − --s-5` 만큼 왼쪽으로 밀고, 사각의 절반(5px)을 더 뺀다. */
.vtl-item::before {
  content: ""; position: absolute;
  left: calc(var(--s-3) - var(--s-5) - 5px); top: calc(var(--s-3) - 5px);
  width: 10px; height: 10px;
  background: var(--ink);
}
/* 미확인 마디는 비운다. 종이로 채우고 잉크 테두리만 남긴다 — 회색을 쓰지 않는다. */
.vtl-item.is-open::before {
  background: var(--paper);
  box-shadow: inset 0 0 0 var(--hairline) var(--ink);
}
/* 가지 — 마디에서 글자 쪽으로 가는 수평선. 축과 같은 굵기.
   `left` 가 음수인 이유는 위 마디와 같다. **글자에 닿기 전에 끊는다**(--s-2 만큼) —
   닿으면 선과 글자가 한 획으로 읽힌다 (`.flow-edge` 와 같은 판단). */
.vtl-item::after {
  content: ""; position: absolute;
  left: calc(var(--s-3) - var(--s-5)); top: var(--s-3);
  width: calc(var(--s-5) - var(--s-3) - var(--s-2)); height: var(--hairline);
  background: var(--ink);
}
.vtl-year {
  display: block;
  font-size: var(--t-caption); font-weight: var(--w-strong); letter-spacing: var(--ls-caption);
}
.vtl-name { display: block; margin-top: var(--s-2); font-weight: var(--w-strong); }
.vtl-note { display: block; margin-top: var(--s-2); font-weight: var(--w-body); }

/* 축이 **그려지며 내려온다.** 페이드가 아니라 마스크다 (§모션). 한 번만 그린다. */
.js-motion .vtl::before {
  clip-path: inset(0 0 100% 0);
  transition: clip-path var(--t-view) var(--ease);
}
.js-motion .vtl.in::before { clip-path: inset(0 0 0 0); }

@media (prefers-reduced-motion: reduce) {
  .js-motion .vtl::before { clip-path: none; }
}

/* ─────────────────────────────────────────── 쿠키 동의 배너 → ADR 0035
 *
 * 화면 아래에 붙는 **잉크 띠**다. 첫 화면을 가리는 물건이라 규칙이 셋이다.
 *
 * ── 낮게, 한 줄로
 *    상자를 화면 가운데 띄우지 않는다. 아래에 붙어 콘텐츠를 밀지 않고 덮기만 한다.
 *    문구가 짧아야 하는 이유이기도 하다(사전에 그렇게 적어 뒀다).
 *
 * ── **페이드를 쓰지 않는다**
 *    §모션이 금지한다. 마스크로 올린다 — `clip-path` 가 아래에서 위로 열린다.
 *    잉크와 종이 사이에 회색이 생길 구간이 없다.
 *
 * ── 두 단추의 무게가 같다
 *    거부가 동의보다 눈에 덜 띄면 자유로운 선택이 아니다(GDPR). 그래서 둘 다 같은
 *    테두리·같은 크기다. 호버에서만 반전한다 — 면을 가진 단추의 어휘 그대로다.
 */
.consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  background: var(--ink); color: var(--paper);
  border-top: var(--hairline) solid var(--paper);
}
.consent[hidden] { display: none; }

/* 우상단 한 모서리. 형태 장치는 한 요소에 한 모서리다 → ADR 0008 */
@media (min-width: 600px) {
  .consent {
    left: auto; right: var(--s-4); bottom: var(--s-4);
    max-width: 48ch;
    border: var(--hairline) solid var(--paper);
    clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
  }
}

.consent-in { display: grid; gap: var(--s-3); padding-block: var(--s-3); }
.consent-body { font-size: var(--t-body); }
.consent-body a { color: inherit; }

.consent-acts { display: flex; gap: var(--s-3); }
.consent-acts button {
  flex: 1;
  font: inherit; font-weight: var(--w-strong);
  background: none; color: inherit;
  border: var(--hairline) solid var(--paper);
  padding: var(--s-2) var(--s-3);
  min-height: 44px;                 /* 손가락 자리 */
  cursor: pointer;
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
.consent-acts button:hover { background: var(--paper); color: var(--ink); }
.consent-acts button:focus-visible { outline: 2px solid var(--paper); outline-offset: 2px; }

/* 마스크로 올린다. JS 가 붙는 `.js-motion` 안에서만 움직인다 —
   스크립트가 없으면 배너 자체가 안 뜨므로 여기서는 있으나 마나지만, 규칙을 지킨다. */
.js-motion .consent { clip-path: inset(100% 0 0 0); }
.js-motion .consent:not([hidden]) {
  animation: consent-up var(--t-view) var(--ease) forwards;
}
@keyframes consent-up { to { clip-path: inset(0 0 0 0); } }

@media (min-width: 600px) {
  /* 모따기와 마스크가 같은 `clip-path` 를 다툰다 — 넓은 화면에서는 모따기를 이긴다.
     띠가 아니라 카드라서 올라오는 동작보다 형태가 중요하다. */
  .js-motion .consent, .js-motion .consent:not([hidden]) {
    clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
    animation: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .js-motion .consent, .js-motion .consent:not([hidden]) { animation: none; clip-path: none; }
}

/* 방침 페이지의 재선택 단추 — 본문 안에 서므로 종이 위의 잉크 테두리다. */
.consent-reset {
  font: inherit; font-weight: var(--w-strong);
  background: none; color: inherit;
  border: var(--hairline) solid var(--ink);
  padding: var(--s-2) var(--s-3);
  min-height: 44px; cursor: pointer;
  margin-top: var(--s-3);
  transition: background var(--t-micro) var(--ease), color var(--t-micro) var(--ease);
}
.consent-reset:hover { background: var(--ink); color: var(--paper); }
