/*
  역할 기반 디자인 토큰.

  색은 "값"이 아니라 "역할"로 나눈다. 값이 같아도 역할이 다르면 토큰을 나눠서
  (예: --surface-card / --surface-control / --text-on-accent 는 모두 지금 white)
  이후 [data-theme="dark"] 블록 하나만 덮어써도 테마가 전환되게 한다.

  라운드는 테마 전환 대상이 아니므로 값 기준으로 묶는다.
*/
:root {
  /* 표면 */
  --bg: #f5f7fb;
  --bg-glow: #dceaff;
  --surface: rgb(255 255 255 / 93%);
  --surface-card: white;
  --surface-control: white;
  --surface-muted: #fbfcff;
  --surface-subtle: #f7f9fd;
  --surface-accent: #eef3fb;
  --surface-row-hover: #f5f8ff;
  --surface-table-header: #c7dcfa;
  --surface-table-group: #dee9ff;
  --surface-table-body: white;
  --surface-code: #111a2b;
  --surface-code-control: #1d2b45;
  --surface-code-control-hover: #26365a;
  --surface-inverse: #152033;
  --surface-sidebar: white;
  --surface-topbar: rgb(255 255 255 / 92%);
  --surface-nav-active: #eef3ff;
  --surface-scrim: rgb(21 32 51 / 45%);

  /* 텍스트 */
  --text: #152033;
  --text-heading: #33415a;
  --text-body: #405069;
  --text-summary: #4f5c70;
  --text-muted: #526176;
  --text-subtle: #63718a;
  --text-on-accent: white;
  --text-on-inverse: white;
  --text-code: #edf3ff;
  --text-code-control: #dbe6ff;

  /* 강조 */
  --accent: #3867d6;
  --accent-strong: #193a86;
  --accent-link: #315cbf;
  --accent-deep: #244caa;
  --accent-border: #3566da;
  --accent-tint: #eef3ff;
  --accent-tint-strong: #c7dcfa;
  --accent-nav: #eaf0ff;

  /*
    경계. --line-control(버튼·탭·행·카드)과 --line-input(입력)은 UI 컴포넌트
    테두리라 WCAG 1.4.11 의 비텍스트 3:1 을 받는다. 안쪽 컨트롤 면과 바깥
    패널 면 양쪽에 대해 3:1 을 넘겨야 해서, 장식용인 --line/--line-panel
    보다 눈에 띄게 진하다. 서열은 입력 > 컨트롤 > 패널 > 일반 순으로 진하다.
  */
  --line: #e2e7f0;
  --line-panel: #dce3f0;
  --line-control: #8694ab;
  --line-input: #808fa7;
  --line-accent: #b4c6de;
  --line-code-control: #3a4a68;
  --line-code-control-hover: #5f83e0;

  /* 초점 */
  --focus-ring: #5887f2;
  --focus-ring-soft: #dceaff;

  /* 상태 */
  --status-neutral-bg: #edf0f5;
  --status-neutral-text: #405069;
  --status-success-bg: #e4f6eb;
  --status-success-text: #1f7143;
  --status-success-strong: #247145;
  --status-warning-bg: #fff1c9;
  --status-warning-text: #835c00;
  --status-edited-bg: #ffedd5;
  --status-edited-text: #9a3412;
  --status-danger-bg: #fde8eb;
  --status-danger-text: #a4293e;
  --status-danger-strong: #af2d42;

  /* 그림자 */
  --shadow-panel: 0 1.2rem 3.5rem rgb(34 58 99 / 7%);
  --shadow-card: 0 0.2rem 0.55rem rgb(34 58 99 / 7%);
  --shadow-card-hover: 0 0.65rem 1.5rem rgb(34 58 99 / 15%);
  --shadow-row-hover: 0 0.3rem 0.9rem rgb(34 58 99 / 12%);
  --ring-selected: 0 0 0 3px var(--accent-tint);

  /* 라운드 */
  --radius-panel: 1.1rem;
  --radius-card: 0.8rem;
  --radius-md: 0.75rem;
  --radius-control: 0.6rem;
  --radius-code: 0.55rem;
  --radius-xs: 0.4rem;
  --radius-chip: 0.35rem;
  --radius-focus: 0.25rem;
  --radius-pill: 999px;

  /*
    레이아웃. 색이 아니라 테마 전환 대상이 아니므로 :root 한 곳에만 둔다.
    탑바는 sticky 라 앵커로 이동하면 대상 상단을 덮는다. --anchor-offset 만큼
    scroll-margin 을 줘 그만큼 위를 비운다. --topbar-height 기본값은 탑바
    min-height 이고, 폭에 따라 줄바꿈되는 실제 높이는 main.js 가 덮어쓴다.
  */
  --topbar-height: 3.9rem;
  --anchor-offset: calc(var(--topbar-height) + 1rem);

  /*
    타이포. 한글은 Pretendard 가 맡는다. Inter 만 앞세우면 라틴 글리프만 받고
    한글은 시스템 글꼴(맑은 고딕 등)로 떨어져 한 문장 안에서 서체가 갈린다.
    Pretendard 의 라틴은 Inter 계열이라 그 뒤의 폴백과도 결이 맞는다.
  */
  --font-sans:
    "Pretendard Variable", Pretendard, Inter, ui-sans-serif, system-ui,
    sans-serif;
  --font-mono: Consolas, "Cascadia Code", monospace;
}

/*
  다크 테마. 라이트가 기본이고 사용자가 명시적으로 켤 때만 적용된다
  (OS prefers-color-scheme 은 따르지 않는다).

  라이트에서 "밝을수록 위 계층"이던 표면 순서를 다크에서는 뒤집어,
  읽는 영역(표 본문 등)이 가장 어둡고 헤더·구분 셀이 밝아지게 맞췄다.
*/
:root[data-theme="dark"] {
  /*
    토큰이 닿지 않는 브라우저 기본 UI(스크롤바, select 드롭다운 목록, 검색창
    지우기 버튼, 자동완성 배경)까지 어둡게 맞춘다. 이게 없으면 다크 화면에
    흰 스크롤바와 흰 드롭다운만 남는다.
  */
  color-scheme: dark;

  /* 표면 */
  --bg: #0b1020;
  --bg-glow: #1b2a4a;
  --surface: rgb(20 27 46 / 93%);
  --surface-card: #1a2236;
  --surface-control: #1a2236;
  --surface-muted: #161d30;
  --surface-subtle: #151c2e;
  --surface-accent: #1b2540;
  --surface-row-hover: #202a42;
  --surface-table-header: #24345c;
  --surface-table-group: #1e2c4d;
  --surface-table-body: #131a2b;
  --surface-inverse: #e8edf7;
  --surface-sidebar: #10172a;
  --surface-topbar: rgb(16 23 41 / 92%);
  --surface-nav-active: #1e2c4d;
  --surface-scrim: rgb(4 8 18 / 62%);

  /* 텍스트 */
  --text: #eef2fa;
  --text-heading: #d3dcee;
  --text-body: #c2cde3;
  --text-summary: #b3c0d8;
  --text-muted: #9fadc6;
  --text-subtle: #8b99b4;
  --text-on-accent: #08101c;
  --text-on-inverse: #0b1020;

  /* 강조 */
  --accent: #7c9cff;
  --accent-strong: #a9c0ff;
  --accent-link: #9ab4ff;
  --accent-deep: #b8caff;
  --accent-border: #7591db;
  --accent-tint: #1b2748;
  --accent-tint-strong: #24345c;
  --accent-nav: #1a2440;

  /* 경계 — 라이트와 같은 이유로 컨트롤·입력 테두리만 3:1 선까지 밝힌다. */
  --line: #232d45;
  --line-panel: #26304a;
  --line-control: #606d8b;
  --line-input: #657191;
  --line-accent: #33436b;

  /*
    초점. 라이트의 --focus-ring 이 1.4.11 을 맞추려 어두워졌는데, 여기서
    재정의하지 않으면 다크가 그 어두운 값을 상속해 링이 배경에 묻힌다.
    다크 표면 위에서 이미 7~8:1 이 나오던 값을 그대로 고정해 둔다.
  */
  --focus-ring: #8aabf6;
  --focus-ring-soft: #1e2c4d;

  /* 상태 */
  --status-neutral-bg: #232c40;
  --status-neutral-text: #c2cde3;
  --status-success-bg: #10301f;
  --status-success-text: #6fdba0;
  --status-success-strong: #7fe3ad;
  --status-warning-bg: #35290a;
  --status-warning-text: #f0c964;
  --status-edited-bg: #431407;
  --status-edited-text: #fdba74;
  --status-danger-bg: #3a1520;
  --status-danger-text: #ff9aad;
  --status-danger-strong: #ff8fa3;

  /* 그림자 */
  --shadow-panel: 0 1.2rem 3.5rem rgb(0 0 0 / 45%);
  --shadow-card: 0 0.2rem 0.55rem rgb(0 0 0 / 35%);
  --shadow-card-hover: 0 0.65rem 1.5rem rgb(0 0 0 / 50%);
  --shadow-row-hover: 0 0.3rem 0.9rem rgb(0 0 0 / 40%);

  /*
    유지: 코드 블록 계열(--surface-code, --surface-code-control,
    --surface-code-control-hover, --text-code, --text-code-control,
    --line-code-control, --line-code-control-hover)은 라이트에서도 이미
    다크라 여기서 재정의하지 않는다. 코드면은 양 테마가 공유하는 면이라
    :root 한 곳에만 값이 있고, 테마 전환의 영향을 받지 않는다.
  */
}

:root {
  /* 기본은 라이트다. OS 가 다크여도 사용자가 켜기 전까지는 따라가지 않는다. */
  color-scheme: light;
  color: var(--text);
  background: var(--bg);
  font-family: var(--font-sans);
}

* {
  box-sizing: border-box;
}

/* nav 처럼 display 를 지정한 요소에서도 화면 전환이 확실히 적용되어야 한다. */
[hidden] {
  display: none !important;
}

.visually-hidden {
  position: absolute;
  overflow: hidden;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  white-space: nowrap;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-width: 320px;
  background:
    radial-gradient(circle at top right, var(--bg-glow) 0, transparent 28rem),
    var(--bg);
}

code {
  font-family: var(--font-mono);
}

.skip-link {
  position: fixed;
  z-index: 10;
  top: 0.5rem;
  left: 0.5rem;
  padding: 0.65rem 0.85rem;
  border-radius: 0.5rem;
  background: var(--surface-inverse);
  color: var(--text-on-inverse);
  transform: translateY(-150%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* ── 앱 셸: 고정 사이드바 + sticky 탑바 ─────────────────────────── */

.app-shell {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  min-height: 100vh;
}

/*
  세로 flex 인 것은 맨 아래 게시 지표 한 자리를 밀어내리기 위해서다. 브랜드·내비는
  흐름 그대로 위에서부터 쌓이고, 지표만 margin-top: auto 로 바닥에 붙는다.
*/
.sidebar {
  position: sticky;
  z-index: 20;
  top: 0;
  display: flex;
  overflow-y: auto;
  height: 100vh;
  flex-direction: column;
  padding: 1.4rem 1.1rem;
  border-right: 1px solid var(--line-panel);
  background: var(--surface-sidebar);
}

/*
  브랜드는 목록 화면으로 돌아가는 홈 링크다. 링크가 되어도 배치는 그대로 두고,
  밑줄·링크 색만 지워 기존 표시를 지킨다.
*/
.brand {
  display: flex;
  gap: 0.7rem;
  align-items: center;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.1rem;
  color: inherit;
  text-decoration: none;
}

/* 클릭 가능하다는 신호는 이름 색만 바꿔 은은하게 준다. */
.brand:hover .brand__name {
  color: var(--accent-strong);
}

.brand:focus-visible {
  border-radius: var(--radius-control);
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

.brand__mark {
  display: grid;
  width: 2.4rem;
  height: 2.4rem;
  flex: 0 0 auto;
  border-radius: var(--radius-control);
  background: var(--accent);
  color: var(--text-on-accent);
  font-size: 0.86rem;
  font-weight: 800;
  place-items: center;
}

.brand__text {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
}

.brand__name {
  color: var(--text);
  font-size: 0.92rem;
  font-weight: 800;
  line-height: 1.25;
}

.brand__tagline {
  color: var(--text-subtle);
  font-size: 0.78rem;
}

/* 전역 nav 는 flex 라 라벨과 목록이 가로로 늘어선다. 사이드바는 세로 스택이다. */
.sidebar-nav {
  display: block;
}

.nav-label {
  margin: 0 0 0.5rem 0.35rem;
  color: var(--text-subtle);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nav-list {
  display: grid;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
  그룹 사이 간격. 소개는 카탈로그 그룹 밖 항목이라, 그룹 라벨이 바로 위 항목에
  눌려 붙으면 둘이 한 덩어리로 읽힌다. 브랜드 아래·지표 위가 쓰는 1.1rem 리듬에
  맞추되, 라벨이 자기 아래 목록에 붙어 보이도록 위쪽을 조금 더 벌린다
  (라벨 자신의 아래 여백은 0.5rem 이다).
*/
.nav-list + .nav-label {
  margin-top: 1.3rem;
}

.nav-link {
  display: block;
  padding: 0.6rem 0.7rem;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  color: var(--text-muted);
  font-size: 0.92rem;
  font-weight: 700;
  text-decoration: none;
}

.nav-link:hover {
  background: var(--surface-accent);
  color: var(--text-heading);
}

.nav-link[aria-current="page"] {
  border-color: var(--line-accent);
  background: var(--surface-nav-active);
  color: var(--accent-strong);
}

.nav-link:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

.app-main {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.topbar {
  position: sticky;
  z-index: 15;
  top: 0;
  display: flex;
  min-height: 3.9rem;
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
  align-items: center;
  padding: 0.75rem clamp(1rem, 3vw, 2rem);
  border-bottom: 1px solid var(--line-panel);
  background: var(--surface-topbar);
  backdrop-filter: blur(12px);
}

.topbar__menu {
  display: none;
  padding: 0.45rem 0.65rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-control);
  background: var(--surface-control);
  color: var(--text-heading);
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
}

.topbar__menu:hover {
  border-color: var(--accent-border);
}

.topbar__menu:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

.topbar__title {
  overflow: hidden;
  margin: 0;
  font-size: 1.15rem;
  letter-spacing: -0.01em;
  line-height: 1.3;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar__actions {
  display: flex;
  margin-left: auto;
  gap: 0.5rem;
  align-items: center;
}

.theme-toggle {
  display: inline-flex;
  gap: 0.4rem;
  align-items: center;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-control);
  background: var(--surface-control);
  color: var(--text-heading);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.theme-toggle:hover {
  border-color: var(--accent-border);
  color: var(--accent-strong);
}

.theme-toggle:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

.theme-toggle__icon {
  font-size: 0.95rem;
  line-height: 1;
}

.content {
  width: min(1180px, 100%);
  padding: clamp(1rem, 3vw, 2rem) clamp(1rem, 3vw, 2rem) 3rem;
  margin: 0 auto;
}

/* 드로어가 열렸을 때만 콘텐츠를 덮는다. */
.sidebar-scrim {
  position: fixed;
  z-index: 19;
  background: var(--surface-scrim);
  inset: 0;
}

h1,
h2,
h3,
h4,
h5,
p {
  margin-top: 0;
}

h2 {
  font-size: 1.35rem;
}

h4 {
  margin-bottom: 0.7rem;
}

h5 {
  margin-bottom: 0.55rem;
  color: var(--text-heading);
  font-size: 0.86rem;
}

.muted {
  color: var(--text-muted);
  line-height: 1.65;
}

.lead-copy {
  color: var(--text-heading);
  font-size: 1.04rem;
  line-height: 1.7;
}

nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

nav a,
.link-button {
  color: var(--accent-link);
  font: inherit;
  font-weight: 700;
  text-decoration: none;
}

.link-button:hover {
  text-decoration: underline;
}

.link-button[aria-current="true"] {
  color: var(--accent-strong);
}

.panel {
  margin: 1rem 0;
  padding: clamp(1.15rem, 4vw, 2.2rem);
  border: 1px solid var(--line-panel);
  border-radius: var(--radius-panel);
  background: var(--surface);
  box-shadow: var(--shadow-panel);
}

/*
  앵커·초점으로 이동하는 목적지는 모두 같은 여백을 쓴다. sticky 탑바가 대상
  상단을 덮지 않게 하는 값이라 한 곳에 모아 두고 --anchor-offset 만 조정한다.
*/
.panel,
#main-content,
#catalog-screen,
.spec-panel,
[data-detail-heading] {
  scroll-margin-top: var(--anchor-offset);
}

.panel-heading {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
}

[data-detail-heading] {
  margin-bottom: 0.5rem;
}

/*
  상세 진입 시 제목으로 초점을 옮기는 건 프로그램적 이동이다. :focus 로 두면
  마우스로 카드를 눌렀을 때도 링이 그려지므로, 키보드 탐색일 때만 보이게 한다.
*/
[data-detail-heading]:focus-visible {
  border-radius: var(--radius-focus);
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.2rem;
}

/*
  상세 패널 제목 줄의 오른쪽 끝에 놓이는 유일한 목록 복귀 장치다. 화면을
  되돌리는 조작이라 본문 링크보다 버튼으로 읽혀야 해서 spec-tab 과 같은 알약
  컨트롤 꼴을 쓴다. 마크업은 앵커 그대로라 새 탭 열기 같은 링크 기능은 유지된다.
*/
.detail-back {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-pill);
  background: var(--surface-control);
  color: var(--accent-link);
  font-size: 0.9rem;
  font-weight: 700;
  white-space: nowrap;
  text-decoration: none;
}

/* 밑줄 대신 면과 테두리로 반응한다 — 알약 컨트롤은 밑줄이 어울리지 않는다. */
.detail-back:hover {
  border-color: var(--accent-border);
  background: var(--surface-accent);
  color: var(--accent-strong);
}

.detail-back:active {
  background: var(--accent-tint-strong);
}

.detail-back:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

#catalog-title:focus-visible,
.spec-panel:focus-visible {
  border-radius: var(--radius-xs);
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.2rem;
}

/*
  상태 문구는 어느 화면에서도 같은 자리에 있도록 사이드바 맨 아래에 둔다. 브랜드가
  위에서 구분선으로 갈라져 있는 것과 대칭으로 아래에도 같은 1px 선을 그어, 이동
  항목과 게시 수치가 서로 다른 층이라는 것을 남긴다.

  margin-top: auto 는 내비가 짧을 때 지표를 바닥에 붙인다. 사이드바가 넘칠 만큼
  내용이 길어지면 auto 가 0이 되어 내비 바로 아래로 따라 올라가고, 사이드바 자체가
  스크롤되므로 어느 쪽에서도 잘리지 않는다.
*/
#catalog-status {
  margin: auto 0 0;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
  color: var(--text-subtle);
  font-size: 0.86rem;
  line-height: 1.4;
  white-space: normal;
}

/*
  게시 지표는 항목마다 줄을 바꿔 쌓는다. 가운데 점으로 이어 붙이면 한 줄이 길게
  늘어서고 좁은 폭에서는 어중간하게 접혀, 어디까지가 한 항목인지 끊기지 않는다.
  구분자 역할은 줄바꿈이 대신한다.

  사이드바 폭(≈15rem)에서는 라벨·값을 두 열로 놓을 수 없다 — 영문 라벨이 길어
  Basic tests passing 과 0/5000 implementations 가 양쪽 다 접힌다. 그래서 라벨을
  값 위에 얹고, 라벨 표기는 바로 위 .nav-label 의 사이드바 관례를 그대로 쓴다.
  값은 모두 같은 왼쪽 끝에서 시작해 세로로 읽힌다.
*/
.status-metrics {
  display: grid;
  gap: 0.5rem;
  margin: 0;
}

/*
  라벨은 UI 크롬이라 상태 색을 타지 않는다. dl 이 서는 것은 ready 상태뿐이고, 그때
  다섯 줄이 통째로 상태 색이면 수치가 아니라 색이 먼저 읽힌다. 상세 화면 .metrics
  가 dt 만 muted 로 내려 두는 것과 같은 처리다.
*/
.status-metrics dt {
  color: var(--text-muted);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.status-metrics dd {
  margin: 0;
  font-weight: 700;
}

#catalog-status[data-state="error"] {
  color: var(--status-danger-strong);
}

#catalog-status[data-state="ready"] {
  color: var(--status-success-strong);
}

#result-count {
  height: max-content;
  margin: 0;
  padding: 0.4rem 0.65rem;
  border-radius: var(--radius-pill);
  background: var(--accent-tint);
  color: var(--accent-link);
  font-size: 0.85rem;
  font-weight: 700;
  white-space: nowrap;
}

.filters {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 0.75rem;
  margin: 1.25rem 0;
}

label {
  display: grid;
  gap: 0.4rem;
  color: var(--text-heading);
  font-size: 0.88rem;
  font-weight: 700;
}

input,
select {
  width: 100%;
  padding: 0.75rem 0.85rem;
  border: 1px solid var(--line-input);
  border-radius: var(--radius-control);
  background: var(--surface-control);
  color: inherit;
  font: inherit;
}

input:focus,
select:focus {
  border-color: var(--accent);
  outline: 3px solid var(--focus-ring-soft);
}

.list-view-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.65rem;
  align-items: center;
}

/*
  선택지가 둘뿐인 전환은 같은 알약 버튼 쌍을 쓴다. 목록 보기(카드형·리스트형)와
  탑바의 이름 표기(한글·영문)가 그 규칙을 공유하므로 선택자만 함께 둔다.
*/
.view-toggle,
.name-toggle {
  display: flex;
  gap: 0.2rem;
  padding: 0.2rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-pill);
  background: var(--surface-control);
}

.view-toggle__button,
.name-toggle__button {
  padding: 0.35rem 0.8rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.view-toggle__button:hover,
.name-toggle__button:hover {
  color: var(--accent-strong);
}

.view-toggle__button[aria-pressed="true"],
.name-toggle__button[aria-pressed="true"] {
  background: var(--accent);
  color: var(--text-on-accent);
}

.view-toggle__button:focus-visible,
.name-toggle__button:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

.algorithm-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 0.85rem;
}

.algorithm-rows {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.algorithm-row {
  display: flex;
  gap: 0.9rem;
  align-items: center;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-control);
  background: var(--surface-card);
  cursor: pointer;
  transition:
    border-color 0.18s ease,
    background 0.18s ease,
    box-shadow 0.18s ease;
}

.algorithm-row:hover {
  border-color: var(--accent-border);
  background: var(--surface-row-hover);
  box-shadow: var(--shadow-row-hover);
}

.algorithm-row:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.2rem;
}

.algorithm-row[data-selected="true"] {
  border-color: var(--accent-border);
  box-shadow: var(--ring-selected);
}

.algorithm-row[data-selected="true"]:hover {
  box-shadow:
    var(--ring-selected),
    var(--shadow-row-hover);
}

/* 행은 한 줄 안에 들어가야 하므로 이름·요약 모두 말줄임 처리한다. */
.algorithm-row h3 {
  overflow: hidden;
  flex: 0 0 12rem;
  margin: 0;
  font-size: 0.98rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.algorithm-row__summary {
  overflow: hidden;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  color: var(--text-summary);
  font-size: 0.88rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.algorithm-row__meta {
  flex: 0 0 auto;
  margin: 0;
  color: var(--text-subtle);
  font-size: 0.8rem;
  white-space: nowrap;
}

.algorithm-card {
  display: flex;
  min-height: 155px;
  flex-direction: column;
  padding: 1rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-card);
  background: var(--surface-card);
  box-shadow: var(--shadow-card);
  cursor: pointer;
  transition:
    transform 0.18s ease,
    border-color 0.18s ease,
    box-shadow 0.18s ease;
}

.algorithm-card:hover {
  transform: translateY(-0.2rem);
  border-color: var(--accent-border);
  box-shadow: var(--shadow-card-hover);
}

.algorithm-card:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.2rem;
}

.algorithm-card[data-selected="true"] {
  border-color: var(--accent-border);
  box-shadow: var(--ring-selected);
}

.algorithm-card[data-selected="true"]:hover {
  box-shadow:
    var(--ring-selected),
    var(--shadow-card-hover);
}

.algorithm-card h3 {
  margin-bottom: 0.45rem;
}

/*
  제목 자체가 상세 링크다. .link-button 의 font: inherit 덕분에 h3 의 크기·굵기는
  그대로 유지되고 링크 색과 hover 밑줄만 얹힌다.
*/
.algorithm-card h3 .link-button:focus-visible,
.algorithm-row h3 .link-button:focus-visible {
  border-radius: var(--radius-focus);
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.2rem;
}

.algorithm-card__summary {
  flex: 1;
  color: var(--text-summary);
  line-height: 1.5;
}

/* 하단 링크가 없어졌으므로 meta 아래 간격도 함께 걷어낸다. */
.algorithm-card__meta {
  margin-bottom: 0;
  color: var(--text-subtle);
  font-size: 0.82rem;
}

.empty-state {
  margin: 0;
  padding: 1.1rem;
  border: 1px dashed var(--line-input);
  border-radius: var(--radius-card);
  color: var(--text-muted);
  text-align: center;
}

/*
  카탈로그 2.1부터 상세 본문은 제목이 선 뒤에 따로 도착한다. 그 자리를 감싸는
  칸이라 로딩·실패 안내와 탭 묶음이 같은 폭을 쓴다. 원격 자산 카드와 같은 어법이다.
*/
.detail-body,
.search-aid-notice {
  display: grid;
  gap: 0.7rem;
  min-width: 0;
}

/*
  검색 본문을 못 받았다는 안내는 결과 위에 선다. 카드형 목록은 auto-fill 격자라
  한 칸에 끼면 안내가 카드처럼 읽히므로 줄 전체를 차지하게 둔다.
*/
.search-aid-notice {
  grid-column: 1 / -1;
  margin-bottom: 0.2rem;
}

/* 정의 목록이 맨바닥에 뜨지 않도록 카드로 묶어 소속을 준다. */
.metrics {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.45rem 1rem;
  margin: 0 0 1.1rem;
  padding: 1rem 1.15rem;
  border: 1px solid var(--line-accent);
  border-radius: var(--radius-card);
  background: var(--surface-accent);
}

/* 카드 배경이 진해진 만큼 보조 텍스트도 한 단계 올려 AA 를 유지한다. */
.metrics dt {
  color: var(--text-muted);
}

.metrics dd {
  margin: 0;
  font-weight: 700;
}

.detail-section,
.alternatives {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

.guidance-grid,
.contract-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.8rem;
}

/*
  계약 카드는 3개다. 2열이 되는 구간이 있으면 반드시 한 칸이 비므로 2열을 아예
  두지 않고 3열 → (700px 이하) 1열로만 접는다.
*/
.contract-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 1.15rem;
}

.guidance-card,
.contract-card,
.source-card,
.test-case {
  min-width: 0;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--surface-muted);
}

/* metrics·표 래퍼와 같은 톤으로 맞춰 계약 영역 전체가 한 덩어리로 읽히게 한다. */
.contract-card {
  border-color: var(--line-accent);
  background: var(--surface-accent);
}

.guidance-card ul {
  margin-bottom: 0;
  padding-left: 1.15rem;
  color: var(--text-body);
  line-height: 1.6;
}

/*
  사용 시나리오. 위 격자와 달리 문장이 이어져 읽히는 산문이라 카드 칸에 넣지
  않고 개요 탭의 폭을 그대로 쓴다. 대신 줄 길이는 제한한다 — 전폭에서 줄이
  100자를 넘으면 다음 줄 첫머리를 찾다가 같은 줄을 두 번 읽는다. 46rem 은
  한글 본문 40자 안팎이 되는 폭이다.

  네 단은 새 시각 언어를 만들지 않고 .detail-section 의 구분선 관례를 그대로
  물려받는다. 단 사이에 같은 1px 선을 긋고, 소제목만 .roadmap-goal 과 같은
  강조색으로 올려 어디서 단이 갈리는지 스캔으로 잡히게 한다.
*/
/*
  단 소제목이 강조색이라 기본 간격으로는 섹션 제목보다 앞서 읽힌다. 첫 단만
  한 칸 더 떨어뜨려 "사용 시나리오"가 한 단계 위라는 것이 남게 한다.
*/
.scenario > h4 {
  margin-bottom: 1.15rem;
}

.scenario-steps {
  display: grid;
  gap: 1.2rem;
}

.scenario-step + .scenario-step {
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
}

.scenario-step h5 {
  margin-bottom: 0.45rem;
  color: var(--accent-deep);
  font-size: 0.92rem;
  font-weight: 750;
}

/*
  문단 하나가 자산 배열의 원소 하나다. 원문이 여러 줄에 걸친 문단이면 문자열 안에
  줄바꿈이 그대로 남아 오므로, 지우지 않고 살려서 보인다. 폭에 따른 줄바꿈은 그대로
  일어나야 하니 pre 가 아니라 pre-wrap 이다.
*/
.scenario-step p {
  margin: 0;
  color: var(--text-body);
  line-height: 1.8;
  white-space: pre-wrap;
}

.scenario-step p + p {
  margin-top: 0.65rem;
}

.json-block,
.source-code {
  overflow: auto;
  margin: 0;
  border-radius: var(--radius-code);
  background: var(--surface-code);
  color: var(--text-code);
  font-size: 0.82rem;
  line-height: 1.55;
  tab-size: 2;
}

.json-block {
  max-height: 22rem;
  padding: 0.85rem;
}

.source-code {
  max-height: 38rem;
  padding: 1rem;
}

.json-block:focus-visible,
.source-code:focus-visible {
  outline: 3px solid var(--focus-ring);
}

/*
  코드 블록은 grid 아이템(.remote-asset-state 의 자식)이다. grid·flex 아이템의 자동
  최소 크기는 min-content 라, 안에 든 pre 의 가장 긴 줄이 그대로 최소 폭이 되어
  카드·본문을 밀어낸다 — pre 자체에 overflow:auto 를 줘도 부모가 이미 벌어진 뒤라
  스크롤이 생기지 않는다. 여기서 자동 최소 크기를 끊어야 pre 안쪽에서만 가로로
  스크롤되고 카드 경계가 지켜진다.
*/
.code-block {
  min-width: 0;
}

/*
  본문도 같은 계통이다. .content 는 .app-main(flex) 의 아이템이라 자동 최소 크기가
  걸리면 넓은 코드 한 줄이 페이지 전체를 가로로 흘린다. 가로 스크롤은 코드 블록
  안에서만 생겨야 한다.
*/
.content {
  min-width: 0;
}

/* 복사 버튼은 코드 위에 겹치지 않도록 코드 블록 상단 바에 오른쪽 정렬한다. */
.code-block__bar {
  display: flex;
  justify-content: flex-end;
  padding: 0.4rem 0.45rem;
  border-radius: var(--radius-code) var(--radius-code) 0 0;
  background: var(--surface-code);
}

.code-block > .json-block,
.code-block > .source-code {
  border-radius: 0 0 var(--radius-code) var(--radius-code);
}

.copy-button {
  padding: 0.3rem 0.65rem;
  border: 1px solid var(--line-code-control);
  border-radius: var(--radius-xs);
  background: var(--surface-code-control);
  color: var(--text-code-control);
  font: inherit;
  font-size: 0.76rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.copy-button:hover {
  border-color: var(--line-code-control-hover);
  background: var(--surface-code-control-hover);
}

.copy-button:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

.spec-tablist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 1.1rem 0 1.3rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--line);
}

.spec-tab {
  padding: 0.5rem 0.95rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-pill);
  background: var(--surface-control);
  color: var(--text-heading);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
}

/*
  테두리 색만 바꾸면 1px 선 하나라 실제로는 반응이 안 보인다. 면까지 함께
  바꿔 detail-back·nav-link 와 같은 hover 어법으로 맞춘다. 선택된 탭은 아래
  규칙이 같은 특이도로 뒤에 와서 강조 면을 그대로 유지한다.
*/
.spec-tab:hover {
  border-color: var(--accent-border);
  background: var(--surface-accent);
  color: var(--accent-strong);
}

.spec-tab:active {
  background: var(--accent-tint-strong);
}

.spec-tab[aria-selected="true"] {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--text-on-accent);
}

.spec-tab:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

/*
  .spec-tabs는 .detail-body(grid)의 항목이라 min-width 기본값 auto가 내용
  최소 폭을 고집한다. 구현 탭의 넓은 표가 들어오면 부모보다 넓게 부풀어
  넘침이 .table-wrap 안 가로 스크롤로 안 빠지고 페이지 전체가 옆으로
  흐른다. 패널과 같은 이유로 사슬 전체에 0을 잇는다.
*/
.spec-tabs,
.spec-panel {
  min-width: 0;
}

.spec-panel > .detail-section:first-child,
.spec-panel > .alternatives:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.test-vector {
  margin-top: 0.7rem;
  border: 1px solid var(--line-panel);
  border-radius: var(--radius-md);
  background: var(--surface-card);
}

.test-vector summary {
  padding: 0.85rem 1rem;
  cursor: pointer;
  color: var(--text-heading);
  font-weight: 750;
}

.test-case {
  margin: 0 0.85rem 0.85rem;
}

/*
  가로로 흐르는 목록이라 li 마커가 앞 항목 텍스트 위로 겹쳐 붙었다. 마커를
  지우고 항목 경계는 칩 배경이 대신한다 (.error-code-list 와 같은 관례).
*/
.alternative-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 0.55rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.alternative-list .link-button {
  display: inline-block;
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-pill);
  background: var(--accent-nav);
}

.subheading {
  margin-top: 1.5rem;
}

/*
  소스 카드 격자. 좁은 폭에서는 한 줄에 하나씩 쌓이고, 넓어지면 두 칸이 된다.
  카드 순서는 C# → Java → JavaScript 이고(main.ts 가 정한다), JavaScript 카드만
  두 칸을 다 써서 아래로 내려간다 — 그 안에 벡터 실행기가 들어가기 때문이다.
  auto-fit 대신 칸 수를 못박은 것은, 세 칸이 생기면 C#·Java 가 한 줄을 채우지
  못하고 어중간하게 벌어지기 때문이다.
*/
.source-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.9rem;
}

@media (min-width: 60rem) {
  .source-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.source-card--full {
  grid-column: 1 / -1;
}

.source-card {
  background: var(--surface-subtle);
}

/*
  schema 2.0은 언어 카드 안에 주 파일·동반 파일·기본 테스트를 함께 둔다. 파일마다
  원격 요청/오류 상태가 독립이므로 얇은 내부 카드 경계를 주어 어느 요청이 실패했는지
  경로와 바로 이어 읽히게 한다.
*/
.file-group-heading {
  margin-top: 1.25rem;
}

.source-file-list {
  display: grid;
  gap: 0.8rem;
}

.source-file {
  min-width: 0;
  padding: 0.8rem;
  border: 1px solid var(--line-panel);
  border-radius: var(--radius-md);
  background: var(--surface-card);
}

.source-file > h5 {
  margin-top: 0;
}

.source-path {
  overflow-wrap: anywhere;
  color: var(--text-subtle);
  font-family: var(--font-mono);
  font-size: 0.78rem;
}

/*
  원격 source/vector는 검증이 끝나기 전 코드·케이스 자리를 대신한다. 버튼을 코드
  위에 겹치지 않고 일반 문서 흐름에 두어 로딩 실패 뒤에도 키보드로 재시도할 수 있다.
*/
.remote-asset-state {
  display: grid;
  gap: 0.7rem;
  min-width: 0;
}

.remote-asset-state > .empty-state {
  margin: 0;
}

.source-retry-button {
  justify-self: start;
  padding: 0.45rem 0.8rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-pill);
  background: var(--surface-control);
  color: var(--text-heading);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.source-retry-button:hover {
  border-color: var(--line-accent);
  background: var(--surface-accent);
}

.source-retry-button:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

/*
  벡터 실행기. JavaScript 카드 안에서 소스 아래에 붙는다. 카드 안의 다른 층(파일
  묶음·기본 테스트)과 같은 얇은 경계·같은 여백을 쓰고, 판정 배지도 커버리지·상태
  표기에 이미 쓰는 .status-badge 어휘를 그대로 빌린다 — 새 색과 새 모양을 만들지
  않는다. min-width:0 은 안에 든 .json-block 이 카드를 밀어내지 않게 하는 같은 이유다.
*/
.source-run {
  min-width: 0;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line-panel);
}

.source-run__controls {
  display: grid;
  gap: 0.75rem;
  margin: 0.9rem 0;
}

.source-run__field {
  display: grid;
  gap: 0.35rem;
  min-width: 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-heading);
}

.source-run__field select,
.source-run__field textarea {
  min-width: 0;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--line-control);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  color: var(--text-body);
  font: inherit;
  font-weight: 400;
}

/*
  입력 JSON 편집 영역. 케이스마다 길이가 제각각이라 높이를 사용자가 잡게 둔다 —
  세로로만 늘였다 줄일 수 있고(가로는 카드 폭을 따라야 한다), min-height 로 바닥을
  받쳐 두어 끝까지 끌어내려도 한 줄짜리로 무너지지 않는다. 기본 높이는 textarea 의
  rows 가 정한다(main.ts).
*/
.source-run__field textarea {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.82rem;
  line-height: 1.5;
  resize: vertical;
  min-height: 6rem;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

.source-run__field select:focus-visible,
.source-run__field textarea:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.1rem;
}

.source-run__field select:disabled,
.source-run__field textarea:disabled {
  opacity: 0.6;
}

.source-run__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}

.source-run__actions .source-retry-button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.source-run__result {
  display: grid;
  gap: 0.5rem;
  min-width: 0;
}

.source-run__verdict {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}

.source-run__note {
  color: var(--text-muted);
  font-size: 0.82rem;
}

.source-run__block-label {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 700;
}

/*
  선택한 공식 케이스의 원래 입력과 기대값을 좌우 2열로 세운다.
  auto-fit + minmax 라 칸이 좁아지는 폭(모바일, 사이드바가 넓은 창)에서는 저절로
  한 열로 접혀 위아래로 쌓인다 — 접히는 지점을 미디어 쿼리로 따로 정하지 않는다.

  두 칸의 높이가 어긋나 보이지 않게 하는 것은 격자 두 겹이다. 바깥에서는 두 칸이
  같은 행에 서서 큰 쪽 높이로 stretch 되고, 칸 안에서는 제목(auto) 아래 본문(1fr)이
  남은 높이를 채운다. 그래서 한쪽 JSON 이 길어도 두 칸의 아래 선이 같이 떨어진다.
*/
.source-run__panes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 0.5rem 1rem;
  min-width: 0;
}

.source-run__pane {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0.3rem;
  min-width: 0;
}

/* 코드 블록은 복사 바 + pre 두 층이라, 남은 높이를 받는 쪽은 pre 여야 한다. */
.source-run__pane > .code-block {
  display: grid;
  grid-template-rows: auto 1fr;
}

/* 큰 케이스도 원본 값 영역 안에서 스크롤해, 아래의 편집 입력을 밀어내지 않는다. */
.source-run__case-values .json-block {
  max-height: 16rem;
}

.table-wrap {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  min-width: 650px;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.data-table th,
.data-table td {
  padding: 0.7rem;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

.data-table th {
  color: var(--text-muted);
  font-size: 0.78rem;
}

/*
  한글은 기본 줄바꿈 규칙에서 음절 사이 어디서나 끊긴다. 표는 열 폭이 좁아지기
  쉬워 "사용 가능"이 네 줄로 쪼개지는 일이 실제로 생겼다. keep-all 로 어절
  단위 줄바꿈만 허용한다. 긴 경로·JSON 은 아래 열 규칙에서 따로 푼다.
*/
.data-table th,
.data-table td {
  word-break: keep-all;
}

/*
  구현 탭의 코드 비교 표(언어·상태·공식 벡터 검증·엔트리포인트·소스)는
  엔트리포인트와 소스 경로가 길어 auto 레이아웃이 남은 열을 한계까지 눌러
  버린다. 값이 짧은 앞 3열은 아예 줄바꿈을 막아 자연 폭을 확보하고, 넘치는
  폭은 .table-wrap 의 가로 스크롤이 받는다. 계약·예시 표(.contract-table)와
  커버리지 표는 열 구성이 달라 제외한다. 26차에 상세가 탭형으로 통일되면서
  같은 표를 그리던 #compare-content 쪽 짝은 갈 곳이 없어져 걷어냈다.
*/
.spec-panel .data-table:not(.contract-table) th:nth-child(1),
.spec-panel .data-table:not(.contract-table) td:nth-child(1),
.spec-panel .data-table:not(.contract-table) th:nth-child(2),
.spec-panel .data-table:not(.contract-table) td:nth-child(2),
.spec-panel .data-table:not(.contract-table) th:nth-child(3),
.spec-panel .data-table:not(.contract-table) td:nth-child(3) {
  white-space: nowrap;
}

/* 계약·예시 표는 2~3열뿐이라 데이터 표의 최소 폭을 낮춘다. */
.contract-table {
  min-width: 32rem;
}

/* 흰 바탕에서 표 영역이 분리돼 보이도록 카드 컨테이너로 감싼다. */
.contract-table-wrap {
  border: 1px solid var(--line-accent);
  border-radius: var(--radius-card);
  background: var(--surface-accent);
}

/*
  캡션 좌우 인셋은 셀 패딩(0.7rem)과 같은 값으로 맞춰 아래 헤더·본문 첫 글자와
  세로선이 어긋나지 않게 한다. 아래 여백은 헤더 행과의 층위를 끊는 몫이다.
*/
.contract-table caption {
  padding: 0.75rem 0.7rem 0.7rem;
  color: var(--text-muted);
  font-size: 0.86rem;
  text-align: left;
}

/*
  층위는 배경 명도로 나눈다: 헤더 > 구분 셀 > 본문.
  본문만 가장 밝게 남겨 읽는 영역의 대비를 최대로 유지한다.
*/
.contract-table thead th {
  background: var(--surface-table-header);
  color: var(--accent-strong);
  font-weight: 750;
}

.contract-table th[scope="rowgroup"],
.contract-table th[scope="row"] {
  width: 1%;
  border-right: 1px solid var(--line-accent);
  background: var(--surface-table-group);
  color: var(--accent-strong);
  font-size: 0.82rem;
  font-weight: 750;
  white-space: nowrap;
}

.contract-table tbody td {
  border-bottom-color: var(--line-panel);
  background: var(--surface-table-body);
}

/*
  입력/출력 그룹 사이는 일반 행 경계보다 뚜렷하게 끊는다. 색은 표 래퍼 테두리·
  행헤더 우측선과 같은 --line-accent 를 쓴다. 인터랙션용인 --accent-border 는
  hover 대비를 맞추느라 진해져 장식선으로는 과했다. 위계는 2px 두께가 맡는다.
*/
.contract-table tbody tr[data-group-start] > * {
  border-top: 2px solid var(--line-accent);
}

.contract-table tbody tr[data-group-start]:first-child > * {
  border-top: 0;
}

/* 마지막 행 경계는 카드 테두리와 겹치므로 지운다. */
.contract-table tbody tr:last-child > * {
  border-bottom: 0;
}

.contract-table td code {
  overflow-wrap: anywhere;
  color: var(--text-heading);
  font-size: 0.84rem;
}

.example-block {
  margin-top: 1.5rem;
}

.example-block h5 {
  margin-bottom: 0.7rem;
}

.error-code-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.55rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 카드 배경이 진해져 기존 칩 틴트가 묻히므로 칩도 함께 올린다. */
.error-code {
  display: inline-block;
  padding: 0.2rem 0.45rem;
  border-radius: var(--radius-chip);
  background: var(--accent-tint-strong);
  color: var(--accent-strong);
  font-size: 0.82rem;
  font-weight: 700;
}

.error-code-detail {
  margin-left: 0.4rem;
  color: var(--text-subtle);
  font-size: 0.82rem;
}

.mutation-label {
  margin-bottom: 0.35rem;
  color: var(--text-heading);
  font-weight: 700;
}

.mutation-raw {
  margin-bottom: 0;
  color: var(--text-muted);
  font-size: 0.84rem;
}

.precondition-list {
  margin: 0;
  padding-left: 1.15rem;
  color: var(--text-body);
  line-height: 1.65;
}

.feedback-metrics {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0.65rem;
}

.feedback-metric {
  padding: 0.85rem;
  border-radius: var(--radius-md);
  background: var(--accent-tint);
  text-align: center;
}

.feedback-metric dt {
  color: var(--text-muted);
  font-size: 0.78rem;
}

.feedback-metric dd {
  margin: 0.2rem 0 0;
  color: var(--accent-deep);
  font-size: 1.35rem;
  font-weight: 800;
}

.feedback-status-summary {
  color: var(--text-body);
  font-size: 0.9rem;
}

/*
  확장 로드맵. 게시물이 아니라 계획이라 새 시각 언어를 만들지 않고, 계약 표
  (.contract-table)의 행 그룹과 피드백 지표 카드를 그대로 물려받는다. 아래는
  칸 수·두 줄짜리 구분 셀처럼 그 차이만 덮는 규칙이다.
*/
.roadmap-metrics {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* 계획이라는 사실이 표보다 먼저 읽혀야 커버리지의 게시 알고리즘과 헷갈리지 않는다. */
.roadmap-plan-note {
  padding: 0.7rem 0.85rem;
  border-left: 3px solid var(--accent-border);
  border-radius: var(--radius-md);
  background: var(--accent-tint);
  color: var(--text-body);
  font-size: 0.9rem;
}

.roadmap-goal {
  margin-bottom: 0.35rem;
  color: var(--accent-deep);
  font-weight: 750;
}

/* 분류 구분 셀은 이름과 "게시 4 → +2" 대비를 두 줄로 쌓는다. */
.roadmap-category-name,
.roadmap-category-delta {
  display: block;
}

.roadmap-category-delta {
  margin-top: 0.2rem;
  color: var(--text-muted);
  font-size: 0.76rem;
  font-weight: 700;
}

.roadmap-entry-title {
  margin-left: 0.4rem;
  color: var(--text-heading);
  font-weight: 700;
}

.roadmap-entry-note {
  color: var(--text-muted);
}

.status-badge {
  display: inline-block;
  padding: 0.2rem 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--status-neutral-bg);
  color: var(--status-neutral-text);
  font-size: 0.78rem;
  font-weight: 750;
  white-space: nowrap;
}

.status-badge[data-status="approved"] {
  background: var(--status-success-bg);
  color: var(--status-success-text);
}

.status-badge[data-status="pending"] {
  background: var(--status-warning-bg);
  color: var(--status-warning-text);
}

.status-badge[data-status="rejected"] {
  background: var(--status-danger-bg);
  color: var(--status-danger-text);
}

/* 수정 입력은 성공·실패 판정과 구분되는 주황 칩으로 양 테마에서 표시한다. */
.status-badge[data-status="edited"] {
  background: var(--status-edited-bg);
  color: var(--status-edited-text);
}

/*
  커버리지 "언어 상태" 열. 색만으로 상태를 알리지 않도록 기호를 함께 붙이고,
  기호의 뜻은 스크린리더용 보조 텍스트가 받는다. 배경·글자 쌍은 피드백 뱃지와
  같은 --status-* 를 그대로 재사용한다.
*/
.coverage-status-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 기본값은 중립이라 R(계획)은 따로 덮어쓰지 않는다. */
.coverage-status {
  display: inline-flex;
  gap: 0.3rem;
  align-items: center;
  padding: 0.2rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--status-neutral-bg);
  color: var(--status-neutral-text);
  font-size: 0.78rem;
  font-weight: 750;
  white-space: nowrap;
}

.coverage-status__mark {
  font-size: 0.72rem;
  line-height: 1;
}

.coverage-status[data-status="O"] {
  background: var(--status-success-bg);
  color: var(--status-success-text);
}

.coverage-status[data-status="I"] {
  background: var(--status-warning-bg);
  color: var(--status-warning-text);
}

.coverage-status[data-status="E"] {
  background: var(--status-danger-bg);
  color: var(--status-danger-text);
}

/*
  번들(러너 통짜) 소스를 참조하는 구현. 검증은 통과했지만 가져다 쓸 자립 소스가
  없어, 상태색 대신 중립색으로 되돌리고 기호도 ◫ 로 갈린다. 상태 규칙과 특이도가
  같으므로 반드시 뒤에 두어야 색을 덮는다.

  R(계획)도 중립색이라 색만으로는 둘이 같아 보인다. R 은 "아직 안 만든 것"이라
  중립이 맞고 앞으로 가장 흔한 칩이 되므로, 색을 옮기는 대신 번들 쪽에 파선
  테두리를 둬 갈랐다. 기호(○ 대 ◫)와 라벨이 이미 다르므로 이건 훑어볼 때를
  위한 보강이다. 자립 분리가 끝나 현재 번들은 0건이고, 되살아날 때를 대비한다.
*/
.coverage-status[data-source="bundled"] {
  border: 1px dashed var(--line-control);
  background: var(--status-neutral-bg);
  color: var(--status-neutral-text);
}

/*
  읽을거리 화면(소개·생각).

  카탈로그 화면들이 "찾아보는" 자리라면 여기는 "읽는" 자리다. 본문은 저장소의
  마크다운을 빌드가 옮긴 산문이라, 카드 격자 대신 문서 조판 규칙을 따로 둔다.
  다만 색·타이포·라운드는 새로 만들지 않고 기존 토큰만 쓴다 — 화면 하나가
  다른 사이트처럼 보이면 안 되고, 다크 전환도 토큰을 타야 저절로 따라온다.

  규칙은 화면이 아니라 문서에 건다(.doc-panel·.doc-body). 읽을거리가 둘로 늘어도
  조판이 갈라지지 않아야 같은 글로 읽힌다.

  폭은 세 단이다. 산문은 읽기 좋은 폭(--doc-measure), 표·코드는 한 단 넓은
  띠(--doc-wide), 가로로 긴 흐름도는 카드 폭 전체를 쓴다. 셋 다 카드 안에서
  가운데로 놓는다 — 왼쪽에 붙여 두면 넓은 화면에서 오른쪽에만 큰 여백이 남아
  글이 카드 구석으로 밀린 것처럼 보인다.
*/
.doc-panel {
  /* 한 줄에 한글 45자 안팎. 눈이 다음 줄 첫 글자를 놓치지 않는 폭이다. */
  --doc-measure: 46rem;
  /* 표·코드가 쓰는 띠. 산문보다 넓되 카드 안에 머문다. */
  --doc-wide: 60rem;
}

/*
  제목 줄도 본문 컬럼에 맞춘다. CTA 를 카드 오른쪽 끝에 붙이면 글은 가운데 있고
  버튼만 멀찍이 떠 있어 둘이 남남으로 읽힌다. 같은 컬럼 폭 안에서 좌우 끝을
  잡아야 본문 첫 줄과 버튼이 같은 세로선 위에 선다.
*/
.doc-panel__heading {
  width: min(100%, var(--doc-measure));
  align-items: center;
  margin-inline: auto;
  margin-bottom: 1.4rem;
}

/*
  화면 이름은 탑바가 크게 들고 있다. 여기 라벨은 어느 읽을거리를 보고 있는지
  본문 위에서 한 번 더 확인해 주는 자리라, 제목이 아니라 작은 눈썹 문구로 둔다 —
  본문 안의 제목이 이 화면에서 유일한 큰 제목이어야 문서의 층이 하나로 읽힌다.
*/
.doc-panel__eyebrow {
  /* 좌우는 건드리지 않는다. 문서 규칙에서 margin 단축은 가운데 축을 덮는다. */
  margin-block: 0;
  color: var(--text-subtle);
  font-size: 0.82rem;
  font-weight: 750;
  letter-spacing: 0.06em;
}

/*
  데모 진입. 이 사이트의 무기가 브라우저 실행이라, 이야기를 읽는 중에도 목록이
  한 번의 클릭 거리에 있어야 한다. 화면을 되돌리는 .detail-back 과 달리 앞으로
  나아가는 조작이라, 같은 알약 꼴을 쓰되 강조 면을 채워 무게를 다르게 준다.
*/
.about-cta {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  padding: 0.5rem 1.05rem;
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--text-on-accent);
  font-size: 0.9rem;
  font-weight: 750;
  white-space: nowrap;
  text-decoration: none;
}

.about-cta:hover {
  background: var(--accent-deep);
}

.about-cta:active {
  background: var(--accent-strong);
}

.about-cta:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

/*
  본문 상자. 화면 이동으로 들어왔을 때 초점을 받는 자리이기도 하다(tabindex="-1").
  마우스로 들어온 이동에서는 링을 그리지 않도록 :focus-visible 만 본다.
*/
.doc-body:focus-visible {
  border-radius: var(--radius-xs);
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.4rem;
}

/*
  폭은 상자가 아니라 각 요소가 정한다. 산문은 읽기 폭, 코드는 한 단 넓은 띠,
  흐름도는 카드 폭 전체다. 폭은 달라도 **가운데 축은 하나**다.

  그래서 이 아래에서 세로 여백을 줄 때는 margin 단축 속성을 쓰지 않는다.
  margin: 0 0 1.4rem 은 좌우를 0으로 함께 덮어 margin-inline: auto 를 지우고,
  그 요소만 왼쪽으로 치우친 다른 축에 서게 된다. 인용 카드와 구분선이 실제로
  그렇게 어긋났었다. 세로 여백은 margin-block 으로만 준다.
*/
.doc-body > * {
  max-width: var(--doc-measure);
  margin-inline: auto;
}

.doc-body > .doc-code {
  max-width: var(--doc-wide);
}

/*
  표는 산문과 같은 컬럼 폭에 세운다. 더 넓게 두면 열 셋짜리 짧은 표가 그 폭을
  채우지 못해, 앞 열은 붙고 마지막 설명 열 뒤로 빈 자리가 길게 남는다. 본문과
  좌우 끝을 맞추면 그 빈 자리가 사라지고 읽는 눈도 한 줄에서 움직인다.
*/
.doc-body > .table-wrap {
  max-width: var(--doc-measure);
}

/*
  흐름도는 가로로 긴 그림이라 산문보다 넓은 띠를 쓴다. 카드 폭 전체로 풀어 두면
  그림만 문서 밖으로 튀어나온 것처럼 보이므로, 코드와 같은 띠에서 멈춘다.
*/
.doc-body > .doc-figure {
  max-width: var(--doc-wide);
}

/*
  README 의 제목 층. 원문 `#` 이 여기서 h2 가 된다(화면 제목 h1 은 탑바가 든다).
  절 제목 위에는 구분선 대신 넉넉한 여백을 둬, README 의 `---` 가 만드는 실제
  구분선과 층이 겹치지 않게 한다.
*/
.doc-body h2 {
  margin-bottom: 1.1rem;
  color: var(--text);
  font-size: clamp(1.5rem, 3.4vw, 1.9rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.doc-body h3 {
  margin-top: 2.4rem;
  margin-bottom: 0.9rem;
  color: var(--text-heading);
  font-size: 1.28rem;
  line-height: 1.4;
}

.doc-body h4 {
  margin-top: 1.8rem;
  color: var(--text-heading);
  font-size: 1.05rem;
}

/* 앵커로 이동한 제목이 sticky 탑바 밑으로 들어가지 않게 한다. */
.doc-body :is(h2, h3, h4, h5, h6) {
  scroll-margin-top: var(--anchor-offset);
}

.doc-body p {
  margin-bottom: 1.15rem;
  color: var(--text-body);
  font-size: 1.02rem;
  line-height: 1.85;
}

/*
  README 는 굵게로 문장의 뼈대를 짚는다. 산문에서 이 표시가 자주 나오므로 색까지
  바꾸면 화면이 얼룩진다 — 무게만 올리고 색은 본문보다 한 단계 진하게만 둔다.
*/
.doc-body strong {
  color: var(--text);
  font-weight: 750;
}

.doc-body a {
  color: var(--accent-link);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.doc-body a:hover {
  color: var(--accent-strong);
}

.doc-body a:focus-visible {
  border-radius: var(--radius-focus);
  outline: 3px solid var(--focus-ring);
  outline-offset: 0.15rem;
}

/* 산문 안 짧은 코드. 계약 필드·경계값이라 본문 흐름을 끊지 않을 만큼만 표시한다. */
.doc-body :not(pre) > code {
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-chip);
  background: var(--surface-accent);
  color: var(--text-heading);
  font-family: var(--font-mono);
  font-size: 0.88em;
}

.doc-body .doc-list {
  margin-bottom: 1.15rem;
  padding-left: 1.25rem;
  color: var(--text-body);
  font-size: 1.02rem;
  line-height: 1.85;
}

.doc-body .doc-list li + li {
  margin-top: 0.6rem;
}

.doc-body .doc-list::marker,
.doc-body .doc-list li::marker {
  color: var(--text-subtle);
}

/* 인용은 본문 옆에 붙는 곁말이라, 계획 안내(.roadmap-plan-note)와 같은 어법을 쓴다. */
.doc-body .doc-quote {
  margin-block: 0 1.4rem;
  padding: 0.9rem 1.1rem;
  border-left: 3px solid var(--accent-border);
  border-radius: var(--radius-md);
  background: var(--accent-tint);
}

.doc-body .doc-quote p {
  margin-bottom: 0;
  color: var(--text-body);
  font-size: 0.96rem;
}

.doc-body .doc-quote p + p {
  margin-top: 0.6rem;
}

.doc-body .doc-rule {
  margin-block: 2.6rem;
  border: 0;
  border-top: 1px solid var(--line);
}

.doc-body .table-wrap {
  margin-bottom: 1.4rem;
}

/*
  소개 문서의 표. 데이터 표의 최소 폭(650px)은 열이 일곱인 커버리지 표를 위한
  값이라 여기서는 과하다. 0으로 풀어 두면 좁은 폭에서 가로 스크롤 대신 설명 열이
  줄바꿈으로 접힌다. 넘칠 때만 .table-wrap 의 가로 스크롤이 받는다.
*/
.doc-table {
  min-width: 0;
}

/*
  이름·분류처럼 값이 짧은 앞 열은 자기 폭만 쓰고, 남는 폭은 마지막 설명 열이
  전부 받는다. auto 레이아웃에 맡기면 남는 폭을 열 셋이 비율로 나눠 가져, 짧은
  앞 열이 필요 없이 벌어지고 설명 열은 그만큼 덜 받는다.
*/
.doc-table th:not(:last-child),
.doc-table td:not(:last-child) {
  width: 1%;
  white-space: nowrap;
}

/*
  코드 블록. 무엇으로 적힌 코드인지는 오른쪽 위 언어 표시가 알린다. 코드면은
  상세 화면의 소스 블록과 같은 --surface-code 라 양 테마에서 같은 면을 쓴다.
*/
.doc-body .doc-code {
  position: relative;
  overflow-x: auto;
  margin-block: 0 1.4rem;
  padding: 1.1rem;
  border-radius: var(--radius-code);
  background: var(--surface-code);
  color: var(--text-code);
  font-family: var(--font-mono);
  font-size: 0.84rem;
  line-height: 1.7;
}

.doc-body .doc-code[data-language]::before {
  float: right;
  margin-left: 1rem;
  color: var(--text-code-control);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  content: attr(data-language);
}

/*
  흐름도. mermaid 블록을 빌드가 인라인 SVG로 옮긴 것이라, 브라우저가 받는 것은
  이미 완성된 그림이고 실행되는 코드는 없다(scripts/mermaid.mjs).

  SVG는 자연 크기로 둔다. 폭에 맞춰 줄이면 글자가 같이 작아져 읽을 수 없게 되므로,
  좁은 화면에서는 이 상자 안에서만 가로로 스크롤한다 — 코드 블록·표가 이미 쓰는
  방식과 같다. 색은 전부 클래스로 빼서 토큰을 타므로 다크 전환도 함께 따라온다.
*/
.doc-figure {
  overflow-x: auto;
  margin-block: 0 1.4rem;
  padding: 1.1rem 0.2rem;
}

/*
  폭이 모자라면 비율을 지킨 채 줄어든다. 얼마나 줄어들 수 있는지(min-width)는
  다이어그램마다 달라 생성기가 인라인으로 싣는다. 그 하한 아래에서는 더 줄이지
  않고 .doc-figure 의 가로 스크롤이 받는다 — 글자가 읽을 수 없어지는 것보다
  좁은 화면에서 옆으로 미는 편이 낫다.
*/
.doc-figure__svg {
  display: block;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

.doc-figure__box {
  fill: var(--surface-card);
  stroke: var(--line-accent);
  stroke-width: 1;
}

/* 판단 노드는 좌우가 깎인 육각형이라 면을 한 단 눌러 상자와 갈린다. */
.doc-figure__box--decision {
  fill: var(--accent-tint);
  stroke: var(--accent-border);
}

.doc-figure__text {
  fill: var(--text-muted);
  font-family: var(--font-sans);
}

.doc-figure__text--title {
  fill: var(--text-heading);
}

.doc-figure__text--bold {
  font-weight: 750;
}

.doc-figure__edge {
  fill: none;
  stroke: var(--line-control);
  stroke-width: 1.6;
}

.doc-figure__arrowhead {
  fill: var(--line-control);
}

/* 간선 라벨은 선 위에 앉으므로 면을 한 겹 깔아 글자가 선에 뚫리지 않게 한다. */
.doc-figure__edge-chip {
  fill: var(--surface-card);
  stroke: var(--line-panel);
  stroke-width: 1;
}

.doc-figure__edge-label {
  fill: var(--text-subtle);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
}

/* ≤820px: 사이드바를 슬라이드 드로어로 전환한다. */
@media (max-width: 820px) {
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: fixed;
    left: 0;
    width: 17.5rem;
    box-shadow: var(--shadow-card-hover);
    transform: translateX(-100%);
    transition: transform 0.22s ease;
  }

  .sidebar[data-open="true"] {
    transform: translateX(0);
  }

  .topbar__menu {
    display: inline-flex;
  }
}

/* 드로어 애니메이션도 동작 축소 설정을 따른다. */
@media (max-width: 820px) and (prefers-reduced-motion: reduce) {
  .sidebar {
    transition: none;
  }
}

@media (min-width: 821px) {
  /* 넓은 화면에서는 드로어 상태가 남아 있어도 무시한다. */
  .sidebar-scrim {
    display: none;
  }
}

@media (max-width: 700px) {
  .guidance-grid,
  .contract-grid {
    grid-template-columns: 1fr;
  }

  /* 좁은 화면에서는 행을 접어 이름·요약이 잘리지 않게 한다. */
  .algorithm-row {
    flex-wrap: wrap;
    gap: 0.3rem 0.9rem;
  }

  .algorithm-row h3,
  .algorithm-row__summary {
    flex: 1 1 100%;
  }

  .feedback-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .panel-heading,
  .filters {
    display: grid;
    grid-template-columns: 1fr;
  }

  /* 좁은 화면에서는 토글이 아이콘만 남되 접근 이름은 유지한다. */
  .theme-toggle__label {
    position: absolute;
    overflow: hidden;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* 탑바가 좁아지면 제목과 조작 묶음이 두 줄로 갈린다. */
  .topbar {
    row-gap: 0.25rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
