/* Lattice — 공통 스타일시트 1장 (ADR-0004 D-6: 번들러 없음, 손으로 쓴 CSS).
 *
 * **토큰 이름의 정본은 SCREEN-001 §1 이다.** 신규 토큰 0건은 tests/unit/test_tokens.py 가
 * 강제한다 — 정본 49개 이름과 집합이 정확히 같아야 한다. **값의 정본은 TICKET-620 부터
 * tokens-pokevault.css 다** — 이 파일의 :root 는 옛 이름을 그 파일의 --pv-* 값에
 * 별칭 연결만 한다(사용처 292곳은 그대로, 정의 한 곳만 바뀐다). tokens-pokevault.css 는
 * base.html 이 이 파일보다 먼저 <link> 로 불러온다 — @import 는 D-7 해시 매니페스트와
 * 충돌한다(이 파일은 Jinja 를 안 타 배포마다 바뀌는 해시 파일명을 담을 수 없다).
 * `ADR-0041` D-2: 1:1 대응 축은 별칭층에 남기고, 별칭층으로 도달 불가능한 축(폰트)만 pv 이름으로 통합한다.
 *
 * 픽셀 치수(56 · 48 · 44 · 24 · 60×84 · 167.5 · 343)를 본문에 직접 적는 것은 토큰이 아니다.
 * SCREEN-002 §6 이 그 관례를 명시했다("새 CSS 커스텀 프로퍼티를 하나도 만들지 않았다").
 * 색상값은 다르다 — 본문에 박으면 사실상 새 토큰이라 가드가 위반으로 잡는다.
 * **잡는 범위**: hex · rgb/rgba · hsl/hsla · hwb · lab/lch · oklab/oklch · color() · color-mix()
 * · CSS 이름 있는 색 148개. 2026-08-31 리뷰 MAJOR 3 전까지는 hex 와 rgb/hsl 뿐이라
 * `color: red` 와 `oklch(...)` 가 그대로 샜다.
 */

/* ── 1. 토큰 — SCREEN-001 §1-1 색상, 값은 PokeVault 별칭(TICKET-620) ──────────────
 * PokeVault 는 다크 전용이다(DESIGN-SYSTEM-pokevault.md, TICKET-588) — 라이트 모드가
 * 없으므로 :root 가 상시 다크 팔레트를 참조한다. 옛 `@media (prefers-color-scheme: dark)`
 * 오버라이드 블록은 그래서 지웠다(TICKET-620 AC-4) — 남겨두면 :root 와 정확히 같은 값을
 * 다시 선언하는 죽은 코드이자, 값이 갈라질 잠재 위험만 남긴다. */
:root {
  /* 배경/표면 — 밝아지는 순서(bg < surface < surface-alt < border, 옛 다크값 기준)를
     pv 스케일의 밝기 순서에 그대로 대응한다: bg-base < bg-surface < bg-elevated < border-default */
  --color-bg:            var(--pv-bg-base);
  --color-surface:       var(--pv-bg-surface);
  --color-surface-alt:   var(--pv-bg-elevated);
  --color-border:        var(--pv-border-default);

  /* 텍스트 */
  --color-text-primary:   var(--pv-text-primary);
  --color-text-secondary: var(--pv-text-secondary);
  --color-text-tertiary:  var(--pv-text-muted);

  /* 링크/포커스/액센트 — 옛 값은 셋 다 같은 블루였다. pv 는 포커스 링만 골드로 분리한다
     (--pv-focus-ring = PokeVault 1차 브랜드색) — PokeVault 자체 설계 의도를 그대로 따른다 */
  --color-link:        var(--pv-accent);
  --color-focus-ring:  var(--pv-focus-ring);
  --color-accent:      var(--pv-accent);

  /* 레어도 배지 — "높다/낮다" 가 아니라 "분류가 다르다" 를 색으로 말한다.
     --pv-badge-* 는 TICKET-619 가 이미 이 옛 이름들에 대응해 정의해 뒀다 */
  --color-badge-current-bg:   var(--pv-badge-current-bg);
  --color-badge-current-text: var(--pv-badge-current-text);
  --color-badge-legacy-bg:    var(--pv-badge-legacy-bg);
  --color-badge-legacy-text:  var(--pv-badge-legacy-text);
  --color-badge-none-border:  var(--pv-badge-none-border);
  --color-badge-none-text:    var(--pv-badge-none-text);

  /* 이미지 오류 상태 */
  --color-error-bg:   var(--pv-error-bg);
  --color-error-text: var(--pv-error-text);

  /* 그림자 — pv 도 평상시엔 보더만 쓰고 그림자는 hover 전용이라 none 을 그대로 계승한다 */
  --shadow-card: var(--pv-shadow-card);

  /* SCREEN-001 §1-2 타이포그래피 (1rem = 16px) */

  --text-display-size: var(--pv-text-display-size);
  --text-display-line: var(--pv-text-display-line);
  --text-display-weight: var(--pv-text-display-weight);

  --text-title-size: var(--pv-text-title-size);
  /* pv 에 제목 전용 line-height 대응이 없다(tokens-pokevault.css §6 에도 미정의) —
     대응이 없는 유일한 자리라 SCREEN-001 원래 값을 그대로 둔다. 색이 아니라 리터럴이어도
     색 리터럴 가드에는 걸리지 않는다 */
  --text-title-line: 1.35;
  --text-title-weight: var(--pv-text-title-weight);

  --text-body-size: var(--pv-text-body-size);
  --text-body-line: var(--pv-text-body-line);
  --text-body-weight: var(--pv-text-body-weight);
  --text-body-strong-weight: var(--pv-text-body-strong-weight);

  --text-caption-size: var(--pv-text-caption-size);
  --text-caption-line: var(--pv-text-caption-line);
  --text-caption-weight: var(--pv-text-caption-weight);

  --text-badge-size: var(--pv-text-badge-size);
  --text-badge-line: var(--pv-text-badge-line);
  --text-badge-weight: var(--pv-text-badge-weight);

  /* SCREEN-001 §1-3 스페이싱 — pv 스케일은 rem 단위지만 16px 기준 환산값이 4px 배수와
     정확히 같다(--pv-space-1 = .25rem = 4px, … --pv-space-10 = 2.5rem = 40px) */
  --space-1: var(--pv-space-1);
  --space-2: var(--pv-space-2);
  --space-3: var(--pv-space-3);
  --space-4: var(--pv-space-4);
  --space-5: var(--pv-space-5);
  --space-6: var(--pv-space-6);
  --space-8: var(--pv-space-8);
  --space-10: var(--pv-space-10);

  /* SCREEN-001 §1-4 라운드 */
  --radius-sm: var(--pv-radius-sm);
  --radius-md: var(--pv-radius-md);
  --radius-lg: var(--pv-radius-lg);

  /* SCREEN-001 §1-5 브레이크포인트.
   * **@media 는 var() 를 읽지 못한다** (CSS 사양). 그래서 아래 미디어 쿼리에는 600px·900px 가
   * 리터럴로 들어간다. 값이 갈라지지 않게 이 두 토큰을 정본으로 두고 주석으로 묶어 둔다.
   * (tokens-pokevault.css §4 주석: pv-bp-md/lg 도 SCREEN-001 과 같은 값이라 새로 정의하지
   * 않았다고 명시 — 리터럴이 두 파일에서 갈라질 걱정은 없다) */
  --bp-md: var(--pv-bp-md);
  --bp-lg: var(--pv-bp-lg);
}

/* ── 2. 리셋과 공통 셸 — SCREEN-001 §2 ──────────────────────────────── */

/* 375px 판정에서 패딩이 폭에 더해져 가로 스크롤이 생기는 가장 흔한 원인을 원천 차단한다
 * (SCREEN-001 §1-5). 전역 규칙이다 — 예외를 두지 않는다. */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text-primary);
  font-family: var(--pv-font-body);
  font-size: var(--text-body-size);
  line-height: var(--text-body-line);
  font-weight: var(--text-body-weight);
  /* SCREEN-001 §2-0 sticky footer. 짧은 페이지에서 푸터가 뷰포트 중간에 뜨지 않게 한다.
   * min-height 는 최솟값이라 콘텐츠가 길면 body 가 그만큼 늘어난다 — 긴 페이지 회귀 없음 */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* 375px 그리드 — SCREEN-001 §2-3. 고정 px 폭을 쓰지 않는다 */
img, svg, table { max-width: 100%; }
img { height: auto; }

a { color: var(--color-link); }

/* SCREEN-001 §3-7 접근성. 마우스 클릭에는 표시하지 않는다 */
:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.container {
  width: 100%;
  padding-inline: var(--space-4);
  box-sizing: border-box;
}
@media (min-width: 600px) {  /* --bp-md */
  .container { padding-inline: var(--space-6); }
}
@media (min-width: 900px) {  /* --bp-lg */
  .container { max-width: 900px; margin-inline: auto; }
}

/* 세트명 최대 43자(ADR-0002 §2). 줄바꿈이 없으면 375px 에서 컨테이너를 밀어낸다 */
.wrap-ko {
  word-break: keep-all;
  overflow-wrap: break-word;
}

/* SCREEN-001 §2-1 헤더 (TICKET-599 정정 반영, TICKET-600).
 * .site-header 자신은 높이·배경·sticky 만 맡는다 — 좌우 패딩을 자체적으로 갖지 않는다.
 * 좌우 정렬은 .site-header__inner 가 .container(위 §1) 규칙을 그대로 재사용해
 * 바디(<main class="container">)·푸터(.site-footer__inner, TICKET-751)와 같은 좌우
 * 기준선을 쓴다.
 * 900px 이상 뷰포트에서 헤더가 뷰포트 끝까지 늘어나 바디/푸터보다 옆으로 더 벌어져
 * 보이던 정렬 불일치를 없앤다 (TICKET-597 이 미리보기에서 먼저 잡은 것과 동일 패턴).
 * 푸터는 TICKET-751 전까지 이 패턴을 안 가져와 900px+ 에서 기준선이 어긋나 있었다 —
 * 지금은 .site-footer__inner(아래 §2-2)가 같은 패턴으로 맞춘다. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  height: 56px;
  /* display:flex + align-items:center 는 SCREEN-001 §2-1 예시 CSS 블록엔 없지만,
   * 이걸 빼면 .site-header__inner(block box)가 부모 56px 안에서 수직 중앙정렬되지
   * 않고 상단에 붙어 검색 버튼(44px)과 하단 테두리 사이에 불균등한 여백이 생긴다
   * (실측: inner 높이 ≈44px, 위 0px/아래 12px). PREVIEW-SCREEN-003-홈.html:148-149
   * 의 검증된 패턴(.site-header 도 display:flex;align-items:center 유지)을 따라
   * 이 회귀를 막는다 — 가로 정렬(.container 폭 일치)은 .site-header__inner 의
   * margin-inline:auto 로 이 속성과 무관하게 이미 성립한다. */
  display: flex;
  align-items: center;
  /* TICKET-623 — `<body>` 가 display:flex 라(§0 레이아웃) 이 요소도 그 flex 아이템이다.
   * 브레드크럼(길 수 있는 세트명·카드명)이 이 안에서 말줄임되려면 이 체인 전체가
   * min-width:0 이어야 한다(`main` 이 이미 같은 이유로 이 값을 쓴다, 위 §0 주석 참조). */
  min-width: 0;
  /* 2026-09-04 TICKET-640(TICKET-654 구현) — --color-bg/--color-border(라이트·다크 겸용)
   * 에서 --pv-bg-overlay/--pv-border-default 로 교체. tokens-pokevault.css:25 주석이
   * --pv-bg-overlay 용도를 "네비 배경·푸터"로 이미 명시 — 새 토큰이 아니라 기존 값의
   * 지정 용도를 셸에 적용한 것이다 (SCREEN-001 §2-1). */
  background: var(--pv-bg-overlay);
  border-bottom: 1px solid var(--pv-border-default);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-width: 0;  /* TICKET-623 — 위 .site-header 와 같은 이유(브레드크럼 말줄임 체인) */
}
.site-header__logo {
  font-family: var(--pv-font-display);  /* ADR-0042 D-7 · D-10 항목 5 — 로고는 Orbitron */
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
  text-decoration: none;
}
/* 터치 타겟 44×44 (SCREEN-001 §3-7) */
.site-header__search {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: 44px;
  min-height: 44px;
  color: var(--color-text-primary);
  text-decoration: none;
}
.site-header__search-label { display: none; font-size: var(--text-caption-size); }
@media (min-width: 600px) {
  .site-header__search-label { display: inline; }
}

/* SCREEN-001 §2-1-nav — 주요 내비게이션 (신설, TICKET-640, TICKET-654 구현).
 * 블록 그대로 옮겨온 것이다 — 값을 새로 정하지 않는다. */
.site-header__nav {
  display: flex; align-items: center; gap: var(--pv-space-3);
  flex: 1 0 auto; justify-content: center; /* 로고·검색 사이 여백을 균등 흡수 */
}
/* 브레드크럼을 가진 화면에서만 375px 숨김 — home.html·search.html 은 <header> 에 이
 * 클래스 자체를 안 붙이므로 항상 보인다(§2-1-nav "공간 우선순위" — 343px 안에
 * 브레드크럼+nav+검색을 다 욱여넣지 않는다). */
.site-header--with-breadcrumb .site-header__nav { display: none; }
@media (min-width: 600px) {
  .site-header--with-breadcrumb .site-header__nav { display: flex; }
}
.site-header__nav-item {
  min-height: 44px; display: inline-flex; align-items: center;
  padding-inline: var(--pv-space-2);
  font-family: var(--pv-font-body); font-size: var(--pv-text-label-size);
  color: var(--pv-text-secondary); text-decoration: none;
  background: none; border: none; /* <button> 리셋 */
}
.site-header__nav-item[aria-current="page"] {
  color: var(--pv-color-gold); font-weight: 700; /* "활성 탭" — tokens-pokevault.css:39 */
}
.site-header__nav-item:disabled,
.site-header__nav-item[aria-disabled="true"] {
  color: var(--pv-text-disabled); cursor: not-allowed;
}
.site-header__nav-item:focus-visible { outline: 2px solid var(--pv-focus-ring); }

/* SCREEN-001 §2-0. <main class="container page"> 가 남는 세로 공간을 흡수 →
 * 짧은 페이지에서 푸터가 뒤로 밀려나지 않는다. 콘텐츠가 넘치면 auto 기준값이 이겨 회귀 없음.
 * min-width: 0 — body 가 flex 컨테이너가 되면서 <main> 이 flex 아이템이 됐다. flex 아이템의
 * min-width 기본값은 auto(=자식 min-content) 라 word-break:keep-all·ellipsis 를 가진 자손이
 * 375px 컨테이너를 벌린다(TICKET-037 회귀 가드가 실제로 이 상태를 잡았다). 아이템 자신에
 * min-width:0 을 줘야 자손의 축소 규칙이 다시 작동한다(기존 .card-grid > li 패턴과 동일). */
main {
  flex: 1 0 auto;
  min-width: 0;
}

/* SCREEN-001 §2-2 푸터. 375px 세로 스택 · 600px+ 2열.
 * 배경·텍스트 색은 2026-09-04 TICKET-640(TICKET-654 구현) — --color-surface/
 * --color-text-secondary(라이트·다크 겸용)에서 --pv-bg-overlay/--pv-text-secondary 로
 * 교체. 근거는 §2-1 헤더와 동일(tokens-pokevault.css:25 "네비 배경·푸터" 주석).
 *
 * .site-footer 자신은 배경·테두리·세로 패딩만 맡는다 — 좌우 패딩을 자체적으로 갖지
 * 않는다(TICKET-751, `.site-header`/`.site-header__inner` 와 같은 패턴). 좌우 정렬·
 * 900px+ 폭 제한·600px+ 2열 배치는 전부 아래 .site-footer__inner 가 .container(§1)
 * 규칙을 재사용해 맡는다 — 이전엔 .site-footer 가 padding-inline 만 베끼고 900px+
 * max-width·margin-inline 을 안 가져와 헤더·본문과 기준선이 어긋났었다. */
.site-footer {
  background: var(--pv-bg-overlay);
  border-top: 1px solid var(--pv-border-default);
  padding-block: var(--space-6);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--pv-text-secondary);
  /* SCREEN-001 §2-0. 콘텐츠가 넘칠 때 푸터 자신이 찌그러지지 않는다 */
  flex-shrink: 0;
}
@media (min-width: 600px) {
  .site-footer__inner {
    display: flex;
    justify-content: space-between;
    gap: var(--space-6);
  }
}
.site-footer p { margin: 0 0 var(--space-2); }
/* pv 시스템엔 별도 링크색 토큰이 없다(§2-2) — 밑줄(전역 `a` 규칙의 기본 밑줄, 여기서
 * 지우지 않는다)만으로 링크를 구분한다. 2026-09-04 TICKET-640(TICKET-654 구현) —
 * --color-link 대신 본문과 같은 --pv-text-secondary. */
.site-footer a { color: var(--pv-text-secondary); }
.site-footer__block + .site-footer__block { margin-top: var(--space-4); }
@media (min-width: 600px) {
  .site-footer__block + .site-footer__block { margin-top: 0; }
}
/* §2-2 5번 Phase 캡션(신설, TICKET-640, TICKET-654 구현) — "Phase 1: 도감" 구간만
 * 강조(§2-1-nav 활성 탭과 같은 근거). 나머지 두 구간은 .site-footer 의 기본
 * caption 크기·--pv-text-secondary 를 그대로 상속한다. */
.site-footer__phase { font-family: var(--pv-font-mono); }
.site-footer__phase-current { color: var(--pv-color-gold); }

.page { margin-block-end: var(--space-8); }

/* ── 3. 공통 컴포넌트 ───────────────────────────────────────────────── */

/* 브레드크럼 — SCREEN-001 §3-7 (nav > ol). 한 줄 초과 시 줄바꿈 허용 */
.breadcrumb { margin-top: var(--space-4); }
.breadcrumb ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
}
.breadcrumb li + li::before { content: "/"; margin-inline-end: var(--space-1); }
.breadcrumb [aria-current="page"] { color: var(--color-text-secondary); }

/* 헤더 안의 브레드크럼 — `ADR-0036` D-6 (TICKET-623). 로고 자리를 대신한다(`base.html`
 * `header_identity` 블록). 헤더는 56px 고정 높이라 본문과 달리 **줄바꿈을 허용하지 않는다**
 * — 대신 마지막 항목(현재 페이지, 세트명·카드명처럼 길 수 있는 값)만 말줄임으로 줄인다.
 * 375px 에서 가로 스크롤을 만들지 않기 위한 것이다(제약 §3 완료 판정 5번, `@qa` 375px 실측). */
.site-header .breadcrumb {
  margin-top: 0;
  min-width: 0;
  flex: 1 1 auto;
}
.site-header .breadcrumb ol { flex-wrap: nowrap; }
/* `:last-child` 를 쓰지 않는다 — 이 저장소의 정적 CSS 검사기(`test_tokens.py` 375px
 * min-width 가드)가 구조적 의사 클래스를 판정할 수 없어 레이아웃 선언이 걸리면 항상
 * 실패로 잡는다("판정 불가 선택자"). 그래서 **모든** `<li>` 에 `min-width:0` 을 준다 —
 * 마지막 항목(현재 페이지)만 실제로 말줄임이 걸리므로(아래 `[aria-current="page"]`,
 * 속성 선택자라 판정 가능하다) 나머지 항목은 짧은 라벨("홈"·"세트")이라 줄어들 일이
 * 없다. `.card-grid > li` 가 이미 쓰는 것과 같은 「일단 shrink 허용」 패턴이다. */
.site-header .breadcrumb li {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 0;
}
.site-header .breadcrumb [aria-current="page"] {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.site-header .breadcrumb a {
  color: inherit;
  text-decoration: none;
}
.site-header .breadcrumb a:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* 레어도 배지 — SCREEN-001 §3-4 CSS 그대로. SCREEN-002 §1-2 가 목록에서 재사용한다 */
.rarity-badge {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-badge-size);
  font-weight: var(--text-badge-weight);
  line-height: var(--text-badge-line);
  white-space: nowrap;
}
.rarity-badge--current { background: var(--color-badge-current-bg); color: var(--color-badge-current-text); }
.rarity-badge--legacy  { background: var(--color-badge-legacy-bg);  color: var(--color-badge-legacy-text); }
.rarity-badge--none {
  background: transparent;
  color: var(--color-badge-none-text);
  border: 1px solid var(--color-badge-none-border);
}

/* 이미지 영역 — SCREEN-001 §3-3. aspect-ratio 가 CLS 를 없앤다
 * background 는 5개 개별 속성으로 쓴다(단축 속성 금지) — TICKET-160. background 단축은
 * background-image 를 포함한 전체를 초기화하므로, 아래 --card-placeholder-url 배경과
 * 같은 선택자에 얹으면 선언 순서에 따라 서로를 지운다. */
.card-image {
  width: 100%;
  aspect-ratio: 5 / 7;
  background-image: var(--card-placeholder-url);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  background-color: var(--color-surface-alt);
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-image img { width: 100%; height: 100%; object-fit: contain; }
.card-image__fallback { display: none; }
/* 확정 상태(NULL·로딩실패)에는 로딩 중 신호(카드뒷면)를 남기지 않는다 — SCREEN-001 §3-3 */
.card-image--empty,
.card-image[data-state="error"] {
  background-image: none;
}
.card-image[data-state="error"] img { display: none; }
.card-image[data-state="error"] .card-image__fallback,
.card-image--empty .card-image__fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-secondary);
  font-size: var(--text-caption-size);
  text-align: center;
  padding: var(--space-4);
}
.card-image__fallback p { margin: 0; }

/* 정렬 컨트롤 — **검색 전용** (SCREEN-002 §1-5). 세트 상세는 이 클래스를 쓰지 않는다
 * (`ADR-0036` D-3~D-5, TICKET-623) — 옛 `.sort-toggle--gated`(라디오 + `:has()` 배타 2택,
 * `ADR-0012` D-2)가 은퇴했다. 세트 상세의 방향 토글은 `.order-toggle`(아래)이다.
 * 검색은 GET 폼 링크라 `:has()` 게이트 없이 항상 보인다. */
.sort-toggle {
  display: flex;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  border: 0;
}
.sort-toggle__option {
  flex: 1 1 0;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
  border-bottom: 2px solid transparent;
}
.sort-toggle__option--selected {
  color: var(--color-text-primary);
  border-bottom-color: var(--color-accent);
}

/* 세트 상세 정렬 방향 토글 — `.order-toggle` (`ADR-0036` D-3~D-5, TICKET-623).
 * `<a>` 링크뿐이라 `:has()` 게이트가 필요 없다 — 항상 보인다(D-5 "가능한 유일한 안").
 * 그 자체가 포커스 가능한 요소라 전역 `:focus-visible`(L149 근방)이 이미 아웃라인을
 * 그려준다 — 라디오 은닉용 `:has(input:focus-visible)` 트릭이 필요 없다. */
.order-toggle {
  display: flex;
  gap: var(--space-2);
}
.order-toggle__option {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding-inline: var(--space-2);
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
  text-decoration: none;
}
.order-toggle__option:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* 세트 상세 툴바 — 왼쪽 등급 필터, 오른쪽 정렬 방향 토글 (`ADR-0036` D-3~D-5, TICKET-623).
 * 375px 에서도 줄바꿈이 되도록 `flex-wrap` 을 둔다 — 등급 칩이 많은 세트에서 한 줄에
 * 다 안 들어가도 가로 스크롤을 만들지 않는다(제약 §3 완료 판정 5번). */
.set-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
}
.set-toolbar__divider {
  color: var(--color-border);
}

/* 언어 패싯 — SCREEN-002 §1-8-2 (신설, TICKET-656). **새 커스텀 프로퍼티 0개** —
 * §1-8-2 「시각 레시피」 원문이 "§3-5 등급 필터칩의 선택/비선택 레시피를 그대로
 * 재사용한다"이므로 아래 값은 `.rarity-filter__chip` 과 같은 토큰을 쓴다.
 *
 * **`.rarity-filter` 와 달리 `@supports selector(:has(a))` 게이트가 없다.** 이 컨트롤은
 * `<a>` 내비게이션이라 `:has()` 지원과 무관하게 항상 동작한다(§1-8-1 — 언어는 서버측
 * 필터다). 죽은 컨트롤을 숨기는 §1-5 규칙은 여기 해당하지 않는다.
 *
 * 언어가 1개면 매크로가 아무것도 안 그려 이 규칙은 현재 어느 페이지에도 적용되지 않는다. */
.language-facet {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
/* 44px 터치 타겟 — `.rarity-filter__chip` 과 같은 규격 (§1-8-2). **높이는 요소 자신의
 * `min-height` 로 낸다** — margin 은 클릭 가능 영역을 넓히지 않는다(TICKET-657, BACKLOG
 * B-213 ① — 옛 주석 "32 + 상하 여백 4+4 = 44px" 은 margin 을 히트 영역으로 잘못 셌다.
 * `getBoundingClientRect().height` 실측은 32px 였다, 656 QA 보고). */
.language-facet__chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-block: var(--space-1);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  color: var(--color-text-secondary);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  text-decoration: none;
}
.language-facet__chip--selected {
  border-color: var(--color-accent);
  background: var(--color-surface-alt);
  color: var(--color-text-primary);
}
.language-facet__chip:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* 언어 선택 드롭다운 — SCREEN-002 §1-8-2a (신설, TICKET-910 B-291·B-292).
 * `.language-facet`(위)를 대체한다 — 트리거는 `.rarity-filter__chip`/`.language-facet__chip`
 * 과 같은 44px 터치 타겟·테두리 레시피를 값만 재사용한다(새 커스텀 프로퍼티 0개).
 * 메뉴는 그림자 토큰이 없어(§1-8-2a "box-shadow 없이") 테두리만으로 분리한다.
 *
 * 언어가 1개면 매크로가 아무것도 안 그려 이 규칙은 현재 어느 페이지에도 적용되지 않는다. */
.language-select {
  position: relative;
  display: inline-block;
}
.language-select__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 44px;
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  color: var(--color-text-secondary);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  cursor: pointer;
  /* <summary> 는 기본 마커(브라우저마다 다른 삼각형/화살표)를 갖는다 — 매크로가
   * 이미 SVG 화살표를 직접 그리므로 기본 마커는 끈다. */
  list-style: none;
}
.language-select__trigger::-webkit-details-marker { display: none; }
.language-select__trigger:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.language-select[open] .language-select__trigger {
  border-color: var(--color-accent);
}
.language-select__menu {
  position: absolute;
  z-index: 10;
  top: calc(100% + var(--space-1));
  left: 0;
  min-width: 100%;
  margin: 0;
  padding: var(--space-1) 0;
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  white-space: nowrap;
}
.language-select__option {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--space-3);
  color: var(--color-text-secondary);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  text-decoration: none;
}
.language-select__option--selected {
  color: var(--color-text-primary);
  font-weight: var(--text-body-strong-weight);
}
.language-select__option:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* 등급 필터칩 — SCREEN-002 §3-5 (TICKET-070). **JS 를 쓰지 않는다.**
 *
 * 타일을 숨기는 규칙은 set.html 이 칩 개수만큼 인라인으로 찍는다(칩 인덱스가 세트마다
 * 다르므로 이 파일에 고정할 수 없다). 여기 있는 것은 **모양과 켜짐 조건**뿐이다.
 *
 * `:has()` 를 모르는 브라우저에서는 칩 줄을 **아예 보여주지 않는다.** §1-5 가 정렬 컨트롤에
 * 대해 정한 것과 같은 규칙이다 — "눌러도 반응 없는 컨트롤보다 없는 편이 낫다".
 * 그래서 기본값이 display:none 이고 @supports 가 켠다. 순서를 바꾸면 죽은 칩이 나간다. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.rarity-filter {
  display: none;
  flex-wrap: wrap;
  gap: var(--space-2);
  /* TICKET-623 — 이 규칙의 위쪽 여백은 이제 조상 `.set-toolbar` 가 낸다(margin-top:
   * var(--space-3)). 예전에는 `.set-meta` 바로 다음 형제였다. */
  margin: 0;
  padding: 0;
  border: 0;
}
@supports selector(:has(a)) {
  .rarity-filter { display: flex; }
}
/* 높이 32 + 상하 여백 4+4 = 실질 44px (§3-5). 8개가 375px 에서 감긴다 */
.rarity-filter__chip {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  margin-block: var(--space-1);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  color: var(--color-text-secondary);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  cursor: pointer;
}
.rarity-filter__chip:has(input:checked) {
  border-color: var(--color-accent);
  background: var(--color-surface-alt);
  color: var(--color-text-primary);
}
/* 라디오가 화면에서 안 보이므로 포커스 링을 칩이 대신 그린다 (§3-9)
   :focus-within 은 마우스 클릭에도 켜진다 — 페이지 표준(:focus-visible, L149)에 맞춰
   :has(input:focus-visible) 로 키보드 포커스만 잡는다 (TICKET-076, :has() 는 위 :checked 규칙과 동일 지원선) */
.rarity-filter__chip:has(input:focus-visible) {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.rarity-filter input { position: absolute; opacity: 0; pointer-events: none; }
/* 필터 결과 0건 (§3-7). 인라인 규칙이 id 특이성으로 켠다 — 여기서는 항상 꺼져 있다 */
.rarity-empty {
  display: none;
  font-size: var(--text-body-size);
  line-height: var(--text-body-line);
  color: var(--color-text-secondary);
}
.rarity-empty__reset {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: var(--space-2);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  color: var(--color-link);
  font-size: var(--text-body-size);
  cursor: pointer;
}

/* 「등급 표기 없음」 칩 (SCREEN-002 §3-5-1 D-4, TICKET-106) — 이 칩의 값은 세트마다
 * 바뀌지 않는 상수(#rarity-filter-none · data-rarity-index="") 라 인라인 <style> 이 아니라
 * 여기 한 번만 넣으면 된다. 기존 칩(세트마다 인덱스가 달라 인라인)과 같은 패턴이다.
 *
 * **그룹 헤더 관련 두 규칙은 `TICKET-623`(`ADR-0036` D-3)에서 삭제했다** — 세트 상세가
 * 등급순 그룹 렌더 자체를 은퇴했다. `.group-heading`/`.group-heading__count` 클래스는
 * 여전히 존재한다(카드 상세의 「다른 버전」·「다른 언어판」 섹션이 쓴다) — 지운 것은
 * `.set-filterable` 스코프의 두 규칙뿐이다. */
.set-filterable:has(#rarity-filter-none:checked) .card-grid > li:not([data-rarity-index=""]) {
  display: none;
}
.set-filterable:has(#rarity-filter-none:checked):not(:has(.card-grid > li[data-rarity-index=""])) .rarity-empty {
  display: block;
}

/* ── 4. 카드 상세 — SCREEN-001 §3-2 (375px 세로 순서) ────────────────── */
/* TICKET-905 버그 3 — 브레드크럼(헤더)과 본문 사이 여백을 **`.card-detail__body`
 * 자신의 margin-top** 으로 명시한다. `<body>` 가 `display:flex`(SCREEN-001 §2-0)라
 * `<main>` 은 `<body>` 의 flex 아이템이고, flex 아이템은 자기 컨텐츠에 **새 블록
 * 포매팅 컨텍스트**를 세운다 — 그래서 `.card-detail__image`(아래)의 margin-top 이
 * `<main>` 밖으로 "탈출"하는 옛 마진 붕괴는 실제로 일어나지 않는다(그 가설은
 * devtools 실측 없이 기각). **진짜 원인은 600px+ 미디어쿼리다** — 그 폭에서
 * `.card-detail__image` 의 margin-top 이 h1 과 나란히 정렬하려고 `0` 으로 덮어써지는데
 * (아래 §4 600px+ 블록), 그 여백을 대신할 값이 이 화면 어디에도 없었다. 다른
 * 화면은 `.page__title` 자체의 margin-top(같은 --space-3)이 모든 폭에서 그대로
 * 살아있어 이 문제가 없다. 여기 margin-top 을 두면: 375px 에서는 `.card-detail__image`
 * 의 margin-top(아래, 같은 --space-3)과 **형제-부모 마진 붕괴**로 겹쳐 12px 그대로고
 * (블록 자식 첫 요소 마진 붕괴 — `.card-detail__body` 는 600px 미만에서 flex 가 아니다),
 * 600px+ 에서는 `.card-detail__body` 가 flex **컨테이너**가 되어 그 마진이 자식과
 * 붕괴하지 않고 그대로 살아남아 다른 화면과 같은 12px 간격을 만든다. `.page`(전역)가
 * 아니라 이 스코프에 두는 이유 — 다른 화면 간격에 영향을 주지 않기 위해서다. */
.card-detail__body { margin-top: var(--space-3); }
.card-detail__image { margin-top: var(--space-3); }
/* 3b — 이미지 근접 출처 표시 + 원본 링크 (SCREEN-001 §3-3a, ADR-0052 §7 D-11·§9 M-7,
 * TICKET-847). **`.card-image`(이미지 박스) 다음의 형제**(TICKET-905 — 전엔
 * `.card-detail__info` 의 첫 자식이었다)라 margin-top 이 이미지와의 간격을 낸다 —
 * 8px(§3-3a). 링크는 본문 링크와 같은 대우(별도 버튼 스타일 없음). */
.card-image-credit {
  margin: var(--space-2) 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
}
.card-image-credit__link {
  color: var(--color-link);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.card-image-credit__link:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.card-detail__name {
  margin: var(--space-4) 0 0;
  font-size: var(--text-display-size);
  line-height: var(--text-display-line);
  font-weight: var(--text-display-weight);
  color: var(--color-text-primary);
}
/* SCREEN-001 §3-2-desc — 카드 설명. h1 바로 아래, 빈 문자열이면 요소 자체가 DOM 에
 * 없다(템플릿 조건부 렌더). 타이포는 본문(body) 토큰 재사용 — 새 토큰을 만들지 않는다.
 * margin-top 은 이 규칙이 아니라 `.card-detail__name + .card-description` 인접
 * 선택자로 붙인다(아래) — 메타 행(.card-detail__meta)의 margin-top 이 이 블록의
 * 유무에 따라 8px/16px 로 갈리는 것과 같은 방식(§3-2 5번 "4b 가 있을 때/없을 때"). */
.card-description {
  margin: 0;
  font-size: var(--text-body-size);
  font-weight: var(--text-body-weight);
  line-height: var(--text-body-line);
  color: var(--color-text-primary);
  word-break: keep-all;
  overflow-wrap: break-word;
  white-space: pre-line;
}
.card-detail__name + .card-description { margin-top: var(--space-2); }
/* 번호줄 + 다른 언어판 인라인 링크 — 이름 옆에 언어·세트코드·번호·레어도를 항상
 * 함께 (제약 §5-4, SCREEN-001 §3-2 5번, 2026-09-09 TICKET-900·902 P11-b 개정).
 * 기본(4b 없음, 대다수)은 8px — h1 바로 뒤에 붙을 때. 4b(description)가 있으면
 * 문단 텍스트 뒤라 더 넓은 16px 로 구획을 분리한다. */
.card-detail__meta {
  margin-top: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}
.card-description + .card-detail__meta { margin-top: var(--space-4); }
/* 번호줄(세트코드 칩 + 번호/분모 + 등급 칩) — `SCREEN-002` §1-1-a 의 `.card-tile__number-line`
 * 을 그대로 재사용한다(위 5번 참조). 타일 컨텍스트의 margin-top(세로 스택 간격)은
 * 여기서는 무의미하다 — 이 자리에서는 `.card-detail__meta` 의 flex 형제일 뿐이다. */
.card-detail__meta .card-tile__number-line { margin-top: 0; }
/* 「다른 언어판 보기」 구분자·링크 — B-293(2026-09-09 TICKET-910). 구분자를
 * "·"(가운데 점)에서 세로 바 " | "로 바꿔 "번호줄이 끝나고 별개 동작(링크)이
 * 시작된다"는 경계를 더 분명히 한다. 링크 자신도 line-height 24px 를 받아
 * 앞선 칩들과 같은 세로 중심에 정렬한다(위 `.card-tile__number-line
 * .card-tile__number` 와 같은 정렬 통일 작업). 새 색상 토큰 0개 —
 * `--color-border`·`--space-1` 재사용. */
.card-detail__meta-divider {
  display: inline-block;
  color: var(--color-border);
  padding-block: var(--space-1);
  line-height: 24px;
}
.card-detail__meta-lang-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  line-height: 24px;
}
/* 5b — 세트명. 이제 `.card-detail__meta` 의 flex 자식이 아니라 그 다음에 오는
 * 독립 블록 줄이다(§3-2 5b번) — 자체 margin-top 으로 4px 간격을 낸다. */
.card-detail__set {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-body-size);
  font-weight: var(--text-body-strong-weight);
  color: var(--color-link);
}
.card-detail__rule {
  margin-top: var(--space-5);
  border: 0;
  border-top: 1px solid var(--color-border);
  width: 100%;
}
/* 속성 목록 — 375px 에서 라벨-값을 세로로 쌓는다. 2열 표를 강제하지 않는다 */
.card-detail__attributes { margin: var(--space-4) 0 0; }
.card-detail__attributes div { margin-bottom: var(--space-3); }
.card-detail__attributes dt {
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
}
.card-detail__attributes dd {
  margin: 0;
  font-size: var(--text-body-size);
  color: var(--color-text-primary);
}
@media (min-width: 600px) {  /* --bp-md: 이미지 280px 고정 + 정보 가변 2단 */
  .card-detail__body { display: flex; gap: var(--space-6); align-items: flex-start; }
  .card-detail__image { flex: 0 0 280px; margin-top: 0; }
  .card-detail__info { flex: 1 1 auto; min-width: 0; }
  /* h1 은 `.card-detail__info` 의 **항상** 첫 자식이다(TICKET-905 — 3b(.card-image-credit)
   * 가 `.card-detail__image`(이미지 컬럼) 쪽으로 옮겨가 더 이상 `.card-detail__info`
   * 안에 들어오지 않는다). 그래서 3b 유무와 무관하게 h1 은 항상 이미지(flex 옆칸)
   * 상단과 나란히 서면 된다 — 옛 `.card-image-credit + .card-detail__name` 인접 선택자
   * 예외(TICKET-847)는 전제(3b 가 이 컬럼 안에 있을 수 있다)가 사라져 함께 지웠다. */
  .card-detail__name { margin-top: 0; }
}

/* ── 4b. 카드 상세 — 연관 카드 (SCREEN-001 §3-2a·§3-2b, TICKET-090) ──── */
/* §3-2 8번·9번의 구분선. 기본 .card-detail__rule(--space-5)과 margin-top 만 다르다 */
.card-detail__rule--section { margin-top: var(--space-8); }
.card-detail__rule--nav { margin-top: var(--space-6); }
.card-detail__section-title {
  margin: var(--space-4) 0 0;
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
}
/* §3-2a 5번 — 상한 초과 시 마지막 행. result-row 와 같은 높이·패딩, 썸네일 없이 가운데 정렬 */
.result-row--more {
  justify-content: center;
  text-align: center;
  color: var(--color-link);
  font-size: var(--text-body-size);
}
/* §3-2b — 이전/다음 내비게이션 */
.card-detail__nav {
  margin-top: var(--space-4);
  display: flex;
  gap: var(--space-3);
}
.card-detail__nav-link {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
/* 한쪽만 존재할 때 — 자기 너비만 차지, 좌측 정렬. 없는 쪽의 빈 칸을 만들지 않는다 (AC-8) */
.card-detail__nav--solo .card-detail__nav-link { flex: 0 1 auto; }
.card-detail__nav-label {
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
}
.card-detail__nav-value {
  margin-top: var(--space-1);
  font-size: var(--text-body-size);
  color: var(--color-link);
  word-break: keep-all;
  overflow-wrap: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── 5. 카드 타일·그리드 — SCREEN-002 §1-1 · §3-3 ────────────────────── */
.card-grid {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}
@media (min-width: 600px) { .card-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 900px) { .card-grid { grid-template-columns: repeat(4, 1fr); } }
/* **grid 트랙의 자동 최소 크기는 아이템의 min-content 다** (CSS Grid §6.6).
 * 안쪽 `.card-tile__name` 이 line-clamp 로 잘려도 트랙은 카드명 전체 폭만큼 벌어진다 —
 * 클램프는 그리는 방식이지 min-content 를 줄이지 않는다.
 * (TICKET-037 @qa 실측: .card-grid 343px 인데 scrollWidth 483 · 문서 498 / 375) */
.card-grid > li { min-width: 0; }

/* `.popular-card-grid`(SCREEN-002 §4-3 · TICKET-117·140) — **검색 결과 그리드가
 * 이 기본 규칙을 그대로 쓴다.** 홈의 인기 카드 섹션은 P9(아래, `.popular-cards`
 * 스코프)가 이 기본을 덮어쓴다 — 검색 결과는 그 재설계 대상이 아니다(SCREEN-002
 * §4-3·§4-4 는 이 티켓이 바꾸지 않았다). `.card-grid` 와 합치지 않는 이유는 취향이
 * 아니라 회귀 방지다: 바로 위 규칙 3개는 test_tokens.py 가 그 정확한 min-width
 * 선언 문자열을 패턴매치해 변이검사로 고정한 대상이다(TICKET-037) — 선택자를
 * 합치면 그 리터럴이 사라져 그 회귀 테스트가 깨진다. 값은 SCREEN-002 §3-3 과
 * 완전히 같다(재계산 없음), margin-top 만 다르다(§2-A-3 — h2 바로 아래라
 * --space-3, `.card-grid` 는 카드 상세 안에서 h2/h3 아래라 --space-4). */
.popular-card-grid {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}
@media (min-width: 600px) { .popular-card-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 900px) { .popular-card-grid { grid-template-columns: repeat(4, 1fr); } }
.popular-card-grid > li { min-width: 0; }

/* 홈 인기 카드 섹션 전용 재배치 — `SCREEN-003` §2-A(P9 전면 재설계, 2026-09-09
 * `TICKET-900`·`902`). `.popular-cards` 조상 스코프로만 좁혀 검색 결과(위 기본
 * 규칙을 그대로 쓴다)에 영향이 없게 한다 — 클래스 이름은 그대로 `popular-card-grid`
 * 하나를 공유하되(새 클래스 0건), 홈 전용 컨텍스트에서만 규칙을 덮어쓴다.
 * 375px~899px: 가로 스크롤 단일 행(개수 무관, peek 패턴 — 타일 폭 그대로 167.5px,
 * 별도 min-width 없음, `.recent-sets-row` 와 같은 레시피). 900px+(--bp-lg): 6열
 * 그리드로 전환하고 7번째부터 CSS 로 숨긴다(서버는 최대 8개 전량을 낸다 — "화면에
 * 보이는 개수"만 6, API 반환 개수 자체는 `TICKET-901` 소관). */
.popular-cards .popular-card-grid {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.popular-cards .popular-card-grid > li {
  flex: 0 0 167.5px;
  scroll-snap-align: start;
}
@media (min-width: 900px) {
  .popular-cards .popular-card-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    overflow-x: visible;
    scroll-snap-type: none;
  }
  .popular-cards .popular-card-grid > li {
    flex: initial;
    scroll-snap-align: none;
  }
  .popular-cards .popular-card-grid > li:nth-child(n+7) { display: none; }
}

/* 우측 가장자리 페이드 — SCREEN-003 §2-A-1(B-295, 2026-09-09 TICKET-910). 새 토큰
 * 없이 --color-bg 를 그라디언트 끝값으로 재사용한다. **375px~899px(가로 스크롤
 * 구간)에서만** — 900px+ 는 6열 고정이라 스크롤이 없어 페이드도 없다(미디어쿼리로
 * 가둔다, §2-A-1 원문). 자동 루프 스크롤은 기각됐다(WCAG 2.2.2 일시정지 요구·
 * 사용자가 원하는 카드에서 못 멈추는 문제 — JS 도 필요 없다, `ADR-0004` D-6). */
@media (max-width: 899px) {
  .popular-cards .popular-card-grid { position: relative; }
  .popular-cards .popular-card-grid::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 32px;
    background: linear-gradient(to right, transparent, var(--color-bg));
    pointer-events: none;
  }
}

.card-tile {
  display: block;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-2);
  text-decoration: none;
  color: inherit;
  height: 100%;
}
.card-tile__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 언어 배지(§1-8-3)의 절대 위치 기준. **오프셋이 없어 이 선언만으로는 아무것도 안
   * 움직인다** — 배지를 안 그리는 화면(`hit_strip_thumb` 등)의 그림이 바뀌지 않는다. */
  position: relative;
  width: 100%;
  aspect-ratio: 5 / 7;
  background-image: var(--card-placeholder-url);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  background-color: var(--color-surface-alt);
  border-radius: var(--radius-md);
  overflow: hidden;
  color: var(--color-text-secondary);
}
/* 확정 상태(NULL·로딩실패)에는 로딩 중 신호(카드뒷면)를 남기지 않는다 — SCREEN-001 §3-3 */
.card-tile__thumb--empty,
.card-tile__thumb[data-state="error"] {
  background-image: none;
}
.card-tile__thumb img { width: 100%; height: 100%; object-fit: contain; }
.card-tile__thumb[data-state="error"] img { display: none; }
.card-tile__thumb-fallback { display: none; }
.card-tile__thumb[data-state="error"] .card-tile__thumb-fallback,
.card-tile__thumb--empty .card-tile__thumb-fallback { display: block; }
/* 언어 배지 — SCREEN-002 §1-8-3 (신설, TICKET-656). 썸네일 안 좌상단 오버레이라
 * 타일의 세로 슬롯(1~5번)에 끼지 않는다 — 타일 높이가 변하지 않는다.
 *
 * **`.rarity-badge` 의 클래스를 공유하지 않는다** (§1-8-3 원문: 레어도 배지 색 토큰을
 * 언어 구분에 재사용하면 오독된다). 아웃라인 레시피만 시각적으로 참고했고 **새 색상
 * 토큰은 0개**다 — 이미지 위에 얹히므로 배경 `--color-surface` 로 가독성을 만든다.
 *
 * `<span>` 이라 인라인 기본값이다 — `display` 를 명시하지 않으면 `min-height` 가
 * 무시된다(test_tokens.py 인라인 레이아웃 가드). */
.language-badge {
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
}
/* 타일 안에서만 오버레이다 (§1-8-3 「위치」). **기본형은 인라인 배지**라 세트 헤더
 * h1 옆(§2-4-lang, 아래 규칙)에서는 글 흐름 그대로 놓인다 — 같은 아톰이 두 자리에서
 * 다르게 앉는 것을 수식자 대신 문맥 선택자로 표현한다(`.site-header .breadcrumb` 선례). */
.card-tile__thumb .language-badge {
  position: absolute;
  top: var(--space-1);
  left: var(--space-1);
  z-index: 1;
}
.card-tile__number {
  /* <span> 이다. **display 를 주지 않으면 인라인이라 margin-top 이 적용되지 않는다**
   * (CSS2.1 §10.8 — 인라인 박스의 세로 마진은 줄 높이에 영향을 주지 않는다). */
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-caption-size);
  color: var(--color-text-tertiary);
}
/* 최대 2줄 후 말줄임. 전체 이름은 <a> 의 aria-label 과 img alt 에 그대로 있다 */
.card-tile__name {
  margin-top: var(--space-1);
  font-size: var(--text-body-size);
  font-weight: var(--text-body-strong-weight);
  color: var(--color-text-primary);
  word-break: keep-all;
  overflow-wrap: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* card-tile--with-set — 세트명 줄. result-row(§4-3)와 같은 1줄 말줄임 레시피,
 * 167.5px 타일이라 271px 폭인 result-row 보다 더 짧게 잘릴 뿐 규칙은 같다.
 * P10 전면 재설계(2026-09-09 TICKET-900·902) 이후에도 위치·레시피는 그대로다 —
 * 세로 순서에서 "3번(카드명) 다음"으로 옮겨졌을 뿐 클래스·값은 안 바뀐다.
 *
 * `.card-tile__set-row`(신설, `SCREEN-002` §1-1-a 「세트 짧은 이름 — 프로모 배지」,
 * TICKET-953·955) — 예전엔 이 margin-top 이 `.card-tile__set` 자체에 있었지만,
 * 그 span 이 이제 flex 래퍼의 자식이라 세로 간격은 래퍼로 옮기고 자식은 가로
 * 축소만 맡는다. */
.card-tile__set-row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
}
.card-tile__set {
  /* `<span>` 은 인라인이 기본이라 overflow·text-overflow 가 무시된다
   * (`test_no_inline_element_carries_block_only_properties` 가드) — 플렉스
   * 아이템이 되면 외부 박스는 어차피 블록화되지만, 정적 CSS 검사는 그 문맥을
   * 모르므로 명시적으로 선언한다(`.code-chip`·`.card-tile__number` 선례와 같다). */
  display: block;
  flex: 1 1 auto;
  min-width: 0; /* 말줄임 유지 — 배지가 있어도 세트명이 먼저 줄어든다 */
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* card-tile--with-set 전용 — 카드명 margin-top 이 기본(§1-1, --space-1)보다 넓다
 * (`SCREEN-002` §1-1-a "2 카드명 margin-top --space-2") — 번호 단독 줄이 빠진
 * 자리라 이미지 슬롯과의 간격을 그만큼 넓게 잡는다. */
.card-tile--with-set .card-tile__name { margin-top: var(--space-2); }
/* 값이 없어도 자리가 사라지지 않는다 — 타일 높이가 흔들리지 않게 (SCREEN-002 §1-2).
 * with_set=False(기본 타일) 전용 슬롯이다 — with_set=True 는 번호줄 안에 등급 칩을
 * 직접 놓으므로(아래) 이 슬롯을 쓰지 않는다. */
.card-tile__badge-slot {
  /* 인라인이면 min-height 가 무시돼 §1-2 의 "슬롯 높이 24px 고정" 이 성립하지 않는다 */
  display: block;
  margin-top: var(--space-2);
  min-height: 24px;
}
/* 번호줄 — `SCREEN-002` §1-1-a(P10 전면 재설계, 2026-09-09 TICKET-900·902 신설).
 * 옛 "번호 단독 줄"(--space-2)·"레어도 배지 슬롯"(--space-2) 둘을 합친 자리라
 * margin-top 은 그중 하나(--space-2)와 같다 — 두 배로 늘지 않는다(둘을 대체할 뿐
 * 더하지 않는다). `card.html`(카드 상세 §3-2 5번)도 이 클래스를 그대로 재사용한다
 * — `.card-detail__meta` 안에서는 세로 스택이 아니라 flex 형제이므로 margin-top 을
 * 0으로 되돌린다(위 §4 `.card-detail__meta .card-tile__number-line` 규칙 참조). */
.card-tile__number-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
/* 번호(진하게) — 기본 `.card-tile__number`(with_set=False 전용, 위)는 캡션 크기·
 * --color-text-tertiary 로 흐리다. 번호줄 안에서는 분모와 짝을 이루는 "진한 분자"
 * 역할이라 색·굵기만 재정의한다(display 는 건드리지 않는다 — 플렉스 아이템은
 * 원래 지정값과 무관하게 블록화되므로 그대로 둬도 레이아웃이 깨지지 않고,
 * `display` 를 다시 선언하면 `test_result_row_text_block_stacks_vertically` 가
 * 요구하는 「.card-tile__number 는 어디서나 block 하나로 선언된다」가 깨진다). */
.card-tile__number-line .card-tile__number {
  margin-top: 0;
  font-weight: 700;
  color: var(--color-text-primary);
  /* B-293 정렬 통일(2026-09-09 TICKET-910) — 언어 배지·세트코드 칩·등급 칩은
   * 이미 24px 고정 높이다. 번호/분모 텍스트만 높이 정의가 없어 들쑥날쑥해
   * 보였다 — 같은 세로 중심선에 앉히려고 line-height 를 그 칩들과 맞춘다. */
  line-height: 24px;
}
/* 분모(흐리게) — 분모가 없으면(대다수 목록 컨텍스트, §0-1 실측) 이 span 자체가
 * 렌더되지 않는다(매크로·card.html 양쪽 `{% if %}`). */
.card-tile__number-den {
  color: var(--color-text-tertiary);
  font-weight: 400;
}
/* 세트코드 칩 — `.rarity-badge`/`.rarity-badge--none` 과 같은 규격(높이 24·
 * --radius-sm·아웃라인)을 값만 복제한다(클래스는 공유하지 않는다 — 언어·레어도·
 * 상태 칩과 같은 오독 방지 원칙, `language-status-chip`·`status-badge` 선례). */
.code-chip {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-badge-size);
  font-weight: var(--text-badge-weight);
  line-height: var(--text-badge-line);
  white-space: nowrap;
  background: transparent;
  color: var(--color-badge-none-text);
  border: 1px solid var(--color-badge-none-border);
}
/* 카드 상세(§3-2 5번) 전용 margin-top 재정의는 `.card-detail__meta` 절(위, §4)에
 * 이미 있다 — 여기서 다시 선언하지 않는다(같은 선택자를 두 곳에 두지 않는다). */

/* 「프로모」 배지 — `SCREEN-002` §2-4-promo (신설, TICKET-953·955). `.code-chip`/
 * `.rarity-badge--none` 과 같은 아웃라인 레시피를 값만 복제한다(클래스는 공유하지
 * 않는다 — 언어·레어도·상태 배지와 같은 오독 방지 원칙). 두 문맥이 이 기본형을
 * 공유하고 높이만 문맥마다 다르다(아래 두 규칙) — §2-4-promo "문맥에 맞는 기존
 * 칩 높이를 그대로 따른다. 새 규격을 만들지 않는다". */
.promo-badge {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-badge-size);
  font-weight: var(--text-badge-weight);
  line-height: var(--text-badge-line);
  white-space: nowrap;
  background: transparent;
  color: var(--color-badge-none-text);
  border: 1px solid var(--color-badge-none-border);
}
/* 카드 타일 세트 표기 옆 — code-chip·rarity-badge 와 같은 높이 24 (§1-1-a).
 * 줄어들지 않는 형제로 고정한다 — 세트명(.card-tile__set)이 먼저 줄어든다. */
.card-tile__set-row .promo-badge {
  height: 24px;
  flex-shrink: 0;
}

/* ── 6. 세트 목록 — SCREEN-002 §2-3 · §2-4 ───────────────────────────── */
.page__title {
  margin: var(--space-3) 0 0;
  font-size: var(--text-display-size);
  line-height: var(--text-display-line);
  font-weight: var(--text-display-weight);
  color: var(--color-text-primary);
}
/* 세트 헤더 h1 옆 표기 언어 배지 — SCREEN-002 §2-4-lang·§3-4 3번 (TICKET-656).
 * "세트명 텍스트 바로 옆(같은 줄, margin-left --space-1)" 원문 그대로다. 제목 글자
 * 크기를 따라가지 않는다 — 배지는 캡션 크기(`.language-badge` 기본형)를 유지한다. */
.page__title .language-badge {
  margin-inline-start: var(--space-1);
  vertical-align: middle;
}
.page__caption {
  margin: var(--space-1) 0 0;
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
}
/* 세트 목록 시리즈 링크 목록 — SCREEN-002 §2-3-1′ (2026-09-05 재설계, TICKET-711 —
 * `ADR-0033` D-11). 옛 필터칩(라디오+CSS `:has()`, `.series-filter`/`.series-filter__chip`
 * + 세트 타일 숨김 규칙 7개, `TICKET-126·127`)을 없앤다 — 이 링크는 `/series/{segment}/`
 * 로 **떠나는** 순수 내비게이션이라 "선택 상태"도 "숨길 대상"도 없다. 그래서 등급
 * 필터칩(§3-5)과 달리 세트 목록(`.set-list`)과 공통 조상으로 묶을 이유도 없다.
 *
 * **필박스 시각은 그대로 재사용한다** — 값은 사용자가 확인한 시안(`TICKET-710` 블록 B,
 * `PREVIEW-SCREEN-002-세트목록-시리즈링크.html`) 그대로다. `--radius-pill` 이라는
 * 별칭은 없다 — `--pv-radius-pill` 을 직접 참조한다(`.badge-live` 가 이미 이 raw 토큰을
 * 직접 쓰는 것과 같은 방식, 새 별칭 0건).
 *
 * **QA FAIL 재현 (2026-09-05, `TICKET-711` 재작업) — flex 가 걸리는 대상이 틀렸었다.**
 * 마크업은 `<nav class="series-links"><ul class="series-links__list"><li
 * class="series-links__item"><a>…</a></li>…</ul></nav>` 다(`molecules.html`).
 * `.series-links`(nav)에 `display:flex` 를 걸면 **그 유일한 자식인 `<ul>` 하나만**
 * flex 아이템이 된다 — `<li>` 들은 `<ul>` 안에서 여전히 기본 블록 흐름(세로 1열)이다.
 * 시안(`PREVIEW-SCREEN-002-세트목록-시리즈링크.html:179` `style="display:contents"`)은
 * `<ul>` 을 레이아웃에서 지워 `<li>` 를 nav 의 직계 flex 아이템으로 만드는 방식을 썼지만,
 * 그 인라인 스타일을 이 컴포넌트로 옮기지 않아 실렌더가 시안과 달라졌다. **`display:
 * contents` 대신 flex 를 `<ul>`(`.series-links__list`) 에 직접 건다** — `display:
 * contents` 는 일부 브라우저 조합에서 자손의 접근성 트리 계산이 불안정한 전례가 있고
 * (WAI-ARIA 실무 이슈), 이 저장소는 이미 `<ul>` 에 `display:grid` 를 직접 거는
 * 선례(`.set-list`, 아래 §6)가 있다 — 그 선례처럼 목록 시맨틱은 `<ul>`/`<li>` 마크업
 * 자체가 준다(`role="list"` 를 별도로 붙이지 않는다, `.set-list` 와 같은 판단). */
.series-links {
  margin: var(--space-3) 0 0;
}
.series-links__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.series-links__item {
  margin: 0;
  padding: 0;
}
/* 44px 터치 타겟 — `.rarity-filter__chip`·(옛)`.series-filter__chip` 과 같은 규격. */
.series-links__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--pv-radius-pill);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
  text-decoration: none;
}
/* 골드 톤 hover — 시안이 확인한 값 그대로(`--pv-color-gold` 직접 참조, `.site-footer
 * __phase-current` 등 이미 있는 것과 같은 방식). */
.series-links__link:hover {
  color: var(--pv-color-gold);
  border-color: var(--pv-color-gold);
}
.series-links__link:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* 세트 목록 언어 패싯 자리 — SCREEN-002 §2-3-1′ 끝 "시리즈 링크 목록 바로 다음 줄
 * (margin-top --space-2)" (TICKET-657, ADR-0042 T-6 원문 유지 · TICKET-711 로 선택자만
 * 갱신). `.language-facet` 자체(모양)는 위 언어 패싯 섹션에 이미 있다 — 여기서는
 * **이 화면에서의 위치**만 정한다. 언어가 1개뿐인 지금은 매크로가 아무것도 안 그려
 * 이 규칙은 어떤 화면에도 적용되지 않는다. */
.series-links + .language-facet {
  margin-top: var(--space-2);
}
/* 2026-09-09 `TICKET-910`(B-291·B-292) — `.language-facet` 를 `.language-select`
 * 로 교체(값·자리는 그대로, 셀렉터만). */
.series-links + .language-select {
  margin-top: var(--space-2);
}

/* 시리즈 전용 페이지 언어 패싯 자리 — SCREEN-002 §2-10-3 "세트 타일 목록 위 margin-top
 * --space-3 자리"(TICKET-658, ADR-0042 T-7). `/sets/` 위 규칙과 값이 다르다(--space-2) —
 * 이 페이지엔 시리즈 필터 fieldset 이 없어(§2-10-3) h1 바로 다음이 그 자리다. 언어가
 * 1개뿐인 지금은 매크로가 아무것도 안 그려 이 규칙은 어떤 화면에도 적용되지 않는다. */
.page__title + .language-facet {
  margin-top: var(--space-3);
}
/* 2026-09-09 `TICKET-910`(B-291·B-292) — `.language-facet` 를 `.language-select`
 * 로 교체(값·자리는 그대로, 셀렉터만). */
.page__title + .language-select {
  margin-top: var(--space-3);
}

.set-list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 600px) { .set-list { grid-template-columns: repeat(2, 1fr); gap: var(--space-4); } }
@media (min-width: 900px) { .set-list { grid-template-columns: repeat(3, 1fr); } }
/* 위와 같은 이유. 세트명 43자(ADR-0002 §2)에 `word-break: keep-all` 이 걸려 있어
 * min-content 가 43자 전체다 (TICKET-037 @qa 실측: 문서 711 / 375). */
.set-list > li { min-width: 0; }

/* 타일 루트가 <a> 가 아니라 <article> 이다 — 히트카드가 자기 링크를 갖기 때문 (SCREEN-002 §2-4) */
.set-tile {
  align-self: stretch;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
}
.set-tile__name {
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-link);
  word-break: keep-all;
  overflow-wrap: break-word;
  text-decoration: none;
}
/* 세트명 옆 표기 언어 배지 — SCREEN-002 §2-4-lang (TICKET-657, ADR-0042 T-6). `.page__title
 * .language-badge`(세트 상세 h1, TICKET-656)와 같은 문맥 선택자 패턴 — 배지 자체는 이미지
 * 위 오버레이가 아니라 글 흐름 그대로 놓이므로 제목 글자 크기를 따라가지 않는다. */
.set-tile__name + .language-badge {
  margin-inline-start: var(--space-1);
  vertical-align: middle;
}
.set-tile__meta,
.set-meta {
  margin-top: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
}
/* 원본 대소문자 그대로 표시한다. 소문자는 URL 에만 적용된다 (ADR-0003 U-4) */
.set-code {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 var(--space-2);
  background: var(--color-surface-alt);
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
}
/* 「예정」 배지 — SCREEN-002 §2-4-status (2026-09-08 신설, TICKET-865 스펙 · TICKET-871
 * 첫 구현). set_code 배지와 같은 높이(20)를 따르고, 시각 레시피는 §1-8-2 언어 패싯의
 * "선택됨" 칩(.language-facet__chip--selected)을 그대로 재사용한다 — 새 커스텀
 * 프로퍼티 0개. 다른 메타 배지와 달리 accent 를 쓰는 이유는 §2-4-status 원문 참조
 * ("지금 행동할 것이 없다/곧 온다"는 시간적 신호라 필터칩과 같은 무게로 다룬다). */
.status-badge--scheduled {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-sm);
  background: var(--color-surface-alt);
  color: var(--color-text-primary);
}
/* 세트 상세 h1 옆(실제로는 `.set-meta`, set_code 배지·예정 배지와 같은 줄 — 위
 * `.status-badge--scheduled` 주석 참조) — set_code 배지·예정 배지와 같은 높이 20
 * (§2-4-promo). `.card-tile__set-row .promo-badge`(24px)와는 문맥이 다른 규격이다. */
.set-meta .promo-badge { height: 20px; }
.set-meta__official { color: var(--color-text-tertiary); }
/* 세트 대표 이미지 스트립 — SCREEN-002 §2-6. **최대 4열 73.75px, 필요하면 축소** —
 * 아이템 개수(1~4)와 무관하게 칸 크기는 여전히 고정이다(TICKET-162 신고 2·3, 트랙
 * 크기는 컨테이너 폭에서만 나오고 실제로 들어찬 아이템 수와는 무관하다). 옛
 * `flex: 1 1 0` 은 개수가 4보다 적으면 폭이 컨테이너÷개수로 늘어났다 — 그 문제는 그대로
 * 안 재발한다.
 * **TICKET-164 — `repeat(4, 73.75px)` 고정폭이 부모 타일 폭보다 넓어지는 순간(타일
 * 내부 가용폭 345px 미만 전 구간 — 1열 <364px·2열 600~735px·3열 900px+ 상시,
 * TICKET-163 실측) 스트립이 타일 밖으로 넘쳤다. `minmax(0, 73.75px)` 로 바꿔 트랙이
 * 가용폭 이상으로 넓어지지 않게 한다 — 넓은 화면(375px 1열 구간)에서는 그대로
 * 73.75px 까지 차고(11px 여유 그대로), 가용폭이 부족하면 넘치는 대신 균등하게
 * 줄어든다. `gap` 은 고정폭(--space-2)이라 안 줄지만, 트랙 최소값이 0 이라 오버플로우가
 * 구조적으로 불가능해진다(4×0 + 3×gap ≤ 어떤 실사용 뷰포트 폭보다도 작다). */
.hit-strip {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 73.75px));
  gap: var(--space-2);
}
.hit-strip li { min-width: 0; }
/* `<a>` 가 타일 루트다 — `.card-tile` 의 배경·테두리·패딩을 물려받지 않는다(§2-6 "레이아웃
 * 변경 없음"은 그리드·썸네일 크기 얘기이지 카드 타일의 틀을 뜻하지 않는다 — 이 스트립은
 * 처음부터 이미지 하나뿐이었다). `.card-tile__thumb` 안쪽 이미지 슬롯 규칙만 재사용한다.
 * width 를 따로 주지 않는다 — `<li>` 가 이미 grid 트랙(위 `.hit-strip`, 최대 73.75px)이라
 * `display: block` 기본 동작(부모 폭을 채움)만으로 같은 값이 나온다. 값을 두 곳에 적으면
 * 하나만 고치고 잊는 사고가 난다. `.card-tile__thumb` 가 `width:100%`(§ 위 참조)라
 * 트랙이 줄어들면 썸네일도 같이 줄어들고, `aspect-ratio:5/7` 가 높이를 비례로 따라간다. */
.hit-strip__thumb {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* 상품 유형 3그룹 — SCREEN-002 §2-3 (신설, TICKET-863 · B-1). 확장팩(기본 펼침)은
 * `<h2>` + 평문 `<ul>`, 덱·박스·세트/프로모(접힘)는 `<details>` — §2-3-2 회색 그룹과
 * 같은 네이티브 disclosure 다(JS 도 `:has()` 도 안 쓴다). `.set-list` 자체(그리드·
 * 간격)는 위 §6 규칙을 그대로 물려받는다 — 여기서는 그룹 헤더/summary 만 새로 정의한다. */
.set-list-group__heading {
  margin: var(--space-4) 0 0;
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
}
.product-type-group {
  margin-top: var(--space-4);
}
.product-type-group__summary {
  display: block;
  min-height: 44px;
  padding-block: var(--space-2);
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
  cursor: pointer;
}
.product-type-group__summary:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.product-type-group .set-list {
  margin-top: var(--space-2);
}

/* 회색 그룹 「한국 미발매」 — SCREEN-002 §2-3-2 (신설, TICKET-863 · B-2 AC-1,
 * `ADR-0048` §5-5 T-8 의 답). 네이티브 `<details>`/`<summary>` — 열림/닫힘·키보드
 * 접근성·스크린리더 알림을 브라우저가 무료로 제공한다(§2-3-2 "더 단순한 도구가
 * 있다"). 기본 상태는 닫힘 — `open` 속성을 붙이지 않는다(마크업 쪽 결정). */
.lang-gap-group {
  margin-top: var(--space-4);
}
.lang-gap-group__summary {
  display: block;
  min-height: 44px;
  padding-block: var(--space-2);
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
  cursor: pointer;
}
.lang-gap-group__summary:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.lang-gap-group__intro {
  margin: var(--space-2) 0 0;
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-tertiary);
}
.lang-gap-group__list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 600px) { .lang-gap-group__list { grid-template-columns: repeat(2, 1fr); gap: var(--space-4); } }
@media (min-width: 900px) { .lang-gap-group__list { grid-template-columns: repeat(3, 1fr); } }
.lang-gap-group__list > li { min-width: 0; }
/* 회색 그룹 안 타일 — `.set-tile` 배경만 더 무디게 한다(§2-3-2 "세트명은 여전히
 * --color-link <a> 다" — 클릭 가능함을 잃지 않는다). */
.set-tile--unavailable {
  background: var(--color-surface-alt);
}

/* 언어 상태 칩 — SCREEN-002 §1-9 (신설, TICKET-863 · B-2 AC-3). `.language-badge`
 * 와 다른 아톰이다(§1-9 "혼동하지 마라" — 배지는 단일 값 오버레이, 이 칩은 평문
 * 텍스트). 배경은 둘 다 없음(투명) — 메타 행의 다른 캡션과 같은 급으로 보이게
 * 한다. 미발매(없음) 변형만 지금 쓰인다(회색 그룹, §2-3-2) — 있음 변형은 §1-9 값
 * 그대로 미리 정의해 둔다. */
.language-status-chip {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-caption-size);
}
.language-status-chip--available { color: var(--color-text-secondary); }
.language-status-chip--unavailable { color: var(--color-text-tertiary); }

/* ── 7. 검색 — SCREEN-002 §4-3 · §4-4 ────────────────────────────────── */
/* 폼은 입력(3-a·3-b)만 감싼다 (2026-09-02 정정, TICKET-134·135). 상태 줄(4)·정렬 링크(5)는
 * 이 `<form>` 의 형제 요소다 — 세로로 쌓이므로 flex 컨테이너가 아니다 */
.search-form { margin-top: var(--space-4); }
/* 입력 줄. 좌 검색 아이콘 24 · 우 지우기 44×44 를 한 상자 안에 둔다 (§4-4 3-a) */
.search-form__field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 48px;
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-text-tertiary);
}
.search-form__field > svg { flex-shrink: 0; }
.search-form__input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  font-family: var(--pv-font-body);
  font-size: var(--text-body-size);
}
.search-form__input::placeholder { color: var(--color-text-tertiary); }
/* 터치 타겟 44×44 (§4-7). `<a>` 라 display 를 명시하지 않으면 크기가 안 먹는다 */
.search-form__clear {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-secondary);
  font-size: var(--text-title-size);
  text-decoration: none;
}
/* §4-4 3-b. 가상키보드의 Go 키에만 기대지 않는다 — 탭할 대상이 화면에 보여야 한다 */
.search-form__submit {
  width: 100%;
  min-height: 48px;
  margin-top: var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-accent);
  color: var(--color-bg);
  font-family: var(--pv-font-body);
  font-size: var(--text-body-size);
  font-weight: var(--text-body-strong-weight);
}
.search-status {
  margin: var(--space-2) 0 0;
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
}
/* §4-4 5번. `<form>` 의 형제가 됐다(2026-09-02, TICKET-134·135) — `.search-form` 스코프가
 * 아니라 DOM 상 바로 앞의 상태 줄에 인접 형제 선택자로 건다. 결과 0건이면 정렬 링크
 * 자체가 렌더되지 않아(PRD-001 §10) 이 선택자가 매치할 대상이 없다.
 * **2026-09-09 정정(`TICKET-910` B-298, §4-3-c)** — 정렬+언어 선택이 `.search-controls`
 * 한 줄로 합쳐지며 `.sort-toggle` 이 더 이상 `.search-status` 의 직계 형제가 아니다.
 * 셀렉터를 그 감싸개로 옮긴다(값은 그대로 --space-3). */
.search-status + .search-controls { margin-top: var(--space-3); }
/* `.search-controls` — 정렬 컨트롤 + 언어 선택 드롭다운을 한 줄에 담는 감싸개(새
 * 컴포넌트 아님, 배치 전용). 375px 에서 넘치면 자연히 다음 줄로 감긴다. */
.search-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
/* `<a>` 는 그 자체로 포커스 가능하다 — 세트별 목록의 `:has(input:focus-visible)` 트릭
 * (라디오 은닉용)이 적용되지 않는다. 전역 `:focus-visible`(L154)이 이미 outline 을 그려
 * 주지만, 이 화면의 정렬 컨트롤임을 코드에서 바로 알 수 있도록 명시적으로도 한 줄 둔다
 * (§4-4 5번 "포커스 표시" — `--color-focus-ring` 재사용, 새 토큰 0건). 세트별 목록은
 * `<label>` 자신이 포커스를 받지 않으므로 이 규칙과 충돌하지 않는다. */
.sort-toggle__option:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
/* `.result-row` 자체는 남긴다 — `card.html`(SCREEN-001 §3-2a "전체 n장 보기" 막대)이
 * 좌우 패딩·높이 레시피만 재사용 중이다 (SCREEN-002 §4-3 폐기 주석, `TICKET-140`).
 * **`__thumb`/`__text`/`__name`/`__set`/`__meta`(1열 행 내부 레이아웃)와 `.result-list`
 * 는 검색 결과 전용이었다 — `result-row` 가 `card-tile--with-set` 그리드로 바뀌며 이
 * 화면에서 더 안 쓴다(`TICKET-141`). 지운다 — 어느 템플릿도 이 클래스를 참조하지 않는다. */
.result-row {
  display: flex;
  gap: var(--space-3);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--color-border);
  text-decoration: none;
  color: inherit;
}
/* §4-4-1 — search-pagination. JS 없음, `<a href>` 뿐이다 (`ADR-0004` D-6 · `ADR-0017` D-1). */
.search-pagination {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
}
/* [이전]·[다음] — 44×44 이상 터치 타겟, 텍스트 그대로가 접근명이다 */
.search-pagination a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-link);
  text-decoration: none;
}
/* 현재 페이지 표시 — 링크가 아니므로 테두리를 두지 않는다(클릭 가능해 보이면 안 된다) */
.search-pagination [aria-current="page"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding-inline: var(--space-3);
  color: var(--color-text-primary);
  font-weight: var(--text-body-strong-weight);
}
.panel {
  margin-top: var(--space-3);
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.panel p { margin: 0 0 var(--space-2); }
/* 마지막 줄 아래 여백은 패널 패딩이 이미 갖고 있다 (검색 0건 패널이 <p> 둘이다) */
.panel p:last-child { margin-bottom: 0; }
/* B-187(TICKET-626)로 제거됨: `.panel--error` — 어느 템플릿도 쓰지 않는 죽은 클래스였다
 * (grep 확인, `panel` 을 쓰는 곳은 search.html · set.html 뿐이고 둘 다 modifier 없음).
 * `--color-error-bg`/`--color-error-text` 토큰 자체는 유지한다 — `SCREEN-001` §1-1이
 * 정한 디자인 토큰 49개 중 하나라 `test_tokens.py::test_app_css_defines_exactly_the_canonical_tokens`
 * 가 :root 정의를 요구한다(사용처 유무와 무관). */

/* 등급 그룹 헤더 3종 — 시각적으로 완전히 동일하다 (SCREEN-002 §1-6) */
.group-heading {
  margin: 0;
  padding-block: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-body-size);
  font-weight: var(--text-body-strong-weight);
  color: var(--color-text-primary);
}
.group-heading__count {
  font-size: var(--text-caption-size);
  font-weight: var(--text-caption-weight);
  color: var(--color-text-secondary);
}

/* ── 8. 홈 — SCREEN-003 §2 (TICKET-660, ADR-0042 T-9 재조립) ─────────────
 * 다크 전용 `--pv-*` 를 직접 참조한다(§2 머리말 "이 절에서 --pv-* 로 시작하지 않는
 * 토큰이 나오면 문서 오류") — 인기 카드 섹션(아래 §8-2 대응)만 예외로 옛
 * SCREEN-001 별칭 토큰을 그대로 쓴다(SCREEN-003 §8-2 "미변경"). */

/* 히어로 — §2 2번. 900px+ 2컬럼(좌 텍스트, 우 통계) */
.hero { margin-top: var(--pv-space-8); }
@media (min-width: 900px) {
  .hero { display: grid; grid-template-columns: 1fr auto; gap: var(--pv-space-10); align-items: center; }
}

/* Phase 배지 — §2 2-1. 점(.pulse)은 aria-hidden, 텍스트가 이미 상태를 말로 전달한다 */
.badge-live {
  display: inline-flex;
  align-items: center;
  gap: var(--pv-space-2);
  padding: 4px 12px;
  border-radius: var(--pv-radius-pill);
  font-family: var(--pv-font-mono);
  font-size: var(--pv-text-micro-size);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pv-color-gold);
  border: 1px solid var(--pv-color-gold);
}
@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .5; transform: scale(.8); }
}
.pulse { display: inline-block; animation: pulse-dot 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .pulse { animation: none; } }

/* h1 — §2 2-2 */
.hero__title {
  margin: var(--pv-space-3) 0 0;
  font-family: var(--pv-font-display);
  font-size: var(--pv-text-hero-size);
  font-weight: var(--pv-text-hero-weight);
  letter-spacing: var(--pv-text-hero-tracking);
  color: var(--pv-text-primary);
  line-height: 1.15;
}
/* 소개문 — §2 2-3 */
.hero__desc {
  margin: var(--pv-space-3) 0 0;
  font-size: var(--pv-text-body-size);
  color: var(--pv-text-secondary);
  max-width: 46ch;
}
/* CTA 그룹 — §2 2-4. 375px 에서 폭 꽉 채운 세로 스택으로 랩 */
.hero__cta {
  margin-top: var(--pv-space-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--pv-space-3);
}
/* 주 CTA — §2 2-4-1 */
.btn-primary {
  padding: 12px 28px;
  border-radius: var(--pv-radius-md);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--pv-font-display);
  font-weight: 700;
  font-size: .8rem;
  letter-spacing: .08em;
  background: var(--pv-color-gold);
  color: var(--pv-bg-base);
  box-shadow: var(--pv-shadow-glow-gold);
  text-decoration: none;
}
/* 보조 CTA — §2 2-4-2. 이번 사이클엔 항상 disabled(AC-2) */
.btn-ghost {
  padding: 12px 22px;
  border-radius: var(--pv-radius-md);
  min-height: 44px;
  font-family: var(--pv-font-display);
  font-weight: 600;
  font-size: .8rem;
  letter-spacing: .08em;
  background: transparent;
  color: var(--pv-text-disabled);
  border: 1px solid var(--pv-border-subtle);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  cursor: not-allowed;
}
.btn-ghost__sub {
  font-family: var(--pv-font-body);
  font-size: var(--pv-text-label-size);
  color: var(--pv-text-muted);
  letter-spacing: normal;
  font-weight: 400;
}

/* 통계 배지 그리드 — §2 2-5. 375px 에서도 2×2 유지(4칸뿐이라 1열로 풀 필요 없다) */
.stat-group {
  margin-top: var(--pv-space-5);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--pv-space-2);
  max-width: 320px;
}
.stat-box {
  background: var(--pv-bg-elevated);
  border: 1px solid var(--pv-border-default);
  border-radius: var(--pv-radius-sm);
  padding: 10px 14px;
  text-align: center;
}
.stat-box__value {
  font-family: var(--pv-font-display);
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--pv-text-primary);
}
.stat-box__label {
  margin-top: 4px;
  font-family: var(--pv-font-mono);
  font-size: var(--pv-text-label-size);
  color: var(--pv-text-muted);
  letter-spacing: var(--pv-text-label-tracking);
}

/* 인기 카드 섹션 — SCREEN-003 §2-A-3·§8-2 "미변경, SCREEN-001 토큰 그대로"
 * (TICKET-117 그대로, 이 티켓은 margin-top 값만 새 세로 순서에 맞춘다). */
.popular-cards { margin-top: var(--pv-space-8); }
.popular-cards__heading {
  margin: 0;
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
}

/* 최근 발매 세트 섹션 — SCREEN-003 §2-C (2026-09-08 신설, ADR-0062 · TICKET-871).
 * 인기 카드와 시리즈 선택 사이 — 어느 것이 앞에 오든(§2 3번, 조건부 렌더) 각 섹션이
 * 독립적으로 margin-top 을 내므로 순서가 바뀌어도 간격 규칙이 깨지지 않는다. */
.recent-sets { margin-top: var(--pv-space-8); }
.recent-sets__heading {
  margin: 0 0 var(--space-3);
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-text-primary);
}
/* §2-C-6 — 375px 에서도 600px+/900px+ 에서도 가로 스크롤 단일 행이다(그리드로
 * 전환하지 않는다). `set_tile()` 내부 CSS 는 하나도 건드리지 않는다 — 감싸는
 * 컨테이너만 새로 정의한다. JS 0줄: 키보드 포커스 이동 시 네이티브
 * scroll-into-view 가 이 컨테이너 안에서 자동으로 일어난다. */
.recent-sets-row {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
/* 우측 가장자리 페이드 — SCREEN-003 §2-C-6(B-295, 2026-09-09 TICKET-910), §2-A-1
 * 과 같은 CSS(셀렉터만 교체). **모든 폭에서 렌더한다** — 이 행은 900px+ 에서도
 * 그리드로 전환하지 않고 가로 스크롤을 유지하므로(위 §2-C-6 "600px+·900px+" 행)
 * §2-A-1 과 달리 미디어쿼리로 가두지 않는다. */
.recent-sets-row::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 32px;
  background: linear-gradient(to right, transparent, var(--color-bg));
  pointer-events: none;
}
/* min-width 300px — 375px 컨테이너(343px)에서 다음 카드가 약 43px 보여
 * "더 있다"는 신호를 준다(peek 패턴, §2-C-6). */
.recent-sets-row > li {
  min-width: 300px;
  flex-shrink: 0;
  scroll-snap-align: start;
}

/* 시리즈 선택 섹션 — §2 4번 */
.series { margin-top: var(--pv-space-8); }
.series__title {
  margin: 0 0 var(--pv-space-1);
  font-family: var(--pv-font-display);
  font-size: var(--pv-text-title-size);
  font-weight: var(--pv-text-title-weight);
  color: var(--pv-text-primary);
}
.series__subtitle {
  margin: 0 0 var(--pv-space-4);
  font-size: var(--pv-text-body-size);
  color: var(--pv-text-secondary);
}
.series-card-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--pv-space-4);
}
@media (min-width: 600px) { .series-card-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .series-card-grid { grid-template-columns: repeat(3, 1fr); } }
/* TICKET-758 — 그리드 아이템은 `li`(자동 `align-items: normal`→`stretch`로 이미
 * 행 최대 높이만큼 늘어난다)지만, 그 안의 `<a class="cover-card">`는 li 의 평범한
 * 블록 자식이라 li 가 늘어나도 자기 콘텐츠 높이만큼만 그려진다 — li 는 맞는데
 * 카드 테두리(보더 박스)가 짧은 카드만큼만 그려져 하단이 어긋나 보였다.
 * li 를 세로 flex 컨테이너로 바꾸고 `.cover-card` 에 `flex: 1` 을 줘서 li 의
 * 늘어난 높이까지 카드 박스 자체가 채우게 한다 — 그리드 트랙 높이 계산은
 * 스트레치 이전의 콘텐츠 높이를 쓰므로(스펙상 정렬은 트랙 산정 이후 단계) 트랙
 * 자체가 찌그러지지 않는다. */
.series-card-grid > li { min-width: 0; display: flex; flex-direction: column; }

/* 시리즈 커버 카드 — §2 4-3. `--accent-color` 그라디언트는 뺐다(색 리터럴 가드 —
 * molecules.html cover_card 주석 참조) — `.cover-card__art` 는 단색이다. */
.cover-card {
  display: block;
  flex: 1; /* TICKET-758 — li(flex column)의 늘어난 높이를 카드 박스가 마저 채운다 */
  min-width: 0; /* TICKET-758 — flex 아이템이 된 뒤: 안쪽 .cover-card__desc 의
    -webkit-line-clamp 가 기대는 축소 가능 폭을 막지 않는다 (TICKET-037 가드) */
  border-radius: var(--pv-radius-lg);
  border: 1px solid var(--pv-border-default);
  overflow: hidden;
  background: var(--pv-bg-surface);
  text-decoration: none;
}
.cover-card__art {
  height: 96px;
  display: flex;
  flex-direction: column;
  padding: var(--pv-space-2) var(--pv-space-3);
  background: var(--pv-bg-overlay);
}
/* TICKET-749 — 발매연도 라벨(PREVIEW-747 안2). release_year 가 없으면 web_app/
 * presenters/home.py 가 item.year_label 자체를 안 채워 이 span 이 렌더되지 않는다
 * (AC-1). 기존 토큰만 참조 — 새 --pv-* 정의 0건(AC-3). */
.cover-card__year {
  display: block;
  font-family: var(--pv-font-mono);
  font-size: var(--pv-text-label-size);
  font-weight: 700;
  letter-spacing: var(--pv-text-label-tracking);
  color: var(--pv-text-muted);
}
.cover-card__count {
  /* flex 아이템이라 실제 렌더에는 영향이 없다(flex 컨테이너의 자식은 항상 블록화된다) —
   * `display` 를 명시하지 않으면 375px 정적 검사기가 "인라인 기본 태그(span)에 블록
   * 전용 속성(margin-top)" 으로 오탐한다(test_tokens.py `_inline_layout_violations`). */
  display: block;
  margin-top: auto;
  align-self: flex-end;
  font-family: var(--pv-font-mono);
  font-size: var(--pv-text-label-size);
  color: var(--pv-text-muted);
}
/* TICKET-744 — span 은 기본 display:inline 이라 padding 이 자식 블록 상자를
 * 감싸지 못하고 인라인 상자만 쪼갠다(이름이 카드 모서리에 붙어 보였다).
 * `display: block` 을 명시해야 padding 이 실제로 적용된다. */
.cover-card__body { display: block; padding: 13px 15px; }
.cover-card__name {
  display: block;
  font-family: var(--pv-font-display);
  font-weight: 700;
  font-size: 1rem;
  color: var(--pv-text-primary);
}
.cover-card__desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 4px 0 0;
  font-size: .8125rem;
  line-height: 1.4;
  color: var(--pv-text-secondary);
}
/* hover/focus 리프트 — 순수 CSS 전환, JS 없음(CLAUDE.md §7 · ADR-0037 D-2) */
.card-lift { transition: transform .25s ease, box-shadow .25s ease, border-color .2s; }
.card-lift:hover, .card-lift:focus-visible {
  transform: translateY(-5px) scale(1.015);
  box-shadow: var(--pv-shadow-card-hover);
}
@media (prefers-reduced-motion: reduce) {
  .card-lift { transition: none; }
  .card-lift:hover, .card-lift:focus-visible { transform: none; }
}

.home-all-sets {
  margin-top: var(--space-4);
  display: flex;
  align-items: center;
  height: 44px;
  font-size: var(--text-body-size);
  font-weight: var(--text-body-strong-weight);
  color: var(--color-link);
  text-decoration: none;
}

/* 시리즈 전용 페이지 `/series/{segment}/` 상호링크 — SCREEN-002 §2-10-4 (TICKET-172).
 * **2026-09-05 TICKET-660 — `.home-all-sets` 의 유일한 소비자가 이 페이지가 됐다.**
 * 홈은 이 티켓에서 히어로 CTA(`.btn-primary`)로 같은 목적지를 대체했다 — 이름은
 * 옛 이름 그대로 남긴다(series.html 이 이미 참조 중이라 이름 자체를 바꾸는 것은 이
 * 티켓 범위 밖이다). */
.series-crosslinks {
  margin-top: var(--space-6);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
.series-crosslinks ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-4);
}
.series-crosslinks a {
  display: flex;
  align-items: center;
  height: 44px;
  font-size: var(--text-body-size);
  color: var(--color-link);
  text-decoration: none;
}

/* ── 9. 헤더 계정 진입점 — SCREEN-001 §2-1-account (신설, TICKET-978, ADR-0050 T-8) ──
 * 새 커스텀 프로퍼티 0개 — §1(색·스페이싱)·§2-1-nav 가 이미 쓰는 --color-* · --space-*
 * 값만 재사용한다(§2-1-account 원문). `<a>`/`<label>` 은 인라인 기본 태그라 세로
 * padding·min-height 를 주려면 display 를 명시해야 한다(test_tokens.py 인라인 레이아웃
 * 가드 — .series-crosslinks a 의 선례와 같은 이유). */
.site-header__login {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: var(--space-2);
  color: var(--color-link);
  font-size: var(--text-body-size);
  text-decoration: none;
}
.site-header__account { position: relative; }
/* 원형 트리거 — 표시 이름 이니셜 1글자(§2-1-account "이미지 업로드 아바타는 범위 밖"). */
.site-header__account-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-surface-alt);
  color: var(--color-text-primary);
  font-size: var(--text-caption-size);
  font-weight: var(--text-body-strong-weight);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.site-header__account-trigger::-webkit-details-marker { display: none; }
.site-header__account-menu {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--space-2);
  min-width: 160px;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  z-index: 10;
}
.site-header__account-menu a,
.site-header__account-menu button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-primary);
  font-family: var(--pv-font-body);
  font-size: var(--text-body-size);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* ── 10. 인증 폼 — SCREEN-004 §0-2·§0-3 (신설, TICKET-978, ADR-0050 T-8) ──────────
 * `.form-field__box` 는 `.search-form__field` 와 같은 시각 레시피(48px·--color-border·
 * --radius-md) 값 그대로다 — 라벨·힌트·오류 슬롯만 새로 얹는다(§0-2 원문). */
.form-intro {
  margin: var(--space-2) 0 0;
  font-size: var(--text-body-size);
  color: var(--color-text-secondary);
}
.form-field { margin-top: var(--space-4); }
.form-field__label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--text-caption-size);
  font-weight: var(--text-body-strong-weight);
  color: var(--color-text-primary);
}
.form-field__box {
  display: flex;
  align-items: center;
  height: 48px;
  padding-inline: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.form-field__input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  font-family: var(--pv-font-body);
  font-size: var(--text-body-size);
}
.form-field__hint {
  margin: var(--space-1) 0 0;
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
}
.form-field__error {
  margin: var(--space-1) 0 0;
  font-size: var(--text-caption-size);
  color: var(--color-error-text);
}
[aria-invalid="true"] { border-color: var(--color-error-text); }
.form-footer {
  margin-top: var(--space-4);
  font-size: var(--text-body-size);
  color: var(--color-text-secondary);
}
/* 연령 확인 체크박스 — SCREEN-004 §2 4번. <label> 은 인라인 기본이라 44px 탭 영역을
 * 주려면 display 를 명시해야 한다(위 §9 주석과 같은 이유). */
.age-confirm { margin-top: var(--space-4); border: 0; padding: 0; }
.age-confirm__label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding-block: var(--space-1);
  font-size: var(--text-body-size);
  color: var(--color-text-primary);
}
/* 폼 상단 안내 패널 변형 — .panel(§7) 그대로에 텍스트색만 준다(SCREEN-004 §0-2
 * ".panel--error 는 새로 만들지 않는다"). */
.panel--notice { color: var(--color-error-text); }

/* ── 11. 내 컬렉션 · 공개 프로필 — SCREEN-007 · SCREEN-008 (신설, TICKET-979, ADR-0050 T-9) ──
 * 새 커스텀 프로퍼티 0개 — 기존 :root 토큰만 참조한다(§1 「값은 set-tile 과 동일 토큰」). */

/* 컬렉션 타일 — SCREEN-007 §2-C. `.set-tile`(§6)과 같은 값을 쓰되 루트가 `<a>`다
 * (히트카드처럼 자기 링크를 갖는 하위 요소가 없어 `<article>` 일 이유가 없다). */
.collection-list {
  margin-top: var(--space-4);
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-3);
}
.collection-tile {
  display: block;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  text-decoration: none;
}
.collection-tile__name {
  display: block;
  font-size: var(--text-title-size);
  line-height: var(--text-title-line);
  font-weight: var(--text-title-weight);
  color: var(--color-link);
  word-break: keep-all;
  overflow-wrap: break-word;
}
.collection-tile__meta {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-caption-size);
  line-height: var(--text-caption-line);
  color: var(--color-text-secondary);
}

/* 공개 프로필 가입월 캡션 — SCREEN-008 §2-A 2번 */
.profile-meta {
  margin-top: var(--space-1);
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
}

/* 등록 건 목록 — SCREEN-007 §2-D · SCREEN-008 §2-B. `card_tile` 매크로(카드 부분)를
 * `.entry-row__card`(고정 상한 167.5px — `.popular-card-grid`(§5)가 이미 쓰는
 * "모바일 2열 그리드 카드 한 장 폭"과 같은 값, 새 치수를 만들지 않는다)로 감싸고
 * 그 아래에 수량·그레이딩·메모·조작 행을 세로로 쌓는다.
 * **옛 설계는 이 컨테이너 없이 `.card-tile` 을 `.entry-row` 에 바로 뒀다** — 폭
 * 제약이 없어 `.card-tile__thumb` 의 width:100% 가 행 전체(페이지 본문 폭) 그대로
 * 퍼져 카드 이미지가 "포스터"처럼 렌더됐다(TICKET-982 결함, 사용자 발견). */
.entry-list { margin-top: var(--space-4); padding: 0; list-style: none; }
.entry-row {
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.entry-row__card { max-width: 167.5px; }
.entry-row__meta {
  margin-top: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  font-size: var(--text-caption-size);
  color: var(--color-text-secondary);
}
.entry-row__note {
  margin-top: var(--space-1);
  font-size: var(--text-caption-size);
  color: var(--color-text-tertiary);
}
.entry-row__actions {
  margin-top: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* 카드 상세 등록 CTA — SCREEN-007 §2-A · §2-A-1 (`ADR-0050` T-9, `TICKET-979`) */
.card-detail__collect { margin-top: var(--space-6); }
.collect-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-start;
}
.collect-actions__btn { min-height: 44px; }
.collect-actions__advanced { flex-basis: 100%; }
.collect-actions__advanced summary {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  color: var(--color-link);
}
.collect-form { margin-top: var(--space-3); border: 0; padding: 0; }
.collect-form legend {
  padding: 0;
  font-size: var(--text-caption-size);
  font-weight: var(--text-body-strong-weight);
  color: var(--color-text-primary);
}
/* 등록 수량 스테퍼 — SCREEN-007 §2-A-1. `role="group"` 로 [-]·값·[+] 를 묶는다 */
.collect-actions__stepper {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.collect-actions__stepper button {
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
}
.collect-actions__stepper button:disabled { color: var(--color-text-tertiary); cursor: not-allowed; }

/* `molecules.form_field()` 의 `type: "textarea"` 분기(신설, `TICKET-979`) — `.form-field__box`
 * 는 §10 원래 정의가 48px 고정 높이라 여러 줄 입력엔 안 맞는다. 이 수식자만 높이를 푼다 —
 * 나머지(테두리·배경·라운드)는 `.form-field__box` 그대로 물려받는다. */
.form-field__box--textarea { height: auto; align-items: stretch; padding-block: var(--space-2); }
.form-field__box--textarea .form-field__input { resize: vertical; min-height: 64px; }

/* 공개 전환 토글 — SCREEN-007 §2-D · §2-F. `<label>` 로 체크박스+텍스트를 묶어
 * 클릭 가능 영역 자체가 44px 이상이 되게 한다(§4 접근성). */
.visibility-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
}

/* 탈퇴 섹션 — SCREEN-007 §2-F. `<details>` 로 기본 접힘, 확정 버튼만 위험색이다. */
.withdraw-section { margin-top: var(--space-6); border-top: 1px solid var(--color-border); padding-top: var(--space-4); }
.withdraw-section summary { cursor: pointer; color: var(--color-error-text); min-height: 44px; display: flex; align-items: center; }
/* 위험 강조 확정 버튼 — `.btn-primary`(§8) 그대로에 텍스트/보더 색만 에러 토큰으로
 * 바꾼다(SCREEN-007 §2-D 5번 원문 "배경·크기는 그대로, 텍스트/보더 색만"). */
.btn-primary--danger {
  color: var(--color-error-text);
  border: 1px solid var(--color-error-text);
}
