/* 랜딩의 **콘텐츠가 아닌 것**만 여기 있다 — 제품 시각화(위)와 스크롤 향상(아래).
   핵심 콘텐츠와 기본 레이아웃은 index.html에 남아, 이 요청이 실패해도 읽고 진단하는
   흐름은 그대로다. 진입 노출·진행 막대·절 눈금 레일이 밖으로 나온 것은 같은 규율의
   다른 면이다: 실패했을 때 **숨김 규칙이 함께 사라져** 본문이 그냥 보인다.
   경계 하나만 지키면 된다 — 여기 있는 것이 없어도 페이지가 온전해야 한다. */

/* ══ 2026-08-30 · 앞표지는 메시지와 제품을 한 화면에 둔다 ═══════════════
   Stripe 레퍼런스에서 가져온 것은 색이 아니라 역할이다: 큰 가치 제안 옆에 실제로
   작동할 것 같은 제품 면을 붙이고, 분위기 이미지는 그 둘의 깊이만 만든다.
   생성 이미지는 장식이라 HTML의 사실·진단 동선은 이것 없이도 그대로 남는다. */
.hero { min-height: min(58rem, 100svh); isolation: isolate; }
.hero-art {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero-art::before {
  content: "";
  position: absolute;
  left: var(--mx, 76%);
  top: var(--my, 34%);
  z-index: 1;
  width: clamp(18rem, 40vw, 36rem);
  aspect-ratio: 1;
  border-radius: var(--radius-full);
  transform: translate(-50%, -50%);
  background: radial-gradient(circle,
    color-mix(in srgb, var(--signal-weak) 22%, transparent), transparent 68%);
  filter: blur(10px);
  opacity: .95;
  transition: left .28s cubic-bezier(.22, .61, .36, 1),
              top .28s cubic-bezier(.22, .61, .36, 1);
}
.hero-art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    var(--neutral-bold) 0%,
    color-mix(in srgb, var(--neutral-bold) 94%, transparent) 30%,
    color-mix(in srgb, var(--neutral-bold) 52%, transparent) 68%,
    color-mix(in srgb, var(--neutral-bold) 76%, transparent) 100%);
}
.hero-art img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: 64% center;
  opacity: .82;
  transform: scale(1.04) translate3d(0, calc(var(--art-y, 0) * -24px), 0);
  will-change: transform;
}
.hero .hero-cols { position: relative; z-index: 1; }
.hero-copy { max-width: 46rem; padding-top: clamp(var(--space-4), 3vw, var(--space-6)); }
.hero-eyebrow { display: inline-flex; align-items: center; gap: var(--space-2); }
.hero-eyebrow i,
.console-status i {
  display: inline-block;
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: var(--radius-full);
  background: var(--signal-weak);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--signal) 28%, transparent);
}
.hero .display {
  max-width: none;
  margin: var(--space-5) 0 var(--space-5);
  font-size: clamp(2.75rem, 5vw, 4rem);
  line-height: 1.02;
  letter-spacing: -.055em;
  font-weight: var(--weight-bold);
}
.hero-line,
.heading-line { display: block; }
.hero-line { width: max-content; max-width: 100%; }
.hero .display .hero-em {
  color: var(--signal-weak);
  background: linear-gradient(100deg,
    var(--inverse), var(--signal-weak),
    color-mix(in srgb, var(--signal-weak) 62%, var(--inverse)));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.hero .lede { max-width: 47rem; font-size: clamp(1.05rem, 1.45vw, 1.25rem); }
.hero .lede em { font-style: normal; }
.hero .lede strong { color: var(--inverse); }
.hero .aud { max-width: 60ch; }
.hero-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-5);
}
.hero-primary { background: var(--inverse); color: var(--ink); }
.hero-primary:hover { background: var(--signal-weak); color: var(--ink); }
.hero-primary span,
.hero-link span { transition: transform .2s cubic-bezier(.22, .61, .36, 1); }
.hero-primary:hover span { transform: translateX(var(--space-1)); }
.hero-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--inverse);
  text-decoration: none;
  font-weight: var(--weight-semibold);
  font-size: var(--text-body);
}
.hero-link:hover { color: var(--signal-weak); }
.hero-link:hover span { transform: translate(var(--space-05), calc(var(--space-05) * -1)); }
.platform-rail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: var(--hairline) solid color-mix(in srgb, var(--inverse) 18%, transparent);
}
.platform-rail span {
  color: color-mix(in srgb, var(--inverse) 68%, transparent);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-micro);
}

/* 실제 진단 입구를 밝은 제품 콘솔로 세운다. 배경 이미지보다 정보가 먼저 읽혀야 한다. */
.hero-console {
  --rx: 0deg;
  --ry: 0deg;
  position: relative;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  border-color: color-mix(in srgb, var(--inverse) 56%, transparent);
  border-radius: var(--radius-xl);
  box-shadow: 0 32px 90px color-mix(in srgb, var(--ink) 46%, transparent),
              inset 0 1px 0 color-mix(in srgb, var(--inverse) 72%, transparent);
  transform: perspective(72rem) rotateX(var(--rx)) rotateY(var(--ry));
  transform-style: preserve-3d;
  transition: transform .34s cubic-bezier(.22, .61, .36, 1), box-shadow .34s ease;
}
.hero-console::after {
  content: "";
  position: absolute;
  inset: var(--space-3);
  z-index: -1;
  border-radius: var(--radius-lg);
  border: var(--hairline) solid color-mix(in srgb, var(--signal) 10%, transparent);
  pointer-events: none;
}
.console-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: var(--hairline) solid var(--rule-soft);
}
.console-status,
.console-time {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--graphite);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-micro);
}
.console-status i { background: var(--signal); box-shadow: 0 0 0 4px var(--signal-weak); }
.hero-console h2 {
  color: var(--ink);
  font-size: clamp(var(--text-title), 2.2vw, var(--text-heading-xl));
  line-height: 1.25;
}
.hero-console label { color: var(--ink); }
.hero-console .small,
.hero-console .status { color: var(--graphite); }
.hero-console .submit-row { flex-wrap: nowrap; }
.hero-console .submit-row .btn { flex: none; }
.console-note {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: var(--hairline) solid var(--rule-soft);
}
.hero-console .panel-alt {
  border-top-color: var(--rule-soft);
  color: var(--graphite);
}
.hero-console .panel-alt a { color: var(--signal); }
.hero-console .panel-alt a:hover { color: var(--ink); }

.hero-console .signal-map {
  min-height: 244px;
  margin: var(--space-4) 0 0;
  border-color: color-mix(in srgb, var(--signal-weak) 30%, transparent);
  background:
    radial-gradient(circle at var(--map-x, 50%) var(--map-y, 48%),
      color-mix(in srgb, var(--signal) 42%, transparent), transparent 38%),
    color-mix(in srgb, var(--neutral-bold) 92%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--inverse) 12%, transparent);
}
.scan-beam {
  position: absolute;
  z-index: 2;
  top: -18%;
  bottom: -18%;
  left: -42%;
  width: 34%;
  transform: skewX(-14deg);
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--signal-weak) 18%, transparent), transparent);
  pointer-events: none;
}
.signal-result { z-index: 3; }

/* 아래 절은 넓게 숨 쉬고, 실물이 주장보다 한 단계 앞으로 나온다. */
section.band { position: relative; overflow: clip; padding: clamp(var(--space-7), 6vw, 6rem) 0; }
.band .head { margin-bottom: clamp(var(--space-6), 4vw, var(--space-7)); }
.band .head h2 {
  max-width: 17em;
  font-size: clamp(1.7rem, 3.4vw, 2.6rem);
  line-height: 1.14;
  letter-spacing: -.038em;
}
.band .head .sec-desc { margin-top: var(--space-4); font-size: var(--text-body-lg); }
/* `.well`은 **면 하나**다 — 방사 색면(`radial-gradient(… signal-weak …)`)을 걷었다
   (2026-08-30). 절마다 모서리에 옅은 색 구름이 떠 있으면 그것이 리듬이 되고, 우물의
   뜻(콘텐츠가 놓이는 면)이 분위기로 바뀐다. 색면은 §1-2의 표면 토큰 하나로 족하다.
   배경은 index.html의 `section.band.well`이 준다. */

/* 큰 면들은 그림자를 쓰지 않는다 — 층은 표면과 경계가 만든다(§1-2). 전에는
   `0 20px 60px`짜리 넓은 그림자가 스플릿·행 카드·CI 상자에 걸려 있었는데,
   그건 elevation이 아니라 후광이라 다크 모드도 없는 이 면에서 재질만 흐렸다. */
.rowout-h { padding: var(--space-5); }

@media (max-width: 47.99rem) {
  .hero { min-height: 0; }
  .hero-art img { object-position: 70% center; opacity: .58; }
  .hero-art::after {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--neutral-bold) 74%, transparent),
      color-mix(in srgb, var(--neutral-bold) 96%, transparent));
  }
  .hero .display { font-size: clamp(1.9rem, 9.2vw, 2.5rem); line-height: 1.07; }
  .hero-actions { align-items: stretch; }
  .hero-actions .btn { width: 100%; }
  .platform-rail { justify-content: space-between; gap: var(--space-2); }
  .hero-console { transform: none; }
  .hero-console .submit-row { align-items: stretch; flex-direction: column; }
  .hero-console .submit-row .btn { width: 100%; }
  .console-time { display: none; }
  .hero-console .signal-map { min-height: 220px; }
}

@media (min-width: 64rem) {
  .hero .hero-cols {
    grid-template-columns: minmax(0, 1.04fr) minmax(24rem, .96fr);
    grid-template-areas: "copy panel" "gauges gauges";
    gap: var(--space-7) clamp(var(--space-6), 5vw, var(--space-8));
  }
  .hero-console { margin-top: var(--space-5); }
  .hero-cols .gauges { margin-top: var(--space-4); }
}

@media (prefers-reduced-motion: no-preference) {
  .console-status i { animation: live-dot 2s ease-in-out infinite; }
  .scan-beam { animation: scan-across 4.8s cubic-bezier(.22, .61, .36, 1) infinite; }
  @keyframes live-dot {
    0%, 100% { opacity: .55; transform: scale(.86); }
    50% { opacity: 1; transform: scale(1); }
  }
  @keyframes scan-across {
    0%, 12% { transform: translateX(0) skewX(-14deg); opacity: 0; }
    22% { opacity: 1; }
    58%, 100% { transform: translateX(430%) skewX(-14deg); opacity: 0; }
  }
}

.signal-map {
  position: relative;
  isolation: isolate;
  min-height: 210px;
  margin: var(--space-4) 0;
  border: var(--hairline) solid color-mix(in srgb, var(--inverse) 20%, transparent);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 48%, color-mix(in srgb, var(--signal) 32%, transparent), transparent 38%),
    color-mix(in srgb, var(--neutral-bold) 48%, transparent);
}
.signal-map::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: .35;
  background-image: radial-gradient(circle,
    color-mix(in srgb, var(--inverse) 40%, transparent) 0 var(--hairline), transparent 1.5px);
  background-size: 18px 18px;
}
.signal-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.signal-lines path {
  fill: none;
  stroke: color-mix(in srgb, var(--signal-weak) 52%, transparent);
  stroke-width: 1;
  stroke-dasharray: 3 6;
}
.signal-lines circle {
  fill: color-mix(in srgb, var(--signal) 18%, transparent);
  stroke: color-mix(in srgb, var(--signal-weak) 52%, transparent);
  stroke-width: 1;
}
.signal-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 86px;
  height: 86px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  gap: 2px;
  text-align: center;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--signal) 18%, transparent), var(--shadow-overlay);
  font-size: var(--text-micro);
  font-weight: var(--weight-bold);
  line-height: 1.2;
}
.signal-center img { width: 26px; height: 26px; display: block; }
.signal-node {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border: var(--hairline) solid color-mix(in srgb, var(--inverse) 22%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--neutral-bold) 72%, transparent);
  color: color-mix(in srgb, var(--inverse) 88%, transparent);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  line-height: 1;
  box-shadow: var(--shadow-raised);
  transition: transform .2s cubic-bezier(.22, .61, .36, 1), background-color .2s ease;
}
.signal-node::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--signal-weak);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 22%, transparent);
}
.signal-node:hover {
  transform: translateY(-3px);
  background: color-mix(in srgb, var(--signal) 38%, var(--neutral-bold));
}
.signal-node.n1 { left: 8%; top: 16%; }
.signal-node.n2 { right: 8%; top: 16%; }
.signal-node.n3 { left: 6%; bottom: 14%; }
.signal-node.n4 { right: 6%; bottom: 14%; }
.signal-result {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  max-width: 112px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-overlay);
  font-size: var(--text-micro);
  line-height: 1.35;
}
.signal-result span { display: block; color: var(--muted); }
.signal-result strong { display: block; color: var(--signal); margin-top: 2px; }

/* 두 공급·수요 축은 표보다 구조가 먼저 보이도록 제품 보드로 그린다. */

/* ── 두 축 — 카드 둘이 아니라 **대조 하나** (2026-08-30 개정) ──
   전에는 아이콘 타일(42px 색면 + 흰 글리프) · 대각 그라디언트 · 모서리 원형 색면 ·
   hover 상승 3px + overlay 그림자를 얹은 카드 둘이었다. 그 조합에는 이름이 있다
   (icon-tile feature card) — 그리고 그 모양은 두 축이 **같은 종류의 두 항목**이라고
   말한다. 이 절의 논지는 정반대다: 둘은 서로 다른 종류의 질문이고, 그래서 따로 잰다.

   이제 그릇을 걷고 **세로 규칙 하나로 가른다.** 남는 차이는 내용이 만든다 —
   왼쪽은 무료·즉시·확정값, 오른쪽은 반복·표본·추정값. 색면 대신 각 축의 끝에
   그 축이 내놓는 값의 모양을 적는다(§5 원칙 ⑯의 어법과 같다). */
.axis-board { display: grid; grid-template-columns: 1fr; gap: var(--space-6); position: relative; }
.axis-card { position: relative; min-width: 0; }
/* 축 이름 — `supply`·`demand`가 라틴이라 mono가 실제로 걸린다(한글이면 글자마다
   폴백해 결이 갈린다). 아이콘 타일(42px 색면)은 걷었다 — 그 자리에 이름 하나면 된다 */
.axis-chip {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-micro);
  color: var(--muted);
}
.axis-card h3 { font-size: var(--text-title); margin-top: var(--space-3); }
.axis-q {
  font-size: var(--text-body-lg);
  font-weight: var(--weight-semibold);
  line-height: 1.55;
  margin-top: var(--space-2);
  max-width: 34ch;
}
.axis-facts { list-style: none; display: grid; gap: var(--space-3); margin-top: var(--space-5); }
.axis-facts li {
  font-size: var(--text-small);
  color: var(--graphite);
  line-height: 1.6;
  padding-left: var(--space-4);
  border-left: var(--hairline) solid var(--rule);
}
.axis-foot {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: var(--hairline) solid var(--rule);
}
.axis-foot span { font-family: var(--font-mono); font-size: var(--text-micro); color: var(--graphite); }
/* 두 축을 잇는 자리 — 전에는 두 카드 **사이 칸**에 선 알약 배지(`+ GEO`)였다.
   격자가 2열이 되면서 가운데 칸이 없어졌고, 애초에 배지 하나로는 '왜 합치나'를
   말할 수 없었다. 두 열을 아래에서 받는 한 문장으로 바꾼다. */
.axis-bridge {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: var(--hairline) solid var(--rule);
  font-size: var(--text-small);
  color: var(--graphite);
}
.axis-bridge strong { font-family: var(--font-mono); color: var(--ink); }

@media (min-width: 48rem) {
  .axis-board { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-7); }
  .axis-card + .axis-card {
    padding-left: var(--space-7);
    border-left: var(--hairline) solid var(--rule);
  }
  .axis-bridge { grid-column: 1 / -1; }
}

@media (max-width: 29.99rem) {
  .signal-map { min-height: 190px; }
  .signal-center { width: 72px; height: 72px; }
  .signal-result { display: none; }
  .signal-node.n1, .signal-node.n3 { left: 4%; }
  .signal-node.n2, .signal-node.n4 { right: 4%; }
}
@media (prefers-reduced-motion: no-preference) {
  .signal-lines path { animation: signal-flow 8s linear infinite; }
  .signal-lines circle { animation: signal-pulse 3.6s ease-in-out infinite; transform-origin: center; }
  .signal-node.n1, .signal-node.n4 { animation: signal-float 4.8s ease-in-out infinite; }
  .signal-node.n2, .signal-node.n3 { animation: signal-float 4.8s ease-in-out -2.4s infinite; }
  .signal-map:hover .signal-node { animation-play-state: paused; }
  @keyframes signal-flow { to { stroke-dashoffset: -72; } }
  @keyframes signal-pulse {
    0%, 100% { opacity: .35; transform: scale(.96); }
    50% { opacity: 1; transform: scale(1.03); }
  }
  @keyframes signal-float { 0%, 100% { margin-top: 0; } 50% { margin-top: -5px; } }
}


/* ══ 스크롤을 되비추는 것들 ══════════════════════════════════════════════
   여기 있는 규칙은 전부 **콘텐츠가 아니라 향상**이다. 그래서 index.html이 아니라
   이 파일에 있다 — 이 요청이 실패하면 숨김 규칙 자체가 없어져 본문이 그냥 보인다
   (인라인에 두면 실패가 더 위험하다). 본문/HTML 비율(design.md §3-6)의 여유를
   여기서 벌어 오는 것도 같은 이유다.
   **규율은 그대로다**(§5 원칙 ⑬): 값이 자라나는 연출은 없다. 움직이는 것은
   전부 "지금 어디를 보고 있는가"를 되비추는 것뿐이다. */

/* ── 진입 노출 — 한 번만, 되돌리지 않는다 ──
   `.reveal-on`은 **관찰자를 실제로 건 다음에** 붙인다(app.js ③). 클래스를 먼저
   붙이고 관찰에 실패하면 본문이 영구히 사라진다 — 실측에서 화면 밖 섹션이 통째로
   opacity 0으로 남았다. 인쇄·구형 브라우저도 같은 함정이라 안전망을 두 겹 둔다
   (타임아웃 해제 + @media print). */
.reveal-on [data-reveal] { opacity: 0; transform: translateY(14px); }
.reveal-on [data-reveal].in {
  opacity: 1;
  transform: none;
  transition: opacity .55s cubic-bezier(.22, .61, .36, 1),
              transform .55s cubic-bezier(.22, .61, .36, 1);
}
/* 격자는 통째로 뜨지 않고 **칸이 차례로** 뜬다 — 넷이 한 번에 나타나면 상자 하나로
   읽히고, 차례로 뜨면 몇 개인지가 눈에 남는다(배점 미터가 이미 쓰는 방식이다).
   그릇 자신은 뜨지 않는다 — 겹치면 같은 전환이 두 겹으로 걸려 느려 보인다. */
.reveal-on [data-reveal].stagger { opacity: 1; transform: none; }
.reveal-on .stagger > * { opacity: 0; transform: translateY(10px); }
.reveal-on .stagger.in > * {
  opacity: 1;
  transform: none;
  transition: opacity .5s cubic-bezier(.22, .61, .36, 1),
              transform .5s cubic-bezier(.22, .61, .36, 1);
}
.reveal-on .stagger.in > *:nth-child(2) { transition-delay: .07s; }
.reveal-on .stagger.in > *:nth-child(3) { transition-delay: .14s; }
.reveal-on .stagger.in > *:nth-child(4) { transition-delay: .21s; }
.reveal-on .stagger.in > *:nth-child(5) { transition-delay: .28s; }
.reveal-on .stagger.in > *:nth-child(6) { transition-delay: .35s; }
.reveal-on .stagger.in > *:nth-child(7) { transition-delay: .40s; }
.reveal-on .stagger.in > *:nth-child(8) { transition-delay: .45s; }
.reveal-on .stagger.in > *:nth-child(n+9) { transition-delay: .50s; }

/* ── 절 머리는 두 겹으로 온다 — 제목 → 요약 (2026-08-29 · 2026-08-30 정정) ──
   전에는 `.head` 한 덩어리가 통째로 떠서, 절이 바뀌었다는 사실은 알려도
   **무엇부터 읽어야 하는지**는 말하지 않았다. 격자에 이미 쓰는 문법(stagger)을
   같은 리듬으로 절의 머리에 넣는다 — 새 어휘가 아니라 있던 어휘의 연장이다.
   아이브로우를 걷으면서 세 겹이 두 겹이 됐다(3번째 지연은 이제 안 걸린다). */
.reveal-on .head[data-reveal] { opacity: 1; transform: none; }
.reveal-on .head[data-reveal] > * { opacity: 0; transform: translateY(9px); }
.reveal-on .head[data-reveal].in > * {
  opacity: 1;
  transform: none;
  transition: opacity .5s cubic-bezier(.22, .61, .36, 1),
              transform .5s cubic-bezier(.22, .61, .36, 1);
}
.reveal-on .head[data-reveal].in > *:nth-child(2) { transition-delay: .08s; }

/* ── 표의 행도 차례로 — 13행 순위표가 벽으로 뜨지 않게 ──
   `.stagger`를 그대로 쓸 수 없다: 그릇이 `<table>`이라 직계 자식이 thead·tbody다.
   행에 걸어야 목록으로 읽힌다. 지연은 위 격자보다 촘촘하다(행이 열셋이라
   같은 간격이면 마지막 행이 0.7초 뒤에 온다 — 그건 로딩으로 읽힌다). */
.reveal-on .rank-rows tbody tr { opacity: 0; transform: translateY(7px); }
.reveal-on .rank-rows.in tbody tr {
  opacity: 1;
  transform: none;
  transition: opacity .45s cubic-bezier(.22, .61, .36, 1),
              transform .45s cubic-bezier(.22, .61, .36, 1);
}
.reveal-on .rank-rows.in tbody tr:nth-child(2) { transition-delay: .03s; }
.reveal-on .rank-rows.in tbody tr:nth-child(3) { transition-delay: .06s; }
.reveal-on .rank-rows.in tbody tr:nth-child(4) { transition-delay: .09s; }
.reveal-on .rank-rows.in tbody tr:nth-child(5) { transition-delay: .12s; }
.reveal-on .rank-rows.in tbody tr:nth-child(6) { transition-delay: .15s; }
.reveal-on .rank-rows.in tbody tr:nth-child(7) { transition-delay: .18s; }
.reveal-on .rank-rows.in tbody tr:nth-child(8) { transition-delay: .21s; }
.reveal-on .rank-rows.in tbody tr:nth-child(9) { transition-delay: .24s; }
.reveal-on .rank-rows.in tbody tr:nth-child(n+10) { transition-delay: .27s; }

/* ── 읽은 만큼 — 스크롤 진행 막대 ──
   요소는 스크립트가 만든다 — JS가 없으면 없는 기능이지 0%에 멈춘 빈 막대가 아니다.
   **색이 두 벌인 이유**: 기본값 `--signal`은 어두운 히어로 위에서 대비가 1.4:1이라
   사실상 안 보인다(히어로를 다 지나기 전까지가 정확히 그 구간이다). 히어로 위에서는
   반전 면의 기준색 짝인 `--signal-weak`를 쓰고, 본문으로 넘어오면 `--signal`로
   돌아온다 — `.chrome.stuck`이 재질을 바꾸는 것과 **같은 지점**이다(app.js ②). */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 40;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  background: var(--signal-weak);
  pointer-events: none;
}
.past-hero .progress { background: var(--signal); }

/* ── 절 눈금 레일 — "얼마나 남았나" 옆에 "지금 어디인가" (2026-08-29) ──
   위 막대는 분량을 말하지 절을 말하지 않는다. 열두 절짜리 한 장에서 읽는 사람이
   실제로 잃는 것은 **자기 위치**다. 형태는 새 무늬가 아니라 이 페이지가 이미 쓰는
   눈금(`.hero::after`·`--tick`)의 세로판이다 — 계측기의 자.
   규율: 값이 아니라 위치라 §5 원칙 ⑬ 밖이다. 요소는 전부 스크립트가 만든다.
   히어로 위에서는 서지 않는다 — 반전 면에서 눈금 색을 두 벌로 갈라 두느니
   **아직 첫 절이 아니라는 사실**을 그대로 두는 편이 정직하다. */
.rail {
  position: fixed;
  right: var(--space-3);
  top: 50%;
  z-index: 35;
  display: none;
  transform: translateY(-50%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
}
.past-hero .rail { opacity: 1; pointer-events: auto; }
.rail a {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  text-decoration: none;
  color: var(--muted);
}
/* 눈금 자신 — 길이가 곧 "여기다". 색만으로 뜻을 싣지 않는다(§5 원칙 ②) */
.rail i {
  display: block;
  width: 14px;
  height: var(--hairline);
  background: var(--rule-bold);
  transition: width .22s cubic-bezier(.22, .61, .36, 1), background-color .22s ease;
}
.rail a:hover i { width: 22px; background: var(--ink); }
.rail a[aria-current] i { width: 26px; height: var(--border-focused); background: var(--signal); }
/* 이름은 평소에 숨어 있다 — 레일에 손이 오면 자가 목차가 된다 */
.rail span {
  font-size: var(--text-micro);
  letter-spacing: 0.04em;
  white-space: nowrap;
  opacity: 0;
  transform: translateX(var(--space-2));
  transition: opacity .22s ease, transform .22s cubic-bezier(.22, .61, .36, 1);
}
.rail a[aria-current] span { color: var(--ink); font-weight: var(--weight-semibold); }
.rail:hover span, .rail a:focus-visible span { opacity: 1; transform: none; }
/* 눈금은 64rem부터(그리드 밖 여백이 처음으로 남는 폭), 이름은 90rem부터 —
   그 사이 폭에서 이름을 띄우면 본문 오른쪽 끝을 덮는다 */
@media (min-width: 64rem) { .rail { display: block; } }
@media (max-width: 89.99rem) { .rail span { display: none; } }

/* ── 히어로의 깊이 — 스크롤을 광원이 되받는다 ──
   면 전체를 움직이지 않는다(글자가 움직이면 읽는 중에 방해가 된다). 움직이는 것은
   카피 뒤의 광원 하나뿐이고, 폭도 히어로 한 화면 안에서 끝난다. `--sy`는 app.js가
   0→1로 채운다(히어로를 지나는 비율) — 값이 없으면 0이라 정지 상태와 같다. */
.hero::before {
  transform: translate3d(0, calc(var(--sy, 0) * 44px), 0);
  will-change: transform;
}

@media print {
  .reveal-on [data-reveal] { opacity: 1 !important; transform: none !important; }
  .reveal-on .stagger > *, .reveal-on .head[data-reveal] > * { opacity: 1 !important; transform: none !important; }
  .reveal-on .rank-rows tbody tr { opacity: 1 !important; transform: none !important; }
  .progress, .rail { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  /* 노출은 남기고 **움직임만** 없앤다 — 위치를 알리는 일은 모션이 아니다 */
  .reveal-on [data-reveal], .reveal-on .stagger > *,
  .reveal-on .head[data-reveal] > *, .reveal-on .rank-rows tbody tr {
    opacity: 1;
    transform: none;
  }
  .reveal-on [data-reveal].in, .reveal-on .stagger.in > *,
  .reveal-on .head[data-reveal].in > *, .reveal-on .rank-rows.in tbody tr {
    transition: none;
    transition-delay: 0s;
  }
  .rail i, .rail span, .rail { transition: none; }
  .hero::before { transform: none; will-change: auto; }
}

/* ══ 2026-09-03 · 접근성 보강 ═══════════════════════════════════════════
   둘 다 "없어도 페이지가 온전한" 것이라 여기 있다(이 파일의 경계). */

/* 본문 바로가기 — 상단 내비를 매번 지나지 않게. 포커스를 받을 때만 선다.
   이 규칙이 없으면 링크가 맨 위에 그냥 보일 뿐이라 읽는 데 지장이 없다. */
.skip {
  position: absolute;
  left: var(--space-4);
  top: -3rem;
  z-index: 60;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font-weight: var(--weight-semibold);
  font-size: var(--text-body);
  text-decoration: none;
  box-shadow: var(--shadow-overlay);
}
.skip:focus { top: var(--space-2); }

/* 탭 타깃 24px (WCAG 2.2 AA 2.5.8). **패딩으로 키우지 않는다** — `--chrome-h`와
   `.hero`의 padding-top이 한 쌍이라 내비가 자라면 히어로가 어긋난다.
   가상 요소로 히트 영역만 넓히면 레이아웃은 그대로다(실측: 20 → 31px). */
.chrome nav a:not(.btn),
footer nav a { position: relative; }
.chrome nav a:not(.btn)::after,
footer nav a::after { content: ""; position: absolute; inset: -6px -4px; }

/* ══ 2026-09-03 · 잘린 열의 신호 ═══════════════════════════════════════════
   오른쪽 끝 페이드. **뷰포트 폭으로 켜지 않는다.** "표가 그릇을 넘는가"는 뷰포트가
   아니라 **그릇의 질문**이고, 둘은 어긋난다: 실측하면 랜딩 표는 421px에서 넘침을
   멈추는데 그건 ADS 눈금에 없는 값이라(`test_design_layout`), 가장 가까운 눈금
   (30rem = 480px)을 쓰면 421~480px 구간에 **없는 잘림을 가리키는** 페이드가 선다.
   그래서 조건을 그대로 잰다 — `app.js` ⑦이 `scrollWidth`를 보고 `.cut`을 붙인다.
   JS가 없으면 페이드가 없을 뿐 스크롤은 그대로다(§6-4. 스크롤 자체는 index.html에
   있어 이 파일이 실패해도 열이 잘리지 않는다).
   순위 페이지는 JS가 없는 면이라 거기서는 48rem 컷을 그대로 쓴다. */
.tablewrap.cut {
  -webkit-mask-image: linear-gradient(to right,
      black calc(100% - var(--space-5)), transparent);
  mask-image: linear-gradient(to right,
      black calc(100% - var(--space-5)), transparent);
}
