/**
 * Frame v1 — site-definition §6 tokens + grammar styles (slice 1).
 *
 * Spec: auction-community/docs/specs/site-definition.md §6 (6.1 notes zone,
 * 6.2 structure grammar, 6.3 time windows, 6.4 land figure, 6.5 legibility).
 *
 * ── SCOPE ROOT ────────────────────────────────────────────────────────────
 * Every token below is defined under `[data-frame="v1"]`, never on `:root`.
 * Legacy pages (ui-v2, dossier) therefore stay pixel-identical: they never see
 * these values. Slice 1 ships the frame as an opt-in island, reviewed at
 * /dev/frame behind NEXT_PUBLIC_FRAME_V1_ENABLED.
 *
 * ── WHY A STATIC ASSET AND NOT AN IMPORTED STYLESHEET ─────────────────────
 * Same reason `public/dossier-v1.css` is one (see globals.css:18-21): a
 * bundled stylesheet cannot be scoped to a runtime flag branch — Turbopack
 * hoists a dynamically-imported module's CSS into the route entry, so every
 * flag-OFF visitor would still download it. Serving it statically and linking
 * it from the rendered tree (React hoists + dedupes the tag) makes the
 * coupling exact. It also keeps the components importable from the
 * `tsx`-based render-contract tests, which cannot resolve a `.css` import.
 *
 * ── ACCESSIBILITY BASELINE (WCAG 2.2 AA intent — spec §6.5) ───────────────
 * Checklist this sheet is built to; the render contract in
 * `scripts/test-frame-v1.mts` pins the markup half of each line.
 *   [1] Semantic table headers — NumTable emits <caption>, <th scope="col">
 *       and <th scope="row">; no layout tables, no header-by-styling.
 *   [2] Folds keyboard-operable — Fold is native <details>/<summary>, so
 *       Enter/Space work with no client JS and no ARIA to keep in sync.
 *   [3] Steps keyboard-reachable — every step carries a focusable hit target
 *       so a long 기일 dot row is traversable by keyboard, not mouse-only.
 *   [4] Visible focus, keyboard-only — :focus-visible draws a 2px accent ring
 *       with a 2px offset (≥ 3:1 against paper); :focus without :focus-visible
 *       draws nothing, so pointer users get no ring.
 *   [5] Status never by colour alone — the red rights flag carries a ⚠ glyph
 *       and its own words; Steps carry filled/hollow SHAPE plus a dated text
 *       label; no state is conveyed by hue only.
 *   [6] Targets ≥ 44px — every interactive part uses
 *       `min-height: max(44px, var(--row-h))`, so the compact density mode
 *       (36px rows) cannot shrink a target below the floor.
 *   [7] Text size floors — body 16px (mobile) / 18px (PC), secondary ≥ 14px,
 *       captions and source tags ≥ 12px. Nothing in the frame is smaller.
 *   [8] Contrast — every text token is measured against paper below; the one
 *       token that fails (ink-4) is deliberately absent from this sheet.
 *   [9] Motion/zoom — no fixed pixel heights on text containers; all blocks
 *       are flow-sized so 200% zoom and reflow do not clip.
 *
 * ── MEASURED CONTRAST ON PAPER #f7f6f3 (WCAG 2.x relative-luminance) ──────
 * Computed 2026-08-22 with the standard formula (sanity-checked: #000 on #fff
 * = 21.00:1). Frame text tokens and their ratios:
 *   ink        #14130f  17.20:1  ✓ body text
 *   ink-2      #44423c   9.30:1  ✓ SECONDARY TEXT TOKEN (the frame's grey)
 *   accent     #2b4a7a   8.22:1  ✓ links / selection / primary action
 *   red        #b3261e   6.05:1  ✓ rights red-flag ONLY
 * Rejected, on purpose:
 *   ink-3      #74716a   4.51:1  — passes 4.5:1 by 0.006. A rule with no
 *                                  margin is not a rule; any future paper
 *                                  tweak silently breaks AA. Not used here.
 *   ink-4      #9e9b92   2.57:1  — FAILS. Never carries text in the frame.
 * NOTE for the head (spec §6.5 says "grey ≥ 4.5:1 and ≥ 14px; today's failure
 * is size, not ratio"): spec §2 records `#737373 on #f7f6f3 = 4.87:1`. That
 * does not reproduce — the same formula gives **4.39:1** for that pair, i.e.
 * a FAIL. The size finding stands either way; the ratio claim needs a re-run
 * of `F-visibility-measure/measure.mjs`. Reported, spec not edited (slice 1 is
 * read-only on specs).
 *
 * ── PROMOTION PATH ────────────────────────────────────────────────────────
 * When the frame becomes the site default (after slices 2-5 recut the pages),
 * promotion is the ONE selector marked `PROMOTE-TO-ROOT` below: change it to
 * `:root, [data-frame="v1"]` and drop the global 14px body rule in
 * globals.css. Nothing else in this file moves.
 */

/* PROMOTE-TO-ROOT — this selector, and only this one, becomes `:root, [data-frame="v1"]`. */
[data-frame="v1"] {
  /* ── Type scale (spec §6.5) ────────────────────────────────────────────
     Mobile values. The PC step lives in the @container block below; it is a
     token override, never a second set of rules, so the two cannot drift. */
  --frame-fs-body: 16px;
  --frame-lh-body: 1.6;          /* ≥ 1.5 required */
  --frame-fs-secondary: 14px;    /* floor — secondary grey text */
  --frame-fs-caption: 12px;      /* floor — captions, source tags */
  --frame-fs-title: 20px;
  --frame-fs-figure: 28px;       /* the ONE emphasised-number size */
  --frame-fw-figure: 800;        /* the ONE emphasised-number weight */

  /* ── Colour (spec §6.5): paper + ink + ONE accent + ONE red. No green. ── */
  --frame-paper: #f7f6f3;
  --frame-ink: #14130f;
  --frame-ink-secondary: #44423c;
  --frame-rule: #e3e0d7;         /* hairlines/borders — non-text, decorative */
  --frame-sunken: #efede7;       /* tag + fold body fill; grey/ink family only */
  --frame-accent: #2b4a7a;       /* links, selection, primary action, focus */
  --frame-red: #b3261e;          /* rights red-flag ONLY — see .frame-tag */

  /* ── Spacing. --gap/--row-h come from the existing [data-density] rows in
     globals.css; the fallbacks keep this sheet standalone-correct. ──────── */
  --frame-gap: var(--gap, 16px);
  --frame-gap-tight: calc(var(--frame-gap) / 2);
  --frame-row-h: var(--row-h, 48px);
  --frame-target: max(44px, var(--frame-row-h));  /* touch/click target floor */
  --frame-radius: 6px;

  color: var(--frame-ink);
  background: var(--frame-paper);
  font-size: var(--frame-fs-body);
  line-height: var(--frame-lh-body);
  word-break: keep-all;
  overflow-wrap: break-word;
  accent-color: var(--frame-accent);
}

[data-frame="v1"] ::selection {
  background: var(--frame-accent);
  color: var(--frame-paper);
}

/**
 * PC step. The threshold is a CONTAINER width, not a viewport width: the frame
 * root is the container, so a frame rendered inside a 390px box reports mobile
 * type even on a 1280px screen. That is what makes /dev/frame show both
 * layouts truthfully on one URL, and it is why FrameRoot renders a shell
 * element around the token element (a container cannot query itself).
 *
 * 1024px: the repo has no canonical PC breakpoint token. The most-used raw
 * value is 768px, but every one of those 11 uses is inside
 * `src/components/ui-v2/styles/`, which is reference-only for this slice
 * (decision (c), user word 2026-08-22). 1024px per the brief; it satisfies
 * acceptance ① (16px at 390, 18px at 1280) with room on both sides.
 */
@container frame (min-width: 1024px) {
  [data-frame="v1"] {
    --frame-fs-body: 18px;
    --frame-fs-secondary: 15px;
    --frame-fs-caption: 13px;
    --frame-fs-title: 24px;
  }
}

.frame-shell {
  container: frame / inline-size;
}

/* ── Shared primitives ──────────────────────────────────────────────────── */

[data-frame="v1"] :focus-visible {
  outline: 2px solid var(--frame-accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Keyboard-only: a pointer click must not leave a ring behind. */
[data-frame="v1"] :focus:not(:focus-visible) {
  outline: none;
}

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

.frame-section {
  display: flex;
  flex-direction: column;
  gap: var(--frame-gap-tight);
  margin: 0 0 var(--frame-gap);
}

.frame-section__title {
  margin: 0;
  font-size: var(--frame-fs-title);
  font-weight: 700;
  line-height: 1.35;
}

/* The ONE emphasised-number style (spec §6.5: one per 667px screen). */
.frame-figure {
  font-size: var(--frame-fs-figure);
  font-weight: var(--frame-fw-figure);
  line-height: 1.2;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--frame-ink);
}

.frame-figure__label {
  display: block;
  font-size: var(--frame-fs-secondary);
  font-weight: 400;
  color: var(--frame-ink-secondary);
}

/* A state line is never an alarm: it is grey text stating what is missing. */
.frame-state {
  font-size: var(--frame-fs-secondary);
  color: var(--frame-ink-secondary);
}

/* ── SourceTag (spec §6.1: "국토부 · 08.21" next to a number) ───────────── */

.frame-sourcetag {
  display: inline-block;
  font-size: var(--frame-fs-caption);
  line-height: 1.4;
  color: var(--frame-ink-secondary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── Tag (spec §6.2 item 3): grey/ink, plus red for the rights flag ONLY ── */

.frame-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--frame-radius);
  border: 1px solid var(--frame-rule);
  background: var(--frame-sunken);
  color: var(--frame-ink-secondary);
  font-size: var(--frame-fs-caption);
  line-height: 1.5;
  white-space: nowrap;
}

/* Colour is the SECOND signal here; the ⚠ glyph and the tag's own words are
   the first. Removing the colour must not remove the meaning (checklist [5]). */
.frame-tag[data-tag="red"] {
  border-color: var(--frame-red);
  background: var(--frame-paper);
  color: var(--frame-red);
  font-weight: 600;
}

.frame-tag__flag {
  font-size: 1em;
}

/* ── KvList (spec §6.2 item 1): label column | value ────────────────────── */

.frame-kv-block {
  display: block;
  min-width: 0;
}

.frame-kv {
  display: grid;
  grid-template-columns: minmax(5.5rem, max-content) 1fr;
  gap: 0 var(--frame-gap);
  margin: 0;
  font-size: var(--frame-fs-body);
}

.frame-kv__label {
  padding: var(--frame-gap-tight) 0;
  color: var(--frame-ink-secondary);
  font-size: var(--frame-fs-secondary);
  border-top: 1px solid var(--frame-rule);
}

.frame-kv__value {
  padding: var(--frame-gap-tight) 0;
  margin: 0;
  color: var(--frame-ink);
  border-top: 1px solid var(--frame-rule);
  min-width: 0;
}

.frame-kv__row:first-child > .frame-kv__label,
.frame-kv__row:first-child > .frame-kv__value {
  border-top: 0;
}

/* `display: contents` keeps the dt/dd pair on the parent grid while the
   wrapper preserves the <dl> row grouping the a11y tree wants. */
.frame-kv__row {
  display: contents;
}

.frame-kv__tag {
  margin-left: 6px;
}

.frame-kv__caption {
  font-size: var(--frame-fs-secondary);
  font-weight: 600;
  color: var(--frame-ink-secondary);
  margin: 0 0 var(--frame-gap-tight);
  text-align: left;
}

/* F-UX-063 (c) — ONE caption under a row's label (`24개월 기준`). A block inside
   the `<dt>`, so it sits under the label word in the label column and costs a
   price-bearing row no height at all; it keeps the label's own grey and size.
   4px was measured (round 7): it is the only gap that lands the caption within
   1px of the value's tail line at BOTH viewports while costing nothing — 0/2/3px
   still read as a wrapped label, 6px costs 2px at 390. */
.frame-kv__label-note {
  display: block;
  margin-top: 4px;
}

.frame-kv__ordinal {
  display: inline-block;
  margin-right: 4px;
  color: var(--frame-ink-secondary);
}

/* ── KvList two-value-column mode (slice 2, Q4): label | col A | col B ──────
   Same swap technique as NumTable: the wide table below the PC step is
   replaced by one fold per row, both generated from the same arrays. */

.frame-kv-grid {
  display: block;
  min-width: 0;
}

.frame-kv-grid__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--frame-fs-body);
  table-layout: fixed;
}

.frame-kv-grid__table th,
.frame-kv-grid__table td {
  padding: var(--frame-gap-tight) 6px;
  border-bottom: 1px solid var(--frame-rule);
  vertical-align: top;
  text-align: left;
}

.frame-kv-grid__table thead th {
  font-size: var(--frame-fs-secondary);
  font-weight: 600;
  color: var(--frame-ink-secondary);
}

.frame-kv-grid__corner {
  width: 8.5rem;
}

.frame-kv-grid__rowhead {
  font-size: var(--frame-fs-secondary);
  font-weight: 500;
  color: var(--frame-ink-secondary);
}

.frame-kv-grid__cell {
  color: var(--frame-ink);
}

/* An undrawn cell carries no text and no marker — the whole point of the
   degraded-cell contract is that nothing is asserted about it. */
.frame-kv-grid__cell.is-omitted {
  background: var(--frame-sunken);
}

.frame-kv-grid__cell > .frame-kv-cell-line {
  display: block;
}

.frame-kv-grid__fold-state {
  margin: 0;
}

/* The compact 390 rendition: same three columns, one word per cell, with the
   court's sentences behind a single disclosure under it. */
.frame-kv-grid__short {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--frame-fs-body);
  table-layout: fixed;
}

.frame-kv-grid__short th,
.frame-kv-grid__short td {
  padding: var(--frame-gap-tight) 4px;
  border-bottom: 1px solid var(--frame-rule);
  vertical-align: baseline;
  text-align: left;
}

.frame-kv-grid__short thead th {
  font-size: var(--frame-fs-caption);
  font-weight: 600;
  color: var(--frame-ink-secondary);
  white-space: nowrap;
}

.frame-kv-grid__short .frame-kv-grid__rowhead {
  font-size: var(--frame-fs-secondary);
}

.frame-kv-grid__detail-row {
  padding-bottom: var(--frame-gap-tight);
}

.frame-kv-grid > .frame-kv-grid__table {
  display: none;
}

@container frame (min-width: 1024px) {
  .frame-kv-grid > .frame-kv-grid__table {
    display: table;
  }

  .frame-kv-grid > .frame-kv-grid__compact {
    display: none;
  }
}

/* ── Fold `openOn="pc"` (slice 2, Q1) ──────────────────────────────────────
   Two renditions of one `children`: the collapsed <details> below the PC step,
   the expanded section at and above it. Never both at once. */

.frame-fold-pair > [data-fold-at="pc"] {
  display: none;
}

.frame-fold-open {
  border-top: 1px solid var(--frame-rule);
  padding-top: var(--frame-gap-tight);
}

.frame-fold-open__title {
  margin: 0;
  font-size: var(--frame-fs-secondary);
  font-weight: 600;
  color: var(--frame-ink-secondary);
}

@container frame (min-width: 1024px) {
  .frame-fold-pair > [data-fold-at="mobile"] {
    display: none;
  }

  .frame-fold-pair > [data-fold-at="pc"] {
    display: block;
  }
}

/* ── NumTable (spec §6.2 item 2): right-aligned numbers, window in header ─ */

.frame-numtable {
  display: block;
  margin: 0;
}

/* No `overflow-x: auto` anywhere in this sheet, on purpose (acceptance ⑥):
   a scroller would HIDE the overflow instead of fixing it. Tables that cannot
   fit declare `fallback="kv"` and swap to folds below the PC threshold. */
.frame-numtable__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--frame-fs-body);
  font-variant-numeric: tabular-nums;
}

.frame-numtable__caption {
  caption-side: top;
  text-align: left;
  padding: 0 0 var(--frame-gap-tight);
  font-size: var(--frame-fs-secondary);
  font-weight: 600;
  color: var(--frame-ink);
}

.frame-numtable__window {
  font-weight: 400;
  color: var(--frame-ink-secondary);
}

.frame-numtable__table th,
.frame-numtable__table td {
  padding: var(--frame-gap-tight) 6px;
  border-bottom: 1px solid var(--frame-rule);
  vertical-align: baseline;
}

.frame-numtable__table thead th {
  font-size: var(--frame-fs-secondary);
  font-weight: 600;
  color: var(--frame-ink-secondary);
  border-bottom: 1px solid var(--frame-rule);
  text-align: right;
  white-space: nowrap;
}

.frame-numtable__table thead th:first-child,
.frame-numtable__rowhead {
  text-align: left;
}

.frame-numtable__rowhead {
  font-weight: 500;
  color: var(--frame-ink);
  white-space: nowrap;
}

/* Numeric cells are ink only — never accent, never red. The component makes
   that structural (cells are typed `string`, so no node can be injected). */
.frame-numtable__cell {
  text-align: right;
  color: var(--frame-ink);
  white-space: nowrap;
}

.frame-numtable__source {
  text-align: left;
  white-space: nowrap;
}

.frame-numtable__folds {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.frame-numtable__fold-state {
  margin: 0;
}

/* The swap. Both renditions are generated from the same rows/columns arrays,
   so the narrow view cannot drift from the wide one. */
.frame-numtable[data-fallback="kv"] > .frame-numtable__table {
  display: none;
}

@container frame (min-width: 1024px) {
  .frame-numtable[data-fallback="kv"] > .frame-numtable__table {
    display: table;
  }

  .frame-numtable[data-fallback="kv"] > .frame-numtable__folds {
    display: none;
  }
}

/* ── Steps (spec §6.2 item 4): dated 기일 dots on a line ─────────────────── */

.frame-steps-block {
  display: block;
  min-width: 0;
}

.frame-steps-block > p {
  margin: 0 0 var(--frame-gap-tight);
}

.frame-steps {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 0;
  padding: 0;
}

.frame-step {
  position: relative;
  flex: 1 1 5.5rem;
  min-width: 5.5rem;
  padding-top: 18px;
}

/* The connecting line. Decorative only — the dates carry the sequence. */
.frame-step::before {
  content: "";
  position: absolute;
  top: 6px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--frame-rule);
}

.frame-step:first-child::before {
  left: 50%;
}

.frame-step:last-child::before {
  right: 50%;
}

.frame-step__dot {
  position: absolute;
  top: 0;
  left: calc(50% - 6px);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--frame-ink-secondary);
  background: var(--frame-paper);
  box-sizing: border-box;
}

/* SHAPE carries the state (checklist [5]): filled = 지나간 기일, hollow =
   아직 오지 않은 기일. The text label below repeats it in words. */
.frame-step[data-status="past"] .frame-step__dot {
  background: var(--frame-ink-secondary);
}

.frame-step[data-status="next"] .frame-step__dot {
  border-width: 3px;
  border-color: var(--frame-ink);
  width: 14px;
  height: 14px;
  left: calc(50% - 7px);
  top: -1px;
}

.frame-step__hit {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--frame-target);
  padding: 4px 2px;
  text-align: center;
}

.frame-step__date {
  font-size: var(--frame-fs-caption);
  color: var(--frame-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.frame-step__label {
  font-size: var(--frame-fs-caption);
  color: var(--frame-ink-secondary);
}

.frame-step[data-status="next"] .frame-step__label {
  color: var(--frame-ink);
  font-weight: 700;
}

/* R7-17: the round's 최저가, on the row it belongs to. In the base rail the
   step is a centered column, so a long amount simply takes its own line under
   the date rather than widening the track — the ruled wrap. */
.frame-step__min {
  font-size: var(--frame-fs-caption);
  color: var(--frame-ink);
  font-variant-numeric: tabular-nums;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The 감정가 head, printed ONLY when no row states a 최저가 (steps.ts). */
.frame-steps__head-note {
  margin-left: 8px;
  font-variant-numeric: tabular-nums;
}

/* R7-17 fix r4 — the three sentences the rail prints when it has no 기일 to
   draw (수집 중 / 등록된 정보 없음 / 일시적으로 확인 불가). Same rhythm as the
   head note above, in the secondary reading register: it is a STATE, not a
   round, and it must not read with a row's weight. */
.frame-steps__empty {
  margin: 0;
  font-size: var(--frame-fs-caption);
  color: var(--frame-ink-secondary);
}

/* ── Fold (spec §6.2 item 5): one-line summary, collapsed by default ─────── */

.frame-fold {
  border-top: 1px solid var(--frame-rule);
}

.frame-fold > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--frame-gap-tight);
  min-height: var(--frame-target);
  padding: var(--frame-gap-tight) 2px;
  cursor: pointer;
  list-style: none;
  font-size: var(--frame-fs-body);
  /* Ink, not accent: a fold handle can sit beside a number, and the frame
     never puts blue on or next to a numeric cell (spec §6.5). The marker and
     the focus ring carry the affordance instead. */
  color: var(--frame-ink);
}

.frame-fold > summary::-webkit-details-marker {
  display: none;
}

.frame-fold > summary:hover .frame-fold__summary-text {
  text-decoration: underline;
}

.frame-fold__summary-text {
  min-width: 0;
}

.frame-fold__marker {
  flex: none;
  color: var(--frame-accent);
  font-size: var(--frame-fs-caption);
  transition: none;
}

.frame-fold__marker::after {
  content: "▾";
}

.frame-fold[open] > summary .frame-fold__marker::after {
  content: "▴";
}

.frame-fold__body {
  padding: var(--frame-gap-tight) 2px var(--frame-gap);
}

/* ── NotesZone (spec §6.1): the ONE 출처·한계 block, page bottom ─────────── */

.frame-notes {
  margin-top: calc(var(--frame-gap) * 2);
  padding-top: var(--frame-gap);
  border-top: 2px solid var(--frame-rule);
  display: flex;
  flex-direction: column;
  gap: var(--frame-gap-tight);
  font-size: var(--frame-fs-secondary);
  color: var(--frame-ink-secondary);
}

.frame-notes__title {
  margin: 0;
  font-size: var(--frame-fs-secondary);
  font-weight: 700;
  color: var(--frame-ink);
}

.frame-notes__sources {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.frame-notes__source {
  display: flex;
  flex-wrap: wrap;
  gap: 0 6px;
}

.frame-notes__source-name {
  color: var(--frame-ink);
}

.frame-notes__source-asof {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.frame-notes__list {
  margin: 0;
  padding-left: 1.1em;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.frame-notes__disclaimer {
  margin: 0;
  padding-top: var(--frame-gap-tight);
  border-top: 1px solid var(--frame-rule);
  font-size: var(--frame-fs-caption);
  line-height: 1.6;
}

.frame-notes__unknowns-title {
  margin: var(--frame-gap-tight) 0 0;
  font-size: var(--frame-fs-secondary);
  font-weight: 700;
  color: var(--frame-ink);
}
