/* G2B 투찰 도우미 — the "계기 / Instrument" UI, 2026-08 renewal.
 *
 * Source of truth: docs/renewal-2026-08/10-design-system.md ("흰 바탕 위의 유리
 * 계기"), which extends docs/design/ui-2026-08.md rather than replacing it.
 * Where those two disagree the renewal document wins; where a document and
 * this file disagree the file is the fact, and the disagreement is reported
 * rather than coded around. Section references below (spec §n) point at the
 * renewal document.
 *
 * Three rules carry the whole design:
 *
 *   1) Words and numbers use different typefaces. Prose is Pretendard, every
 *      figure is IBM Plex Mono tabular (.num). That duality IS the identity.
 *
 *   2) The signal color (--signal) marks the recommended bid and the rail
 *      marker, nothing else. Exactly one element carries a drop shadow
 *      (.reco-hero); every other box-shadow in this file is an inset line.
 *
 *   3) Glass is chrome, light is background, data is opaque. Translucency
 *      exists in exactly three places — the sticky topbar, the landing hero
 *      frame, the login card — and never under an amount, a table, a chart, a
 *      badge or the legal notice. On this product that boundary is not taste:
 *      --signal on the ambient layer is 3.76:1, on an opaque surface 5.02:1
 *      (spec §1.4), and the number it colours is worth hundreds of millions.
 *
 * All assets are self-hosted (/static/fonts.css). No CDN, no external URL, no
 * build step, no framework.
 *
 * Map: 1 Tokens · 2 Base · 3 Numerals · 4 Glass · 5 Skeleton · 6 Surfaces ·
 *      7 Data · 8 Recommendation hero · 9 Price rail · 10 Forms · 11 Landing ·
 *      12 Responsive · 13 Motion · 14 Fallbacks (must stay last — see §14).
 */

/* ========================================================================= *
 * 1. Tokens — the only thing that differs between light and dark (spec §2)
 * ========================================================================= */

:root {
  /* --- Ink and paper --------------------------------------------------- */
  --ink: #0F172A;            /* body text, headings */
  --ink-2: #475569;          /* secondary text */
  --ink-3: #5C6B80;          /* captions, placeholders (>= 4.85:1 everywhere) */
  --paper: #F6F7F9;          /* page background (app screens) */
  --surface: #FFFFFF;        /* panel surface; also the landing ground */
  --line: #E2E8F0;           /* decorative hairline */
  --line-strong: #CBD5E1;    /* table rules, chart axis — decorative */
  --line-control: #7C8AA0;   /* borders of inputs/selects/textareas/.btn */

  /* --- Action ----------------------------------------------------------- */
  --accent: #1E40AF;
  --accent-ink: #FFFFFF;
  --accent-soft: #EEF2FF;

  /* --- Signal — reserved for the recommendation ------------------------- */
  --signal: #B45309;
  --signal-soft: #FEF3C7;

  /* --- Status ----------------------------------------------------------- */
  --win: #15803D;   --win-soft: #DCFCE7;
  --loss: #B91C1C;  --loss-soft: #FEE2E2;
  --idle: #64748B;  --idle-soft: #F1F5F9;

  /* --- Glass (spec §3) — alpha 0.80 is a floor, not a taste. ------------
   * At 0.72 the topbar's --ink-2 falls to 4.01:1 once the landing's reversed
   * band scrolls under it; 0.80 holds 4.89:1 in that same worst case. */
  --glass-bg: rgba(255, 255, 255, 0.80);
  --glass-filter: saturate(1.8) blur(20px);
  --glass-border: rgba(15, 23, 42, 0.08);
  /* The landing hero frame's own edge, and nothing else's (§11). --glass-border
   * stays where it is because it also draws the topbar hairline on every tool
   * screen, and those screens are verified as they stand; this token exists so
   * the one pane that has to READ as a pane can have a real boundary without
   * dragging the app's chrome along. Measured against the ambient light just
   * outside the frame it is 3.05:1 at its weakest across 375/768/1024/1440 x
   * light/dark (3.81:1 at its strongest) — the same >= 3:1 bar §10 applies to a border
   * that identifies a component, which is exactly this border's job. Paired
   * with --glass-highlight one pixel inside it, the two lines read as the lit
   * upper face of a pane rather than as a stroked box. */
  --glass-edge: rgba(15, 23, 42, 0.50);
  --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.90);
  --glass-backdrop-h: 200%;                       /* sticky-topbar mask trick */
  --glass-mask: linear-gradient(to bottom,
                  #000 0%, #000 50%, transparent 50%, transparent 100%);

  /* --- Ambient light layer (spec §4) ------------------------------------
   * Colours are tokens; the geometry lives in the .lp-hero rule (§11). It
   * cannot live here: a custom property is substituted on the element that
   * DECLARES it, so an --amb-*-x override on .lp-hero would never reach a
   * gradient string baked at :root. Explicit zero-alpha stops (not the
   * keyword `transparent`) keep the falloff from hazing towards grey. */
  --amb-plan-x: 70%;    /* predicted-price band centre, in .lp-hero coords */
  --amb-floor-x: 19%;   /* award-floor band centre / recommendation marker */
  /* Alphas raised 2026-08-13 from 0.08 / 0.06 / 0.05, which composited to
   * something a measurement could find and an eye could not. The reason is
   * geometric, not chromatic, and it is worth writing down because the numbers
   * alone do not show it: BOTH blob centres sit behind the glass frame, and
   * inside that frame the opaque .reco-hero card and the opaque .rail plate
   * cover them completely. So the layer's peak is painted where nothing can
   * see it, and every pixel a reader actually sees is falloff. Measured on the
   * old values at 1440: the strongest visible ambient pixel anywhere on the
   * page was 7/255 off white, and most of the hero was 2-3/255 — i.e. white.
   *
   * The centres are not free to move: aligning them with the rail's band
   * centres is the whole signature (§4.3 of the spec). What the falloff can do
   * is reach further, so the radii below grew with the alphas.
   *
   * The ceiling is contrast, and it is what fixes these three numbers. At the
   * worst three-blob overlap the layer composites to #C9CBDE, where --ink-2 is
   * 4.72:1 (--ink 11.12:1, --accent 5.43:1) — so body text still clears AA
   * anywhere on this layer, not merely where the copy happens to sit today.
   * One step higher and --ink-2 drops under 4.5:1. --signal (3.13:1) and
   * --ink-3 (3.38:1) were already barred from this layer and stay barred. */
  --amb-cool-a: rgba(30, 64, 175, 0.13);
  --amb-cool-b: rgba(30, 64, 175, 0.10);
  --amb-cool-0: rgba(30, 64, 175, 0);
  --amb-warm:   rgba(180, 83, 9, 0.08);
  --amb-warm-0: rgba(180, 83, 9, 0);

  /* --- Spacing (4px rhythm) --------------------------------------------- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;

  /* --- Radii ------------------------------------------------------------ */
  --r1: 4px; --r2: 8px; --r3: 12px;
  --r4: 20px;                /* glass frames only — large glass reads thicker */

  /* --- Layers ----------------------------------------------------------- */
  --z-nav: 100;
}

/* Dark mode redefines tokens; component rules stay as written. The single
 * sanctioned exception is .lp-invert in §11, where the landing's reversed
 * band must stop reversing (spec §5.7) — it is a contrast fix, not a restyle. */
@media (prefers-color-scheme: dark) {
  :root {
    --ink: #E2E8F0;
    --ink-2: #CBD5E1;
    --ink-3: #94A3B8;
    --paper: #0B1220;
    --surface: #131C2B;
    --line: #233047;
    --line-strong: #33415B;
    --line-control: #64748B;

    --accent: #60A5FA;
    --accent-ink: #0B1220;
    --accent-soft: #1C2C4C;

    --signal: #F59E0B;
    --signal-soft: #3B2A08;

    --win: #4ADE80;   --win-soft: #0F2E1C;
    --loss: #F87171;  --loss-soft: #3B1717;
    --idle: #94A3B8;  --idle-soft: #1E293B;

    --glass-bg: rgba(19, 28, 43, 0.80);
    --glass-border: rgba(226, 232, 240, 0.12);
    --glass-edge: rgba(226, 232, 240, 0.48);
    --glass-highlight: inset 0 1px 0 rgba(226, 232, 240, 0.10);

    /* Same geometry, raised alpha: the same value reads weaker on a dark ground.
     * Worst three-blob overlap at these values is #3C5169, where --ink-2 is
     * 5.53:1 and --ink 6.65:1. --accent does NOT clear 4.5:1 there (3.23:1) —
     * it does in light (5.43:1), so the rule has to come from the dark case.
     * That is why spec §4.5 now bars a link from the ambient layer in BOTH
     * modes rather than barring only --signal. */
    --amb-cool-a: rgba(96, 165, 250, 0.19);
    --amb-cool-b: rgba(96, 165, 250, 0.15);
    --amb-cool-0: rgba(96, 165, 250, 0);
    --amb-warm:   rgba(245, 158, 11, 0.11);
    --amb-warm-0: rgba(245, 158, 11, 0);
  }
}

/* ========================================================================= *
 * 2. Base layer
 * ========================================================================= */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Let form controls and scrollbars follow the mode too. */
  color-scheme: light dark;
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: 'Pretendard Variable', system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  word-break: keep-all;   /* wrap Korean on word boundaries, not mid-word */
}

/* Type scale: 12 / 13 / 14 / 16 / 20 / 26 / 34 / 44 */
h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--ink);
}

h1 { font-size: 26px; }
h2 { font-size: 20px; }
h3 { font-size: 16px; font-weight: 600; }
h4 { font-size: 14px; font-weight: 600; color: var(--ink-2); }

p {
  margin: 0 0 var(--s3);
}

p:last-child {
  margin-bottom: 0;
}

small {
  font-size: 13px;
}

a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

a:hover {
  text-decoration-thickness: 2px;
}

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

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--s5) 0;
}

/* Focus ring — always 2px and always visible for keyboard users. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r1);
}

/* The `hidden` attribute has to outrank a component's own display rule.
 *
 * A bare `[hidden]` selector is (0,1,0) — exactly the weight of `.field` in
 * §10 — so `<div class="field" id="lookupDateField" hidden>` on /reco fell to
 * source order and computed to `display: flex`: the 날짜 재입력 row was on
 * screen before any lookup had run. The `:not()` raises this to (0,2,0) so it
 * beats any single-class rule, `!important` settles it against a compound one,
 * and both together make the fix general instead of a patch for one id. The
 * exclusion keeps the platform's `hidden="until-found"` behaviour intact
 * (find-in-page must still be able to reveal such a section).
 *
 * Everything that toggles visibility in this app does it through the IDL
 * attribute (`el.hidden = true/false`), which adds and removes the attribute,
 * so nothing here has to fight the !important. */
[hidden]:not([hidden="until-found"]) {
  display: none !important;
}

/* Text for screen readers only: the long description of a chart, the name of
 * a control-only table column, and the words a badge conveys with colour.
 * NOT for section headings — every landing section prints its own (§11).
 * Absolutely positioned, so it never takes a grid or flex slot. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ========================================================================= *
 * 3. Numerals — the identity of this design
 *    Every amount, rate and count in the app goes through .num. The
 *    components below opt into the same stack so a figure still reads as a
 *    figure if markup forgets the class. IBM Plex Mono is a Latin-only
 *    subset, so adjacent Korean (원, 건, …) falls back to Pretendard.
 * ========================================================================= */

.num,
.mono,
.metric-value,
.reco-hero-figure,
.rail-tick > i,
.rail-marker > i,
.lp-metric-figure {
  font-family: 'IBM Plex Mono', 'Pretendard Variable', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: 0;
}

/* ========================================================================= *
 * 4. Glass — the chrome layer (spec §3)
 *    Glass is attached in exactly two places in this file: the rule below,
 *    and the .topbar pseudo-elements in §5. Never fold it into .panel,
 *    .reco-hero, .lp-step or any other shared surface — one login card would
 *    drag every data panel in the app into a translucent box, which is how
 *    glass spreads (spec §1.5).
 * ========================================================================= */

/* The material. Four things make it, and only these four:
 *   1) an 0.80 fill — the floor that keeps --ink-2 at 4.89:1 when the
 *      landing's reversed band scrolls under the bar (spec §2.5);
 *   2) saturate(1.8) blur(20px) — apple.com's own measured values;
 *   3) a 1px hairline — glass announces itself at its edge, not by its blur;
 *   4) an inset top highlight — the light line on the pane's upper face.
 * There is no drop shadow: .reco-hero owns the only one in this file, so the
 * heaviest-looking element on screen stays the recommended amount (spec §3.2).
 *
 * .auth .panel rides in this selector list instead of taking a .glass class
 * in the markup. It matches the login card and nothing else, and at 0,2,0 it
 * outranks .panel's 0,1,0 regardless of source order, so the glass cannot
 * leak to another panel (spec §5.6). The landing hero frame is the single
 * place .glass is written in HTML: <div class="lp-hero-frame glass"> (§4.4).
 * The login card is also the one screen where text sits on glass — its
 * backdrop is a still --paper field with no ambient layer, so the 7.47:1
 * label contrast actually holds (spec §2.6). */
.glass,
.auth .panel {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);   /* Safari 9-17 has no unprefixed support */
  border: 1px solid var(--glass-border);
  border-radius: var(--r4);
  box-shadow: var(--glass-highlight);             /* inset highlight ONLY, no drop shadow */
}

/* ========================================================================= *
 * 5. Skeleton — top bar, content wrapper, page head
 * ========================================================================= */

.shell {
  max-width: 1120px;
  margin: 0 auto;
  padding: var(--s6) var(--s4) var(--s8);
}

/* The bar itself paints nothing: the glass is on ::before, the hairline on
 * ::after, and the content sits above both.
 *
 * Structural constraint, must hold in every template: .topbar stays a direct
 * child of <body>. Firefox stops running backdrop-filter on a sticky element
 * whose ancestor combines overflow with border-radius, and filter / opacity<1
 * / transform / mask / will-change / contain:paint / isolation on ANY ancestor
 * creates a backdrop root that cuts the glass off from what it refracts. */
.topbar {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: none;
  border-bottom: 0;
}

/* Glass, twice the bar's height and masked back to its top half, so the blur
 * fades out instead of ending on a knife edge along the bar's lower boundary
 * (backdrop-filter samples only what is directly behind the element).
 * If a real device shows the mask misbehaving, demote the two tokens
 * (--glass-backdrop-h: 100%, --glass-mask: none) — never this rule. The edge
 * hardens; nothing else changes. No inset highlight here: a highlight is the
 * light line on an element's top face, and while scrolling the bar's top face
 * is off-screen. */
.topbar::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--glass-backdrop-h);
  pointer-events: none;                  /* must never eat clicks on the page below */
  background: var(--glass-bg);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  -webkit-mask-image: var(--glass-mask);
  mask-image: var(--glass-mask);
}

/* Hairline. A separate pseudo because ::before is 200% tall — its own border
 * would land off-screen. ::after is generated last, so it paints above. */
.topbar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--glass-border);
  pointer-events: none;
}

/* Content above the glass (position + z-index), laid out as before. */
.topbar-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s1);
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 var(--s4);
  min-height: 56px;
}

.brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-right: var(--s3);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
}

.topnav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s1);
  margin-left: auto;
}

/* Nav links stay --ink-2, never --accent: on glass in dark mode over a white
 * surface --accent drops to 3.55:1 (spec §2.5).
 *
 * min-height alone only guaranteed one axis of the 44x44 target. The short
 * labels this bar actually carries — 검증, 근거, 추천, 통계 — are two Korean
 * glyphs at 14px, and at 375 (where §12 trims the side padding to 8px) that
 * came to 40px wide. min-width pins the other axis for any label; centring
 * keeps a short one from sitting off to the left inside it. */
.topnav a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 0 var(--s3);
  border-radius: var(--r1);
  font-size: 14px;
  color: var(--ink-2);
  text-decoration: none;
}

.topnav a:hover {
  color: var(--ink);
  background: var(--idle-soft);
}

/* The current page is marked by weight and an underline bar, not by color.
 * The bar is non-text, so 3.55:1 clears its 3:1 bar. */
.topnav a[aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 0 var(--accent);
}

/* Company label — a status, not a link (test contract: class name is fixed). */
.nav-company {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: var(--s2);
  padding-left: var(--s3);
  border-left: 1px solid var(--line);
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
}

/* Makes the POST logout button read as one more quiet nav link. */
.linklike {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--s3);
  border: 0;
  border-radius: var(--r1);
  background: none;
  font: inherit;
  font-size: 14px;
  color: var(--ink-2);
  cursor: pointer;
}

.linklike:hover {
  color: var(--ink);
  background: var(--idle-soft);
}

.topbar form {
  margin: 0;
  flex-shrink: 0;   /* logout stays a real target; the company label shrinks instead */
}

.page-head {
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line);
}

.page-sub {
  margin: var(--s2) 0 0;
  max-width: 62ch;
  font-size: 14px;
  color: var(--ink-2);
}

/* Vertical rhythm for a page's major blocks. */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

/* Two columns; one column below 900px, stacked in markup order. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5);
  align-items: start;
}

/* /login only — one calm card centered in the viewport. Because the card is
 * pinned to the viewport centre, nothing scrolls behind it and its backdrop
 * stays a flat --paper field: that, and only that, is why the card may be
 * glass with text on it (spec §2.6). No ambient layer on this screen (§4.6). */
.auth {
  display: grid;
  place-items: center;
  min-height: calc(100vh - 160px);
  min-height: calc(100dvh - 160px);
}

/* Size only — the card's surface is the glass rule in §4. */
.auth .panel {
  width: 100%;
  max-width: 400px;
}

/* ========================================================================= *
 * 6. Surfaces — panel, callout, empty state, error, legal notice
 * ========================================================================= */

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r2);
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--line);
}

.panel-head > h2,
.panel-head > h3 {
  font-size: 16px;
  font-weight: 600;
}

/* Side notes in a panel head (counts, periods) stay quiet. */
.panel-head > small,
.panel-head > .page-sub {
  margin: 0;
  font-size: 13px;
  color: var(--ink-2);
}

.panel-body {
  padding: var(--s5);
}

/* A wide table scrolls inside its own box, never the page.
 * Preferred markup: <div class="table-scroll"><table class="data-table">…
 * The :has() rule is the safety net for a panel body holding a bare table;
 * it cannot match through the wrapper, so the two never double up. */
.table-scroll {
  overflow-x: auto;
}

.panel-body:has(> .data-table) {
  overflow-x: auto;
}

/* Let a table run edge to edge inside its panel. */
.panel-body.table-scroll,
.panel-body > .table-scroll {
  padding: 0;
}

.callout {
  padding: var(--s3) var(--s4);
  border-left: 3px solid var(--line-strong);
  border-radius: 0 var(--r1) var(--r1) 0;
  background: var(--idle-soft);
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-2);
}

.callout strong {
  color: var(--ink);
}

/* The dashed edge identifies a component, so it takes --line-control (3.50:1
 * on --surface) rather than the decorative --line-strong (1.48:1). */
.empty {
  padding: var(--s7) var(--s4);
  border: 1px dashed var(--line-control);
  border-radius: var(--r2);
  text-align: center;
  font-size: 14px;
  color: var(--ink-2);
}

/* Error — announced by the left bar and the sentence itself, not by color. */
.error {
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  border-left: 3px solid var(--loss);
  border-radius: 0 var(--r1) var(--r1) 0;
  background: var(--loss-soft);
  font-size: 14px;
  color: var(--loss);
}

/* Legal notice: "the recommendation is a probabilistic reference; the final
 * bid and its consequences are the user's responsibility." Promoted out of
 * the landing footer's .callout into a block of its own (spec §5.7).
 * Always opaque, never inside glass, never on the ambient layer, and always
 * --ink on --surface: 17.85:1 light / 13.86:1 dark. This has to stay one of
 * the highest-contrast texts in the app — do not soften it to --ink-2.
 *
 * text-align is pinned rather than inherited. This block is legal prose and
 * reads left-aligned wherever it is placed, including inside a centred section
 * such as .lp-close — inheritance is not something a class can outrank, so it
 * has to be stated. */
.notice {
  padding: var(--s5);
  border: 1px solid var(--line-control);
  border-radius: var(--r2);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
  text-align: left;
}

/* Long-form legal prose — /legal/privacy and /legal/terms, and nothing else.
 *
 * These are the only two screens in the app that are a DOCUMENT rather than an
 * instrument, so they need the one thing no other screen does: a measure.
 * .shell is 1120px, and a 처리방침 set edge to edge across that at 1440 is a
 * wall nobody reads. 680px puts roughly 40 Korean characters on a line, which
 * is the middle of the comfortable band for Hangul (the ch unit is sized on
 * '0' and understates a full-width glyph by half, so it is the wrong unit
 * here). Nothing else on the page changes: the topbar still spans the window.
 *
 * Permanently opaque, inheriting --ink on --paper. No glass, no
 * backdrop-filter — a legal document is a data surface (spec §5.7), and this
 * is text a reader may have to quote back to us. */
.legal-doc {
  max-width: 680px;
}

.legal-doc > section {
  margin-top: var(--s7);
}

.legal-doc h2 {
  margin-bottom: var(--s3);
  font-size: 18px;
}

.legal-doc h3 {
  margin: var(--s4) 0 var(--s2);
}

.legal-doc ul,
.legal-doc ol {
  margin: 0 0 var(--s3);
  padding-left: 1.35em;
}

.legal-doc li {
  margin-bottom: var(--s2);
}

.legal-doc li:last-child {
  margin-bottom: 0;
}

/* The tables inside a policy carry two columns of prose, not figures, so they
 * keep the .data-table rules but drop the tabular-figure alignment. Each is
 * wrapped in .table-scroll: at 375 the 항목/내용 pair cannot fit, and the box
 * scrolls rather than the page. */
.legal-doc .table-scroll {
  margin-bottom: var(--s3);
}

.legal-doc .data-table th[scope="row"] {
  white-space: nowrap;
}

/* Provenance and dates: how this document came to exist, and when it took
 * effect. Quiet, but not fine print — --ink-2 is the secondary-text token,
 * measured 7.07:1 light / 12.61:1 dark on --paper. The body of the document
 * itself stays --ink (16.65:1 / 15.19:1): it is the text a reader may have to
 * act on, and softening legal prose to a secondary token is exactly the move
 * that makes a policy look like something to skip. */
.legal-meta {
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  color: var(--ink-2);
}

.legal-meta p {
  margin: 0 0 var(--s2);
}

.legal-meta p:last-child {
  margin-bottom: 0;
}

/* ========================================================================= *
 * 7. Data — tables, metrics, charts, badges. All permanently opaque.
 * ========================================================================= */

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.data-table th {
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--line-strong);
  text-align: left;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  white-space: nowrap;
}

.data-table td {
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

.data-table tbody tr:last-child td,
.data-table tbody tr:last-child th {
  border-bottom: 0;
}

/* Two-column key/value tables (개찰 결과, 하한 검증, …) put the header in the
 * row, not in a thead. A row header is a label, so it takes the hairline the
 * cell beside it takes — the strong rule above belongs to the column axis. */
.data-table th[scope="row"] {
  border-bottom: 1px solid var(--line);
  padding: var(--s3) var(--s4);
  font-size: 14px;               /* match the value cell beside it, not a column head */
  font-weight: 600;
  color: var(--ink-2);
  white-space: normal;           /* a long Korean label wraps instead of widening */
}

/* An action packed into a table cell (the admin user toggle) must not add the
 * default form block margin to the row's height. */
.data-table form {
  margin: 0;
}

/* Numeric columns align right so digits line up for comparison. */
.data-table th.num,
.data-table td.num {
  text-align: right;
  white-space: nowrap;
}

.data-table tbody tr:hover {
  background: var(--idle-soft);
}

/* Recommended row: tint + weight + a left bar, so it survives color blindness.
 * Must follow the hover rule to keep the highlight while hovering. */
.data-table tr.is-recommended,
.data-table tbody tr.is-recommended:hover {
  background: var(--accent-soft);
  font-weight: 700;
}

.data-table tr.is-recommended td:first-child {
  box-shadow: inset 3px 0 0 0 var(--accent);
}

/* A row of headline figures inside a panel. One user today: the /stats
 * 예측 대비 실측 panel, three figures.
 *
 * auto-fit is safe here, but not for the obvious reason, so the real one is
 * written down: this container is wide enough to fit a FOURTH 200px track,
 * and auto-fit creates it — then collapses it to 0px because no item lands
 * in it, and the three figures divide the full width. Measured, 3 items in a
 * 1024px container: `325.33px 325.33px 325.33px 0px`.
 *
 * What defeats the collapse is any child that spans the row. Give this same
 * grid one `grid-column: 1 / -1` item and the fourth track is occupied, so
 * it cannot collapse and three figures sit in four tracks (measured:
 * `238px 238px 238px 238px`). That is precisely what happened to the landing
 * band, whose .lp-metric-note spans the row — hence its own explicit grid in
 * §11. Add a full-width child here and this rule needs the same fix. */
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s5);
}

/* A single figure with its label (sample size, hit rate, …). */
.metric {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
}

.metric-label {
  font-size: 13px;
  color: var(--ink-2);
}

.metric-value {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink);
}

.metric-unit {
  margin-left: 2px;
  font-family: 'Pretendard Variable', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink-2);
}

/* Chart frame — reserves the canvas's box before Chart.js measures it, so a
 * histogram cannot shove the page down when it finishes drawing. Chart.js
 * fills the frame (maintainAspectRatio: false), so the ratio lives here and
 * every chart in the app is the same shape. */
.chart-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 7;
  min-height: 200px;
}

.chart-frame canvas {
  width: 100% !important;
  height: 100% !important;
}

/* Badges (test contract: class names are fixed).
 * Neutral badges take their text color from --ink-2, not --idle: --idle on
 * --idle-soft is only 4.36:1, which fails AA at this size. Do not "fix" back. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  padding: 2px var(--s2);
  border: 1px solid currentColor;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}

.badge-win {
  color: var(--win);
  background: var(--win-soft);
}

.badge-loss {
  color: var(--loss);
  background: var(--loss-soft);
}

/* "Unknown" says "no value yet" with a dashed edge, not with color. */
.badge-unknown {
  color: var(--ink-2);
  background: transparent;
  border-style: dashed;
}

.badge-pending {
  color: var(--ink-2);
  background: var(--idle-soft);
}

.badge-done {
  color: var(--accent);
  background: var(--accent-soft);
}

/* Overdue result entry: the opening date has passed and no result was
 * recorded. An addition to the badge family, not a replacement —
 * badge-pending / badge-done above stay exactly as they are. Amber reads as
 * "your turn", not "error": nothing has gone wrong, the user simply owes an
 * input.
 *
 * The text is --ink, NOT --signal. --signal on --signal-soft is 4.51:1, which
 * spec §2.3 rules out for text for exactly this reason; --ink on --signal-soft
 * is 16.03:1 light / 11.20:1 dark, the same pairing the rail marker chip uses.
 * The amber lives in the border. */
.badge-due {
  background: var(--signal-soft);
  color: var(--ink);
  border-color: var(--signal);
}

/* The result-status badges in the recent-recommendations table are <a>
 * links (badge-pending / badge-done / badge-due); the 12px pill itself is far
 * under the 44x44 target minimum. Grow the hit area via a pseudo-element
 * instead of the visual pill, so the anchor markup and the compact table
 * density are both untouched. Non-interactive badges (win/loss/unknown, plain
 * <span>) don't get this — the WCAG target rule only applies to controls. */
a.badge {
  position: relative;
}

/* -11px, not -10px. The offsets are resolved against the anchor's PADDING box,
 * which is the 24px pill minus its 1px borders = 22px; at -10px the target
 * measured 42px tall in the browser and missed the 44x44 rule (spec §7.3) by
 * 2px. 22 + 11 + 11 = 44. The row is 49px tall, so the taller overlay still
 * clears the row above and below. */
a.badge::after {
  content: "";
  position: absolute;
  inset: -11px -6px;
}

/* The other two links that live in a data cell: the recommendation ID in
 * /app's recent list and the agency name in /stats' 발주기관 list. Both are
 * bare inline anchors — a two-digit ID is roughly 17x23px — so neither meets
 * the 44x44 target (spec §7.3).
 *
 * They take the treatment directly above: the hit area grows on a
 * pseudo-element, never on the visible text. The alternative — display:block
 * plus padding on the anchor — would have to cancel .data-table td's own
 * padding to avoid stacking with it, and would change the height of every row
 * in both tables; the overlay changes no layout at all, and the row is already
 * 47px tall, so the 44px target fits inside it without touching the rhythm.
 * The anchor is made inline-block so the overlay has a real containing block
 * even when a long agency name wraps to two lines.
 *
 * min-width + width:100% means "44px, or the text's width if that is wider",
 * so a long agency name gets a target as wide as itself and a short ID still
 * gets 44px, centred so the overhang is symmetrical. Badges are excluded:
 * a.badge has its own overlay and two would stack. */
.data-table td > a:not(.badge) {
  position: relative;
  display: inline-block;
}

.data-table td > a:not(.badge)::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  min-width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* ========================================================================= *
 * 8. Recommendation hero — the only element in the app with a drop shadow
 *    It is also never glass: this card holds the single number whose
 *    misreading costs the most, so it keeps an opaque --surface fill even
 *    when it sits inside the landing's glass frame (spec §8-2).
 * ========================================================================= */

.reco-hero {
  padding: var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  background: var(--surface);
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.10);
}

.reco-hero-label {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-2);
}

/* --signal on --surface is 5.02:1; on the ambient layer it would be 3.76:1.
 * That gap is why this card must never become translucent (spec §1.4). */
.reco-hero-figure {
  display: block;
  margin-top: var(--s2);
  font-size: 44px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--signal);
  overflow-wrap: anywhere;
}

.reco-hero-sub {
  margin: var(--s3) 0 0;
  font-size: 14px;
  color: var(--ink-2);
}

/* One line of calculation path: (predicted price - A) x floor rate + A. */
.reco-hero-basis {
  margin: var(--s4) 0 0;
  padding-top: var(--s3);
  border-top: 1px dashed var(--line);
  font-size: 13px;
  color: var(--ink-2);
}

/* ========================================================================= *
 * 9. Signature — the price rail
 *    Jinja writes the left/width percentages inline because they come from
 *    the data. The two bands must be told apart without color: one solid
 *    fill, one diagonal hatch. The marker is the only part that uses the
 *    signal color. The landing's ambient layer is this rail blown up to hero
 *    width, so the two must keep agreeing (spec §4.3).
 * ========================================================================= */

/* Each label is centered on its own tick. The recommended bid sits at ~0.5%
 * of the axis by construction, so half its label hangs past the axis start —
 * that is what the side padding is for. Keep <i> text to short words
 * ("추천 투찰", "기초금액"); amounts belong in the legend and the aria-label. */
/* The plate. Data is never on glass (spec §8-1), and this figure is data: tick
 * labels, the marker chip, the legend's swatches and its amounts. On /reco it
 * sits in a --surface .panel-body and the fill changes nothing; on the landing
 * it sits inside .lp-hero-frame, where without a fill of its own every one of
 * those labels would have been sitting directly on the translucent frame. It
 * is also what makes the 3:1 figures quoted for the bands and the legend
 * swatches true on the landing — they are measured against --surface.
 *
 * Margins: as a flex item of .lp-hero-frame.stack, .rail establishes its own
 * formatting context, so .rail-track's 56px/84px margins stay inside this
 * background box and the plate covers the chip above the track and the label
 * rows below it. (.rail-clip cannot do that job — overflow-x:clip with
 * overflow-y:visible does not establish a formatting context.) */
.rail {
  margin: 0;
  padding: 0 var(--s7);
  border-radius: var(--r2);
  background: var(--surface);
}

/* Clips only the track + its tick/marker overhang, never the figcaption
 * legend below — overflow-x:clip on the whole <figure> used to also clip
 * the legend's trailing values when a line ran long. The .rail padding is
 * still the buffer both children share for label overhang near 0%/100%. */
.rail-clip {
  overflow-x: clip;   /* any residual overhang must not scroll the page */
}

/* The two numbers that keep the three label rows apart are declared here, once,
 * and everything below is derived from them. The invariant is a single
 * sentence: THE LABEL'S LINE BOX MUST BE STRICTLY SHORTER THAN THE ROW PITCH.
 *
 * It is written as two tokens rather than as six hand-tuned pixel values
 * because the last time it was tuned by hand the relationship was implicit —
 * a `line-height: 1.2` ratio in one rule and three `bottom` offsets in three
 * others — and a later edit to either half silently reopened the overlap. A
 * ratio is also the wrong kind of number here: it tracks font-size, while the
 * thing it has to clear is a fixed row pitch.
 *
 * 14px inside a 20px pitch leaves 6px of air between adjacent rows, and
 * `white-space: nowrap` on the label means one line always, so the rows cannot
 * collide for ANY data — no reasoning about how close two ticks land is
 * needed. Deepest row: 3 x 20 + 4 + 14 = 78px below the track, inside the
 * 84px bottom margin. */
.rail-track {
  --tick-row: 20px;   /* row pitch */
  --tick-lh: 14px;    /* label line box — MUST stay strictly below --tick-row */
  position: relative;
  height: 14px;
  margin: 56px 0 84px;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  background: var(--surface);
}

.rail-band {
  position: absolute;
  top: 0;
  bottom: 0;
  min-width: 2px;
}

/* Predicted-price band — solid fill.
 *
 * The fill is --line-control, not --line-strong. A band is a graphical object
 * the reader needs in order to read the rail at all, so WCAG 1.4.11 asks 3:1
 * of it and the "decorative" exemption --line-strong rides on (spec §2.3) does
 * not apply: --line-strong on the track's --surface is 1.48:1 light /
 * 1.67:1 dark. --line-control is this file's >= 3:1 line token — 3.50:1 light,
 * 3.59:1 dark — and .empty in §6 already borrows it on exactly this argument.
 * It also stays well under the amber marker (--signal on --surface: 5.02:1
 * light / 7.96:1 dark), so the recommendation still wins the screen.
 *
 * Never "fix" this by darkening --line-strong globally: that token also draws
 * the table rules and the chart axis, and they would come with it (spec §8-18).
 * The solid-vs-hatch split below is what separates the two bands without
 * colour, so this rule must stay a flat fill. */
.rail-band-plan {
  background: var(--line-control);
}

/* Award-floor band — hatch fill, so pattern (not color) separates the two. */
.rail-band-floor {
  background: repeating-linear-gradient(
    45deg,
    var(--idle) 0 2px,
    transparent 2px 6px
  );
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

/* A tick is a hairline crossing the axis and poking out above; below, each
 * variant's leader runs down to its own label row (see below). Real data
 * regularly lands 기초금액 / 예정가 중앙 / 92% 지점 within ~1% of each other on
 * the axis, so x-proximity alone cannot separate their labels — three fixed
 * rows do, regardless of how close the tick positions are. */
.rail-tick {
  position: absolute;
  top: -8px;
  bottom: -8px;
  width: 0;
  border-left: 1px solid var(--ink-2);
}

/* Two things keep a label readable, and the second one is not optional.
 *
 * (1) The line box comes from --tick-lh, so it is the same number the row
 *     pitch is checked against (see .rail-track above).
 * (2) A tick belonging to a LOWER row has to run past the rows above it to
 *     reach its own label, so its leader crosses their text whenever the two
 *     ticks sit within a label's width of each other — which real data does
 *     constantly (measured on /reco: 기초금액 at 88.76% of the axis, 예정가
 *     중앙 at 88.67%, so the median leader ran straight through the 기초금액
 *     label). The label therefore paints ABOVE every leader (z-index) on an
 *     opaque plate the width of its own text (background + side padding), and
 *     the line passes behind it instead of through it. Ticks are siblings, so
 *     without the z-index the later tick's border still wins on paint order
 *     however opaque the earlier label's background is.
 *
 * The plate is --surface because that is what .rail paints under it on both
 * screens it appears on — the /reco panel body and the landing's glass frame,
 * where §9's .rail plate is the opaque ground. */
.rail-tick > i {
  position: absolute;
  z-index: 1;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  padding: 0 3px;
  background: var(--surface);
  font-style: normal;
  font-size: 12px;
  line-height: var(--tick-lh);
  color: var(--ink-2);
  white-space: nowrap;
}

/* Three rows, all below the track. The amber marker label (below) is the
 * only thing that ever sits above the track, so the two can never overlap
 * by construction — no reasoning about where rec_bid falls is needed. Each
 * row's leader (the tick's own bottom offset) is a different length, so
 * ownership of a label reads even when two ticks sit a pixel apart. */
.rail-tick-base {
  bottom: calc(-1 * var(--tick-row) - 4px);
}

.rail-tick-median {
  bottom: calc(-2 * var(--tick-row) - 4px);
}

.rail-tick-target {
  bottom: calc(-3 * var(--tick-row) - 4px);
}

/* Recommended-bid marker — the only signal-colored mark on the rail. */
.rail-marker {
  position: absolute;
  top: -14px;
  bottom: -14px;
  width: 2px;
  margin-left: -1px;
  background: var(--signal);
}

.rail-marker::before {
  content: "";
  position: absolute;
  bottom: -4px;
  left: 50%;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  background: var(--signal);
  transform: rotate(45deg);
}

/* The marker label is a signal-tinted chip with ink text: --signal on
 * --signal-soft is 4.51:1 (spec §2.3), which sits 0.01 above the line and is
 * therefore never used for text. --ink on --signal-soft is 16.03:1. */
.rail-marker > i {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  padding: 0 var(--s2);
  border: 1px solid var(--signal);
  border-radius: var(--r1);
  background: var(--signal-soft);
  font-style: normal;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
}

/* The legend reuses the band and marker classes as swatches. Each entry is a
 * label/value pair so a narrow viewport can stack them (label line, value
 * line) instead of running the whole phrase — swatch, Korean label, and an
 * unbreakable mono amount with no space to wrap on — together on one line,
 * which used to overflow the 261px rail box at 375px:
 * <figcaption class="rail-legend">
 *   <span class="rail-legend-item">
 *     <span class="rail-legend-label"><span class="rail-band rail-band-plan"></span>예상 예정가격:</span>
 *     <span class="rail-legend-value">…원</span>
 *   </span>
 * </figcaption>
 * Below 600px this legend is the only place the labels appear, so it must
 * carry the amounts as well as the names. */
.rail-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
  margin-top: var(--s3);
  font-size: 13px;
  color: var(--ink-2);
}

.rail-legend-item {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.rail-legend-label {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  white-space: nowrap;
}

.rail-legend-value {
  overflow-wrap: anywhere;   /* defensive: a mono amount has no space to wrap on */
}

/* The swatch is the legend's key, so it carries the same 3:1 duty as the band
 * it mirrors — hence --line-control (3.50:1 light / 3.59:1 dark on --surface)
 * rather than the decorative --line-strong (1.48:1 / 1.67:1). On the hatch
 * swatch the border is the only thing bounding it, because the hatch's gaps
 * are transparent. Those ratios are quoted against --surface, which is true on
 * the landing only because .rail carries its own opaque plate above. */
.rail-legend .rail-band {
  position: static;
  display: inline-block;
  width: 20px;
  height: 10px;
  border: 1px solid var(--line-control);
}

.rail-legend .rail-marker {
  position: static;
  display: inline-block;
  width: 3px;
  height: 14px;
  margin: 0;
}

.rail-legend .rail-marker::before {
  content: none;
}

/* ========================================================================= *
 * 10. Forms and buttons
 *     Every border that identifies a control takes --line-control (3.50:1 on
 *     --surface), which WCAG 1.4.11 requires and the decorative
 *     --line-strong (1.48:1) never met. --line-strong stays where it belongs:
 *     table rules, the chart axis, the rail track (spec §5.5).
 * ========================================================================= */

.form {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s4);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  min-width: 0;
}

.field-wide {
  grid-column: 1 / -1;
}

.field-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.field-hint {
  font-size: 13px;
  color: var(--ink-2);
}

/* Status line (autofill result, sample size, …) — carries success and failure
 * alike, so it stays neutral; a real failure uses .error. */
.field-msg {
  font-size: 13px;
  color: var(--ink-2);
}

/* One row inside a .field: a control and the thing sitting next to it — an
 * input with its lookup button, or a checkbox with its text. It wraps rather
 * than overflowing, so 375px never gets a horizontal scrollbar. */
.field-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
}

/* The text input takes the leftover width; the button keeps its own. A
 * checkbox or radio is excluded — it keeps its fixed 20px box below. */
.field-inline > input:not([type="checkbox"]):not([type="radio"]) {
  flex: 1 1 200px;
  width: auto;
  min-width: 0;
}

/* When the label IS the row (label wrapping a checkbox), its text is prose,
 * not a field name, and the stacked `label > input` margin must not apply. */
label.field-inline {
  font-weight: 400;
  color: var(--ink);
}

label.field-inline > input {
  margin-top: 0;
}

/* The disclosure that wraps /app's ③ 고급 설정 fields:
 * <details><summary>③ 고급 설정 …</summary><div class="form">…</div></details>
 * It is a control, so it owes the 44x44 target (spec §7.3); with no rule at
 * all it rendered at the browser default — a 16px/1.65 line box, 26px tall.
 *
 * display stays list-item. Switching it to flex or block would delete the
 * browser's disclosure triangle, and this control has no other affordance
 * saying it opens. So the height comes from padding instead: 8px top and
 * bottom takes the 26.4px line box to 42.4px and min-height finishes the last
 * 1.6px. Width is the panel's, since a list-item box is block-level. */
summary {
  min-height: 44px;
  padding: var(--s2) 0;
  cursor: pointer;
}

input,
select,
textarea {
  width: 100%;
  min-height: 44px;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line-control);
  border-radius: var(--r1);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 16px;  /* below 16px iOS zooms the page on focus */
}

textarea {
  min-height: 96px;
  line-height: 1.6;
  resize: vertical;
}

input[type="checkbox"],
input[type="radio"] {
  width: 20px;
  min-height: 20px;
  accent-color: var(--accent);
}

/* --ink-3 is the caption/placeholder token: 5.43:1 on --surface. */
input::placeholder {
  color: var(--ink-3);
}

/* Disabled text is body text, so it takes the strongest quiet ink available:
 * --ink-2 on --idle-soft is 6.92:1 light / 9.85:1 dark. */
input:disabled,
select:disabled,
textarea:disabled {
  background: var(--idle-soft);
  color: var(--ink-2);
}

/* Labels that wrap their input (login, admin toggles) stack the same way. */
label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

label > input,
label > select,
label > textarea {
  margin-top: var(--s2);
  font-weight: 400;
  color: var(--ink);
}

/* A form that owns a page rather than a column. /app is its only user: the
 * 최근 추천 table below it needs the shell's full width (§4.3-⑤ of the
 * wireframes, amended 2026-08-13), so the two blocks stack and the form would
 * otherwise be stretched to 1072px, putting a 기초금액 input half a screen
 * wide next to a four-character label. 560px keeps this form's two field
 * columns at the ~256px they measured inside the old .split, so the fields
 * themselves render exactly as they were verified. */
.form-column {
  max-width: 560px;
}

.form-actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s2);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;
  padding: 0 var(--s5);
  border: 1px solid var(--line-control);
  border-radius: var(--r1);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

.btn:hover {
  background: var(--idle-soft);
}

.btn-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

/* Mixing with --ink darkens in light mode and lightens in dark mode. */
.btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 86%, var(--ink));
  border-color: color-mix(in srgb, var(--accent) 86%, var(--ink));
  color: var(--accent-ink);
}

.btn-ghost {
  border-color: var(--line-control);
  background: transparent;
  color: var(--ink);
}

.btn-ghost:hover {
  background: var(--idle-soft);
}

/* Small button for table rows: keeps the 44px target, trims padding and type. */
.btn-sm {
  padding: 0 var(--s4);
  font-size: 14px;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ========================================================================= *
 * 11. Landing only (.lp-*, plus the legal footer the landing owns).
 *     The comment above each rule is the markup it expects.
 * ========================================================================= */

/* The landing's ground is white, not --paper. The ambient layer below fades
 * to zero alpha and needs white to fade into, and the hero box (max 1120px
 * wide) would otherwise read as a white card pasted onto grey. landing.html
 * does not extend base.html and has no body class, so the page identifies
 * itself by the one section only it contains. Browsers without :has() keep
 * today's --paper ground and nothing else changes. */
body:has(.lp-hero) {
  background: var(--surface);
}

/* Hero: copy on the left, a real recommendation render on the right.
 * <section class="lp-hero">
 *   <div class="lp-hero-copy">h1 + p + .lp-cta</div>
 *   <div class="lp-hero-render">
 *     <div class="lp-hero-frame glass">.reco-hero + .rail</div>
 *   </div>
 * </section>
 *
 * The ambient light layer is this element's background-image — no extra DOM.
 * It is not decoration: the three blobs ARE the two rail bands and the
 * recommendation marker, blown up to hero width, so the glass frame refracts
 * the product's own distribution and the light continues into the real rail
 * as you scroll (spec §4.1).
 *
 * The gradient strings must live here rather than in :root, because a custom
 * property is substituted on the element that DECLARES it — an --amb-*-x
 * override on .lp-hero would never reach a string baked at :root. Only the
 * colours are tokens.
 *
 * The x centres come from the rail percentages in the markup, converted into
 * hero coordinates (spec §4.3: hero_x = (track_x0 + rail_x * track_w) /
 * hero_w). Change the hero or rail padding and they must be recomputed, or
 * this signature degrades into a pretty gradient.
 *
 * Never give .lp-hero filter / opacity<1 / transform / mask / isolation /
 * contain:paint: each creates a backdrop root and would cut the glass frame
 * off from the very light it exists to refract. */
/* ---- The landing's display scale ----------------------------------------
 * The tool's type scale in §2 does not change and should not: 26px is the
 * right size for a heading that labels a panel of figures. It is the wrong
 * size for a heading that has to open a page — at 1440 the old 44px hero
 * headline measured smaller than the recommended amount rendered beside it.
 *
 * So the landing carries display sizes of its own, layered on top and scoped
 * to .lp-*. They are fluid rather than stepped because the page already has
 * four layout breakpoints and a second, different set of type breakpoints is
 * one more thing to keep in step by hand. `rem + vw` rather than plain `vw`:
 * a pure-viewport size ignores the browser's text-zoom entirely, and this page
 * has to survive a reader at 200%.
 *
 * Measured at the four verified widths — 375 / 768 / 1024 / 1440:
 *   h1                34 / 44 / 51 / 64      (768 is what the old rule gave)
 *   section h2        22 / 23 / 26 / 32
 *   .lp-metric-figure 30 / 30 / 33 / 40
 *   .lp-close h2      26 / 30 / 35 / 44
 * Every minimum is >= the size that width rendered before, so nothing shrinks
 * anywhere. */

/* Hero: copy on the left, a real recommendation render on the right.
 * <section class="lp-hero">
 *   <div class="lp-hero-copy">h1 + p + .lp-cta + .lp-cta-note</div>
 *   <div class="lp-hero-render">
 *     <div class="lp-hero-frame glass">.reco-hero + .rail</div>
 *   </div>
 * </section>
 *
 * The ambient light layer is this element's background-image — no extra DOM.
 * It is not decoration: the three blobs ARE the two rail bands and the
 * recommendation marker, blown up to hero width, so the glass frame refracts
 * the product's own distribution and the light continues into the real rail
 * as you scroll (spec §4.1).
 *
 * The gradient strings must live here rather than in :root, because a custom
 * property is substituted on the element that DECLARES it — an --amb-*-x
 * override on .lp-hero would never reach a string baked at :root. Only the
 * colours are tokens.
 *
 * The x centres come from the rail percentages in the markup, converted into
 * hero coordinates (spec §4.3: hero_x = (track_x0 + rail_x * track_w) /
 * hero_w). Change the hero or rail padding and they must be recomputed, or
 * this signature degrades into a pretty gradient. The 2026-08-13 pass changed
 * only the vertical padding and the blob radii, so both centres still hold.
 *
 * The radii grew with the alphas (see the token block in §1): the centres are
 * pinned to the rail and both of them land behind the opaque cards, so the
 * only part of this layer a reader can ever see is its falloff, and the
 * falloff had to reach the exposed ground — the copy column, the band above
 * and below the frame, and the gap the frame floats in.
 *
 * align-items is `start`, not `center`. Centred, a 207px copy column against
 * a 690px frame left 305px of empty white above the headline and the page
 * opened on nothing. Top-aligned, the headline is the first thing on the page
 * and the frame's extra height reads as the instrument being tall, which it is.
 *
 * Never give .lp-hero filter / opacity<1 / transform / mask / isolation /
 * contain:paint: each creates a backdrop root and would cut the glass frame
 * off from the very light it exists to refract. */
.lp-hero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s6);
  align-items: start;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(40px, 4.4vw, 80px) var(--s4) clamp(64px, 7vw, 120px);
  background-color: var(--surface);
  background-repeat: no-repeat;
  background-image:
    radial-gradient(78% 72% at var(--amb-plan-x) 38%,
      var(--amb-cool-a) 0%, var(--amb-cool-0) 70%),
    radial-gradient(64% 58% at var(--amb-floor-x) 70%,
      var(--amb-cool-b) 0%, var(--amb-cool-0) 72%),
    radial-gradient(26% 28% at var(--amb-floor-x) 74%,
      var(--amb-warm) 0%, var(--amb-warm-0) 70%);
}

/* Weight 600, not the 700 every other heading in this file carries: at 64px a
 * Korean display line set in Pretendard's bold reads as shouting, and
 * 10-design-system.md §2.2-2 spelled 600 out for exactly this headline before
 * the code went the other way. */
.lp-hero h1 {
  font-size: clamp(34px, 1.3rem + 3vw, 64px);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.03em;
}

/* The lead sentence — one paragraph directly under the hero heading. It sits
 * on the ambient layer, where --ink-2 is 5.67:1 and --ink 13.37:1. --signal
 * is 3.76:1 there and is therefore never used on this layer (spec §4.5).
 *
 * --ink-3 is barred here too, and this note is the only thing recording it:
 * it is the one text token the spec's contrast tables never checked against
 * this layer. Measured on the worst three-blob overlap — all three gradients
 * at their stated alpha, composited in paint order, i.e. the FIRST
 * background-image layer is the topmost one, so --amb-warm goes down on
 * --surface first and --amb-cool-a lands last, giving #DDDEEA light and
 * #2B3A4D dark — --ink-3 is 4.05:1 light and 4.51:1 dark. AA asks 4.5:1 of a
 * caption, so that is a fail in light and a hairline pass in dark. Nothing
 * inside .lp-hero uses it today; keep it that way, and reach for --ink-2 when
 * a caption is wanted on the light. Retuning the token instead was considered
 * and rejected: --ink-3 is the caption and placeholder ink for the whole app,
 * spec §8-19 already pins its value from below, and every row of the §2.3
 * table would have to be recomputed to buy contrast on a layer that no
 * caption sits on. */
.lp-hero-copy > p {
  margin-top: var(--s4);
  max-width: 46ch;
  font-size: 20px;
  color: var(--ink-2);
}

.lp-hero-render {
  min-width: 0;
}

/* The glass frame floating on the light. It takes the material from the
 * .glass class in the markup; this rule adds its padding and its edge.
 * Everything inside stays opaque — .reco-hero keeps its --surface fill and its
 * drop shadow, and .rail carries a --surface plate of its own, declared in §9 —
 * because glass never nests inside glass and a number never sits on a
 * translucent plate (spec §4.4). Both of those fills are load-bearing here:
 * this is the one screen where anything inside them has glass behind it.
 *
 * The edge is --glass-edge rather than the shared --glass-border. On white the
 * 0.08 hairline measured 1.04:1 against its surroundings — the pane had no
 * boundary at all, which is half of why the client could not see any glass.
 * Overriding border-color here, and not the shared token, keeps the topbar's
 * hairline on the tool screens exactly as it was verified. The three fallback
 * blocks in §14 redefine --glass-edge alongside --glass-border, so this frame
 * still flattens with everything else.
 *
 * The fill does the other half of the work, and it needs no help: 0.80 white
 * over the ambient light is visibly paler than the ambient beside it, so the
 * pane reads as a step in the light — which is what glass is. That step only
 * exists because the light behind it is now strong enough to be washed out. */
.lp-hero-frame {
  padding: var(--s6);
  border-color: var(--glass-edge);
}

/* CTA row: <div class="lp-cta"><a class="btn btn-primary">…</a>
 *            <a class="btn btn-ghost">…</a></div>
 *
 * There is one button here today and there is no second one to add: §2.2-4 of
 * the wireframes pairs [도입 문의] with (로그인), and the 문의 address does not
 * exist yet (§11-1). A 44px button under a 64px headline read as an
 * afterthought, so the landing's CTA is sized like the commitment it is —
 * 56px tall, 17px label — and the sentence below it says what happens on the
 * other side of the click. That is the honest way to make one button look
 * deliberate; a decorative second button pointing at an anchor the topbar
 * already carries is the dishonest way, and §2.2-4 retired it once already. */
.lp-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s6);
}

.lp-cta .btn {
  min-height: 56px;
  padding: 0 var(--s6);
  font-size: 17px;
}

/* <p class="lp-cta-note"> — sits directly under .lp-cta inside .lp-hero-copy.
 * Two classes, so it outranks the 20px lead-paragraph rule below (0,1,1)
 * without the template having to place it outside the copy column. */
.lp-hero-copy > .lp-cta-note {
  margin: var(--s3) 0 0;
  max-width: 46ch;
  font-size: 14px;
  color: var(--ink-2);
}

/* Landing section headings — ONE RULE, applied to all of them: every section
 * on this page states its own name in a visible <h2>.
 *
 * "All sr-only" is not an available rule: canon spells out a visible heading
 * for the evidence band and for the honest close (11-wireframes §2.5-1,
 * §2.8-1), both of which render. So the only treatment that can be
 * consistent is "all visible". Two headings — 검증 지표 and 작동 방식 — were
 * sr-only, which
 * left the calibration figures and the three 작동 방식 cards floating with no
 * name above them, and made the topbar's 검증/근거 anchors land on untitled
 * bands. The hero keeps the page's only h1; the step cards keep their h3, so
 * the outline stays h1 > h2 > h3 with nothing skipped.
 *
 * Every one of these sections is a grid, so its heading spans all the tracks
 * instead of taking a card's slot. */
.lp-metrics > h2,
.lp-steps > h2,
.lp-nots > h2,
.lp-facts > h2 {
  grid-column: 1 / -1;
  margin: 0;
}

/* …and one size for all of them, the reversed band's included. Kept in a rule
 * of its own because .lp-invert is not a grid: folding its heading into the
 * list above would hand it `margin: 0` at (0,1,1) and beat the `.lp-invert > *`
 * auto-centring at (0,1,0), which is how a full-bleed band's heading ends up
 * flush against the viewport edge.
 *
 * A section heading is the page's second voice after the hero, so it is set
 * from the display scale rather than from §2's 20px h2 — at 20px on a 1440
 * page every band read as a panel and nothing led. */
.lp-metrics > h2,
.lp-steps > h2,
.lp-nots > h2,
.lp-facts > h2,
.lp-invert > h2 {
  font-size: clamp(22px, 1rem + 0.9vw, 32px);
  letter-spacing: -0.02em;
}

/* Calibration band: <section class="lp-metrics"> holding its <h2>, three
 * .lp-metric figures and the .lp-metric-note lines. Hairlines top and bottom
 * make it read as one strip across the page.
 *
 * The track count is written down rather than inferred. This band used to
 * borrow .metrics' `repeat(auto-fit, minmax(200px, 1fr))` (§7), which here
 * resolved to FOUR tracks — measured 250px x4 at 1440 and 226px x4 at 1024.
 * Four rather than three because .lp-metric-note spans `1 / -1`: it reaches
 * into the fourth track, so auto-fit cannot collapse it the way it does in a
 * plain .metrics panel, and the three figures stopped short of the right
 * edge. The band read as an accident rather than a composition.
 *
 * The figure count is fixed by 11-wireframes §2.3 at three, so the grid says
 * three; below 768 they stack, as §2.3 requires. */
/* Section rhythm, decided here and nowhere else. Before this pass every band
 * on the page carried the same --s8 of air and the same 20px heading, so the
 * page had no order of importance and the eye had nowhere to rest. The five
 * beats, in the order a reader meets them:
 *
 *   hero      opens        tallest, display type, the light and the pane
 *   metrics   grounds it   a short tinted strip — measured fact, not prose
 *   steps     explains     roomy white, bordered cards (the mechanism)
 *   invert    proves       the deepest band on the page, and the one reversal
 *   nots/facts qualifies   quiet hairline lists, no boxes
 *   close     lands it     roomy, centred, display type
 *
 * Nothing here is decoration: the devices are ground, air and type size, and
 * each one says how much weight to give what it holds.
 *
 * The metrics band takes a --idle-soft ground rather than the two hairlines it
 * had. It is the plinth the hero stands on, and a change of ground says
 * "different kind of statement" more plainly than a rule does. It is not a
 * second reversal — the dark band below is still the only one on the page. */
.lp-metrics {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5);
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(32px, 3.4vw, 56px) var(--s4);
  background: var(--idle-soft);
  border-radius: var(--r3);
}

/* <div class="lp-metric"><span class="lp-metric-figure num">79.763%</span>
 *   <span class="lp-metric-label">…</span></div> */
.lp-metric {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
}

.lp-metric-figure {
  font-size: clamp(30px, 1rem + 1.7vw, 40px);
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
}

.lp-metric-label {
  font-size: 14px;
  color: var(--ink-2);
}

/* <p class="lp-metric-note"> — the method-and-sample sentence that must ride
 * with every calibration figure, and the live inventory line. It spans the
 * whole band; grid-column is simply ignored if the template places it after
 * the band instead. Wording is owned by 11-wireframes §2.3-4: the backtest is
 * a date-ordered walk-forward replay, never a "holdout". */
.lp-metric-note {
  grid-column: 1 / -1;
  margin: 0;
  max-width: 88ch;
  font-size: 13px;
  color: var(--ink-2);
}

/* How it works: <section class="lp-steps"> holding three .lp-step.
 * Numbering, if any, is written in the markup — this IS an ordered process. */
.lp-steps {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5);
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(64px, 6.5vw, 112px) var(--s4) var(--s7);
}

/* <article class="lp-step"><h3 class="lp-step-title">…</h3>
 *   <p class="lp-step-body">…</p></article>
 *
 * These three keep their boxes. They are the one place on the page where a
 * card earns its border — three parallel parts of one mechanism, read across
 * rather than down. The declarations and the facts below do not have that
 * shape, and they gave their boxes up (see .lp-not / .lp-fact). */
.lp-step {
  padding: var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  background: var(--surface);
}

.lp-step-title {
  font-size: 18px;
  font-weight: 600;
}

.lp-step-body {
  margin: var(--s2) 0 0;
  font-size: 14px;
  color: var(--ink-2);
}

/* Term footnotes under the three steps (사정률 · 투찰률 · A값 · 낙찰하한율).
 * <dl class="lp-terms">
 *   <div class="lp-term"><dt>사정률</dt><dd>…</dd></div>  (x4)
 * </dl>
 * A <div> grouping each dt/dd pair is valid inside <dl> and is what lets the
 * pairs become grid cells. Two columns from 768px (§12). A heading, if the
 * page wants one, goes before the <dl>, not inside it. */
.lp-terms {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s4);
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 var(--s4) clamp(64px, 6.5vw, 112px);
  font-size: 14px;
}

.lp-term {
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}

.lp-term dt {
  font-weight: 600;
  color: var(--ink);
}

.lp-term dd {
  margin: var(--s1) 0 0;
  color: var(--ink-2);
}

/* The one colour-reversed band on the page: the backtest evidence table.
 * It redefines the token set locally instead of hard-coding dark colours, so
 * every component inside keeps working unchanged — the same trick the
 * dark-mode block uses. This band is also the reason the glass alpha is fixed
 * at 0.80: it scrolls under the topbar, and at 0.72 the bar's --ink-2 fell to
 * 4.01:1 over it (spec §2.5).
 * <section class="lp-invert">
 *   <h2>…</h2><p>…</p>
 *   <div class="lp-evidence"><table class="data-table">…</table></div>
 *   <p>…</p>
 * </section> */
.lp-invert {
  --ink: #E2E8F0;
  --ink-2: #CBD5E1;
  --ink-3: #94A3B8;
  --surface: #131C2B;
  --paper: #0B1220;
  --line: #233047;
  --line-strong: #33415B;
  --line-control: #64748B;
  --accent: #60A5FA;
  --accent-ink: #0B1220;
  --accent-soft: #1C2C4C;
  --signal: #F59E0B;
  --signal-soft: #3B2A08;

  /* Status tokens too: the evidence table's 구간 포함 column is a .badge, and
   * a light-mode mint pill on a reversed band reads as a rendering bug.
   * Verified dark pairings (spec §2.4): win 8.44:1, loss 5.75:1. */
  --win: #4ADE80;   --win-soft: #0F2E1C;
  --loss: #F87171;  --loss-soft: #3B1717;
  --idle: #94A3B8;  --idle-soft: #1E293B;

  /* The deepest band on the page, deliberately. It is the only place the
   * product shows its predictions next to what actually happened, so it gets
   * the height the wireframes budgeted it (§2.0: the largest of any section)
   * and the page's one reversal. Everything above and below it is white. */
  padding: clamp(80px, 9vw, 152px) var(--s4);
  background: var(--paper);
  color: var(--ink);
}

/* In dark mode there is nothing to invert. Reversing again would put a
 * near-white surface under the glass topbar, where --accent falls to 3.55:1
 * (spec §2.5). So the band becomes a raised surface with hairlines instead.
 * This is the one component rule in the file that dark mode changes, and it
 * exists to protect a contrast guarantee, not to restyle anything. */
@media (prefers-color-scheme: dark) {
  .lp-invert {
    --surface: #1B2536;
    background: var(--surface);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
  }
}

/* The band bleeds to the viewport edges; its contents do not. */
.lp-invert > * {
  max-width: 1120px;
  margin-right: auto;
  margin-left: auto;
}

.lp-invert > * + * {
  margin-top: var(--s5);
}

/* Wrapper around the evidence .data-table. It scrolls inside itself so 375px
 * never scrolls the page, and it stays an opaque surface — a data table is
 * never glass. Inside .lp-invert, --surface is already the dark value. */
.lp-evidence {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r2);
  background: var(--surface);
}

/* "우리가 하지 않는 것" (4 declarations, 2x2 from 768px) and the data/security
 * facts (3 items, 3 columns from 768px).
 * <section class="lp-nots"><h2>…</h2><article class="lp-not">…</article>x4</section>
 * <section class="lp-facts"><h2>…</h2><article class="lp-fact">…</article>x3
 *   <p>취득하지 않은 보안 인증은 적지 않습니다.</p></section>
 * Icons, if any, are inline SVG at 24x24 with stroke-width 1.5 — never emoji,
 * never an icon font, never an external file (spec §5.8). */
.lp-nots,
.lp-facts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5);
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(56px, 5vw, 88px) var(--s4);
}

/* A closing sentence spans the whole grid instead of taking a card's slot.
 * The section headings do too — see the .lp-* h2 rule above. */
.lp-nots > p,
.lp-facts > p {
  grid-column: 1 / -1;
  margin: 0;
}

/* No box. These seven blocks — four things the service does not do, three
 * facts about the data — are declarations, not parts of a mechanism, and
 * boxing them made them weigh exactly as much as the three steps above and the
 * evidence band below. They now take the same hairline treatment as the term
 * footnotes, which is what they are: things stated plainly, to be read and
 * moved past. Losing the border is what lets the sections above and below
 * them lead. */
.lp-not,
.lp-fact {
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}

.lp-not > h3,
.lp-fact > h3 {
  color: var(--ink);
}

.lp-not > p,
.lp-fact > p {
  margin: var(--s2) 0 0;
  font-size: 14px;
  color: var(--ink-2);
}

/* Honest close + login CTA:
 * <section class="lp-close"><h2>…</h2><p>…</p><div class="lp-cta">…</div>
 *   <p class="notice">…</p></section> */
.lp-close {
  max-width: 720px;
  margin: 0 auto;
  padding: clamp(72px, 8vw, 136px) var(--s4);
  text-align: center;
}

.lp-close h2 {
  font-size: clamp(26px, 1rem + 1.9vw, 44px);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.02em;
}

/* Prose in the close section — scoped to direct children, and explicitly not
 * to the legal notice.
 *
 * Written bare, `.lp-close p` is (0,1,1) and outranks `.notice` (0,1,0), so a
 * notice placed in this section would silently repaint --ink-2 at 16px; the
 * section's text-align would centre it on top of that, by inheritance rather
 * than by any rule .notice could outrank. The template used to have to lift
 * the notice out of the section to dodge both. It no longer has to: :not()
 * takes the paragraph rule off .notice, > keeps it off the paragraphs inside a
 * <div class="notice">, and .notice pins its own alignment in §6. Markup
 * should not have to be careful to keep a legal notice legible. */
.lp-close > p:not(.notice) {
  margin-top: var(--s4);
  font-size: 16px;
  color: var(--ink-2);
}

.lp-close .lp-cta {
  justify-content: center;
}

/* Final CTA band. <section class="lp-final"><h2>…</h2><div class="lp-cta">…
 * The design budget is 400px tall (11-wireframes §2.9); keep the copy short
 * rather than capping the height, which would clip it. No countdown, no
 * limited-time anything — those are forbidden on this landing. */
.lp-final {
  padding: var(--s8) var(--s4);
  background: var(--idle-soft);
  text-align: center;
}

.lp-final > * {
  max-width: 720px;
  margin-right: auto;
  margin-left: auto;
}

.lp-final .lp-cta {
  justify-content: center;
}

/* Legal footer.
 * <footer class="site-footer">
 *   <p>상호 · 대표자 · 주소 · 전화 · 이메일 · 사업자등록번호</p>
 *   <ul class="legal"><li class="legal-item"><a href="…">이용약관</a></li>…</ul>
 *   <dl class="fact-block"><dt>서비스명</dt><dd>…</dd>…</dl>
 * </footer>
 * The six items 전자상거래법 §10 requires are supplied by the client; this
 * file only lays them out. Never invent placeholder company data. */
.site-footer {
  padding: var(--s7) var(--s4) var(--s8);
  border-top: 1px solid var(--line);
  background: var(--surface);
  font-size: 13px;
  color: var(--ink-2);
}

.site-footer > * {
  max-width: 1120px;
  margin-right: auto;
  margin-left: auto;
}

.legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--s5);
  margin-top: var(--s2);
  padding: 0;
  list-style: none;
}

.legal-item {
  display: flex;
  align-items: center;
}

/* The two policy documents are real links, so they keep the 44px target. */
.legal-item a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* Key-value facts, for people and for generative search engines that quote a
 * page rather than render it. Two columns (key | value) from 768px, stacked
 * below that. Every value here must be a fact this repo can defend — the
 * verification figures come from docs/calibration-report-2026-08-wp14.md. */
.fact-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s1) var(--s5);
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}

.fact-block dt {
  font-weight: 600;
  color: var(--ink);
}

.fact-block dd {
  margin: 0;
}

/* ========================================================================= *
 * 12. Responsive — 375 / 768 / 1024 / 1440, never a horizontal page scroll.
 *     Glass does not change with width: the bar keeps the same recipe at
 *     375px, because it is the only visual thread between the landing and
 *     the tool screens. Performance is answered by the fallbacks in §14, not
 *     by viewport width — a small screen is not a user request (spec §7.1).
 * ========================================================================= */

@media (max-width: 599px) {
  /* A 44px figure wraps at this width, which reads worse than a smaller one. */
  .reco-hero {
    padding: var(--s5);
  }

  .reco-hero-figure {
    font-size: 34px;
  }

  /* The frame stays glass; only its padding gives way. */
  .lp-hero-frame {
    padding: var(--s4);
  }

  /* "375의 CTA는 전폭" (11-wireframes §9-3). Primary only, and only where the
   * button IS the page's committing action: the landing's hero and close CTAs
   * (.lp-cta), and the one submit each form carries (.form-actions holds
   * exactly one .btn-primary and nothing else, on every screen in this app).
   * Deliberately not `.btn`: the 공고 정보 불러오기 lookup beside the 공고번호
   * input is a .btn-primary too, but it lives in a .field-inline, and a
   * viewport-wide lookup button would read as a second page-level CTA
   * competing with 추천 계산. Small inline buttons keep their own width. */
  .lp-cta .btn-primary,
  .form-actions .btn-primary {
    width: 100%;
  }

  /* Here the tick labels overlap each other, and a label centered on a tick
   * at 0% or 100% of the axis gets cut. So the axis carries no labels at all
   * and the legend does the naming. Ticks, bands and the signal marker stay,
   * so every position is still visible. */
  .rail {
    padding: 0 var(--s4);
  }

  .rail-track {
    margin: var(--s5) 0 var(--s4);
  }

  .rail-track i {
    display: none;
  }

  /* Labels are hidden above, so the per-variant leader lengths would
   * otherwise protrude as bare lines with nothing to lead to. Collapse every
   * tick back to the plain 8px poke. */
  .rail-tick-base,
  .rail-tick-median,
  .rail-tick-target {
    bottom: -8px;
  }

  /* Topbar: two deliberate rows instead of three ragged ones (brand /
   * links+company / logout). Brand keeps its own row; the nav row is forced
   * onto a new line and kept to one line by giving the fixed-content items
   * (links, logout) flex-shrink:0 and letting only .nav-company — the
   * tenant-identity signal, not a control — shrink and truncate. */
  .topnav {
    flex-basis: 100%;
    flex-wrap: nowrap;
    margin-left: 0;
    margin-top: var(--s1);
  }

  .topnav a,
  .linklike {
    flex-shrink: 0;
    padding: 0 var(--s2);
  }

  .nav-company {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* Tablet and up */
@media (min-width: 768px) {
  .shell {
    padding: var(--s7) var(--s5) var(--s8);
  }

  .topbar-inner {
    padding: 0 var(--s5);
  }

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

  /* .lp-hero h1 used to step to 44px here. It is now one fluid clamp in §11,
   * which passes through 44px at exactly this width and keeps going. */

  .lp-metrics,
  .lp-steps,
  .lp-facts {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .lp-terms,
  .lp-nots {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .fact-block {
    grid-template-columns: max-content minmax(0, 1fr);
  }
}

/* .split becomes two columns at 900px */
@media (min-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

@media (min-width: 1024px) {
  /* Two columns, and the only place the ambient layer moves: the rail now
   * lives in the right-hand column, so both blob centres shift right with it
   * (spec §4.3 — recompute these two if the hero columns or paddings change). */
  .lp-hero {
    --amb-plan-x: 83%;
    --amb-floor-x: 62%;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  /* Side padding only. The vertical rhythm belongs to each section's own rule
   * in §11 — a `padding` shorthand here would silently flatten it back to one
   * value for every band, which is what it used to do. */
  .lp-hero,
  .lp-metrics,
  .lp-steps,
  .lp-terms,
  .lp-invert,
  .lp-nots,
  .lp-facts,
  .lp-close,
  .lp-final,
  .site-footer {
    padding-left: var(--s5);
    padding-right: var(--s5);
  }
}

@media (min-width: 1440px) {
  .shell {
    padding-top: var(--s8);
  }
}

/* ========================================================================= *
 * 13. Motion — two kinds, both off when the reader asks for less.
 *     a) Keyframe animation: transform/opacity only, 150-300ms, 1-2 per screen.
 *     b) Interaction-state transitions on links, buttons, nav, table rows
 *        and form controls: color/background-color/border-color/box-shadow
 *        at 150ms, never a layout property (width/height/top/left/margin).
 *     Never animate backdrop-filter's blur or the glass alpha, and never tie
 *     either to scroll position: every frame would recomposite the backdrop,
 *     and the contrast guarantees in §1 hold only for fixed values (spec §6).
 *     No parallax — the ambient layer moves with the page.
 * ========================================================================= */

a,
.btn,
.topnav a,
.linklike,
.data-table tbody tr,
input,
select,
textarea {
  transition: color 150ms ease, background-color 150ms ease,
              border-color 150ms ease, box-shadow 150ms ease;
}

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

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

/* ========================================================================= *
 * 14. Fallbacks — three signals, one opaque outcome. KEEP THIS SECTION LAST.
 *
 *   (a) @supports — the browser cannot do backdrop-filter at all.
 *   (b) prefers-contrast: more — the ONLY signal Safari actually fires;
 *       without this branch an iPhone user has no way to turn translucency off.
 *   (c) prefers-reduced-transparency — Chrome/Edge 118+, Firefox 113 (flagged).
 *
 * Safari does not support (c) at all, which is why (b) is not optional.
 *
 * Nothing here rewrites a component rule; the tokens are redefined and every
 * glass surface follows. Under any fallback the glass becomes an opaque
 * --surface plane and the topbar becomes an ordinary bar with a
 * --line-control hairline. No fallback moves the layout by one pixel.
 *
 * Two ordering constraints put this section at the end of the file:
 *   1) It must follow @media (prefers-color-scheme: dark). Both redefine
 *      --glass-bg on :root at equal specificity, so source order decides. In
 *      front of the dark block, a dark-mode user who switches on "increase
 *      contrast" would get the translucent glass back — the exact case these
 *      blocks exist to prevent. Behind it, --glass-bg: var(--surface)
 *      resolves to the dark --surface and the two conditions compose.
 *   2) The prefers-contrast branch also flattens the ambient layer, and
 *      `.lp-hero { background-image: none }` ties .lp-hero's own rule in §11
 *      on specificity — so it, too, only wins from behind.
 *
 * (a) MUST test both spellings. Safari 9-17 supports only
 * -webkit-backdrop-filter, so testing the unprefixed property alone would
 * report "no support" on exactly the browsers the prefix exists to serve and
 * would switch their glass off (spec §3.4).
 * ========================================================================= */

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root {
    --glass-bg: var(--surface);
    --glass-filter: none;
    --glass-border: var(--line-control);
    --glass-edge: var(--line-control);
    --glass-highlight: none;
    --glass-backdrop-h: 100%;
    --glass-mask: none;
  }
}

@media (prefers-contrast: more) {
  :root {
    --glass-bg: var(--surface);
    --glass-filter: none;
    --glass-border: var(--ink-2);        /* boundary strengthened, not just opaque */
    --glass-edge: var(--ink-2);
    --glass-highlight: none;
    --glass-backdrop-h: 100%;
    --glass-mask: none;
  }

  /* The light layer flattens too: to someone who asked for more contrast, a
   * faint wash of colour behind the text is noise, not atmosphere. */
  .lp-hero {
    background-image: none;
  }
}

@media (prefers-reduced-transparency: reduce) {
  :root {
    --glass-bg: var(--surface);
    --glass-filter: none;
    --glass-border: var(--line-control);
    --glass-edge: var(--line-control);
    --glass-highlight: none;
    --glass-backdrop-h: 100%;
    --glass-mask: none;
  }

  /* The light flattens with the glass, for the same reason it does under
   * prefers-contrast: someone who has asked the OS to stop rendering
   * translucency is not asking for a coloured wash behind the copy either. */
  .lp-hero {
    background-image: none;
  }
}
