/* =============================================================================
   whereto.com.au — v2 "Stall Number" · blush theme
   design.md still documents the original teal ground; this file supersedes it
   on colour until the spec is rewritten. Structure, type and motion still come
   from design.md and must not be invented outside it.
   Contrast figures in comments are measured, not estimated.
   ========================================================================== */

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

:root {
  /* ══ SKY THEME ═════════════════════════════════════════════════════════════
     Ground: #f0f8ff, the palest of three sky blues, chosen from blue-options
     against blush, butter and mint.

     The reasoning is the same one that produced Singapore's pink. That was a
     blend of the Singapore flag's red and white; Australia's flag is blue and
     white. Sky blue was never rejected on its merits over there — it lost to a
     flag argument that now points the other way.

     Every ratio below is measured, not estimated. Three tokens in the Singapore
     build carried comments claiming figures the colours did not meet, including
     a control edge stated at 3.55:1 that measured 2.92 — under the floor on the
     only visible boundary of every button on the page. Where a value has
     headroom over its floor, that is deliberate. */

  /* ── Surfaces ── */
  --signboard:       #f0f8ff;   /* hero ground. 14.19:1 with ink */
  --signboard-deep:  #eaf2fc;   /* header bar, closing band, footer. 13.49:1 */
  --signboard-edge:  #d8e6f5;   /* dividers on the ground: decorative, exempt */
  --signboard-ctl:   #7d8b9e;   /* control edges on the ground: 3.23:1 / 3.07:1 */
  --enamel:          #fcfdff;   /* page ground */
  --enamel-sunk:     #f2f6fa;
  --enamel-edge:     #e4ebf2;   /* dividers: decorative, exempt from 3:1 */
  --enamel-edge-ctl: #78828e;   /* control edges: 3.83:1 on enamel, 3.59:1 on
                                   sunk. Carries real headroom on purpose —
                                   this is the token that shipped at 2.92:1
                                   against a claimed 3.55:1 in the blush build,
                                   and it is the only visible boundary on .btn,
                                   the Open-now toggle and the distance menu. */

  /* ── Type on the branded ground ──
     Split from --enamel because that variable was doing two jobs: page
     background AND text on the ground. They pull in opposite directions the
     moment the ground is light. */
  --on-board:      #12292c;     /* 14.19:1 on signboard, 13.49:1 on deep */
  --on-board-mute: #2b4152;     /* 9.88:1 and 9.39:1.

                                   This one value decides how thick the hero
                                   wash has to be, and therefore whether the
                                   photograph is visible at all. The type must
                                   out-contrast the darkest pixel behind it, so
                                   lighter type forces a thicker wash, which
                                   hides the photo.

                                   At #44586a the wash needed 81%. At #2b4152 it
                                   needs 66%. Derive this before anything else
                                   in the palette — see the hero rule below. */

  /* ── Ink and accents ── */
  --ink:         #12292c;
  --ember:       #a83c0a;       /* burnt orange. 5.90:1 on the ground, 6.22:1 on
                                   the page. Red earth against a blue sky — the
                                   Australian answer to the question Singapore
                                   answered with the lantern, rather than a copy
                                   of the answer. */
  --ember-deep:  #7d2c06;       /* hover only */
  --on-ember:    #ffffff;       /* 6.33:1 on the fill. Ink would be 2.40:1 —
                                   design.md §3 rule 4 says the opposite and is
                                   stale; it describes the dark-ground era. */
  --plate:       #c6e1ff;       /* rank numeral plate. Ink numeral 11.32:1 */
  --plate-hot:   #accef4;       /* the same plate on hover. 9.34:1 */
  --turmeric-star: #e8b34a;     /* the rating star only. 1.88:1 on the card.
                                   Exempt because it is decorative in the strict
                                   sense: the value is carried by the ink
                                   numeral beside it and by a visually-hidden
                                   "Rated" label. Delete the glyph and no
                                   information is lost.

                                   The exemption dies the moment this becomes a
                                   five-star row with filled and empty states —
                                   the fill would then BE the value, carried by
                                   colour at 1.88:1, and 1.4.1 would fail. */
  --navy:        #072754;       /* the wordmark. 13.04:1 on the header */
  --slate:       #4f606e;       /* secondary type on the page. 6.39:1 */
  --slate-light: #2b4152;       /* alias of --on-board-mute, kept for old rules */
  --open:        #1f6b4d;       /* 6.31:1 on the card */
  --closed:      #a5342a;       /* 6.60:1 on the card */

  /* ── Type (§4) ── */
  --display: "Archivo", system-ui, sans-serif;
  --body:    "Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  --step--1: clamp(11px, 0.68rem + 0.05vw, 12px);
  --step-0:  clamp(15px, 0.90rem + 0.10vw, 16px);
  --step-1:  clamp(18px, 1.00rem + 0.50vw, 22px);
  --step-2:  clamp(26px, 1.30rem + 1.50vw, 38px);
  --step-3:  clamp(34px, 1.40rem + 3.60vw, 68px);

  /* ── Structure (§5) ── */
  --shell: 1180px;
  --gut:   clamp(18px, 4vw, 40px);
  --bar-h: 66px;

  --s1: 6px;  --s2: 10px; --s3: 16px; --s4: 26px;
  --s5: 42px; --s6: 68px; --s7: 110px;

  --ease: cubic-bezier(0.22, 0.9, 0.24, 1);
}

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

body {
  background: var(--enamel);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--step-0);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Anchors and focus-scroll must clear the fixed bar. */
[id] { scroll-margin-top: calc(var(--bar-h) + 12px); }

img { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input  { font: inherit; color: inherit; }
a { color: inherit; }

:focus-visible { outline: 2px solid var(--ember); outline-offset: 3px; }

.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: var(--gut); }

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

/* Skip link — keyboard users shouldn't tab the header on every page. */
.skip {
  position: absolute; left: var(--gut); top: -60px; z-index: 400;
  background: var(--ember); color: var(--on-ember);
  padding: 12px 18px;
  transition: top 0.15s var(--ease);
}
.skip:focus { top: 12px; }

/* ── Utility type: Archivo narrow, uppercase, tracked (§4) ── */
.u {
  font-family: var(--display);
  font-variation-settings: "wdth" 66;
  font-weight: 600;
  font-size: var(--step--1);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* ── Display type: Archivo wide, uppercase, tight (§4) ── */
.d {
  font-family: var(--display);
  font-variation-settings: "wdth" 120;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 0.98;
  text-transform: uppercase;
  text-wrap: balance;
}

/* ═══ FILM GRAIN ═══════════════════════════════════════════════════════════
   Static. §7 bans ambient background motion, and a crawling grain is that. */
.grain {
  position: fixed; inset: 0;
  z-index: 200;
  pointer-events: none;
  opacity: 0.09;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

/* ═══ HEADER ═══════════════════════════════════════════════════════════════ */
.hdr {
  position: fixed; top: 0; left: 0; right: 0; z-index: 90;
  min-height: var(--bar-h);
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s2) var(--gut);
  background: var(--signboard);
  border-bottom: 2px solid var(--ember);
  transition: background 0.3s var(--ease);
}
body.past-hero .hdr,
body.results-mode .hdr { background: var(--signboard-deep); }

/* The inertia rig. The header must stay outside #viewport: position:fixed
   cannot survive inside a transformed ancestor. Desktop pointers only, so
   #viewport is inert on phones and native scrolling is untouched. */
#page { padding-top: var(--bar-h); }
body.smooth #viewport {
  position: fixed; top: 0; left: 0; width: 100%;
  height: 100vh; height: 100svh;   /* vh first for Safari < 15.4 */
  overflow: hidden;
}
body.smooth #page { will-change: transform; }

/* Header search: hidden on the home hero, present everywhere else. */
.hdr-search {
  flex: 1 1 auto; min-width: 0; max-width: 420px;
  display: flex; align-items: center; gap: var(--s2);
  padding: 7px 12px;
  border: 1px solid var(--signboard-ctl);
  background: color-mix(in srgb, #fff 55%, var(--signboard-deep));
  opacity: 0; visibility: hidden;
  transition: opacity 0.25s var(--ease), visibility 0.25s var(--ease);
}
body.past-hero .hdr-search,
body.results-mode .hdr-search,
body.no-hero .hdr-search { opacity: 1; visibility: visible; }
.hdr-search input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--on-board); font-weight: 300;
  font-size: 16px;   /* never lower: iOS zooms the page on focus below 16 */
}
.hdr-search input::placeholder { color: var(--on-board-mute); }
.hdr-search .u { color: var(--ember); }
/* This is the only pointer-driven way to run a search from the header, so it
   carries a real target size, not the 17px the utility type computes to. */
.hdr-search button {
  color: var(--ember);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  min-width: 44px; min-height: 40px;
  padding: 0 4px;
}

.hdr-nav { margin-left: auto; display: flex; align-items: center; gap: var(--s4); }
.hdr-nav a {
  color: var(--on-board-mute); text-decoration: none;
  padding: var(--s1) 0; border-bottom: 1px solid transparent;
  transition: color 0.15s linear, border-color 0.15s linear;
}
.hdr-nav a:hover { color: var(--ink); border-bottom-color: var(--ember); }

@media (max-width: 760px) { .hdr-nav a.opt { display: none; } .hdr-search { max-width: none; } }

/* ── Lantern lockup (§10, carried forward unchanged) ── */
.logo {
  display: inline-flex; align-items: center; gap: 0.5em;
  text-decoration: none; line-height: 1; flex: 0 0 auto;
  --msg-h: 40px;
}
.logo__mark { height: var(--msg-h); width: auto; overflow: visible; flex: 0 0 auto; }
.logo__word {
  display: flex;
  font-family: var(--display);
  font-variation-settings: "wdth" 100;
  font-weight: 800;
  font-size: calc(var(--msg-h) * 0.52);
  letter-spacing: -0.03em;
  white-space: nowrap;
}
.logo__word span { display: inline-block; }
/* Navy on the pink header: 10.83:1. The letters animate opacity and position
   only, so recolouring them needs no change to the entry animation. */
.logo__word .a { color: var(--navy); }
/* Navy. "whereto.com" reads as one word and only ".au" is coloured. The lighter
   weight on ".com" stays, so it is still subordinate — the separation is
   carried by weight alone rather than by weight and colour. */
.logo__word .b { color: var(--navy); font-weight: 500; }
.logo__word .c { color: var(--ember); }
@media (max-width: 620px) { .logo { --msg-h: 32px; } .logo__word { display: none; } }

/* ═══ HERO ═════════════════════════════════════════════════════════════════ */
.hero {
  position: relative; isolation: isolate;
  background: var(--signboard);
  color: var(--on-board);
  padding: clamp(52px, 9vw, 104px) 0 clamp(46px, 8vw, 92px);
  border-bottom: 2px solid var(--ember);
  overflow: hidden;
}
/* The scrim under this was rebuilt for the blush theme: it now LIGHTENS toward
   pink instead of darkening toward teal, because the type went from cream to
   ink. Slight desaturation stops the photo's own colours arguing with the
   pink — without it the reds in the stall signage clash. */
.hero-photo {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 62%;
  filter: saturate(0.62) brightness(1.06);
}
/* A 2.4:1 frame overflows horizontally on a phone, so only the X component
   applies. Shift right to keep the lit stall in shot. */
@media (max-width: 620px) { .hero-photo { object-position: 64% 50%; } }

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  /* z-index: -1 puts the wash BEHIND the hero's own content and in front of the
     photograph. Without it the sheet paints over the type and the whole hero
     goes to mush — which is exactly what happened when this rule was rewritten
     for the new wash figures and this one declaration was dropped. The colours
     and sizes were all correct; they were simply underneath. */
  z-index: -1;
  /* ── The wash ──────────────────────────────────────────────────────────────
     The ground colour laid over the photograph. Too thin and the type fails;
     too thick and the photograph may as well not be there — which is what
     happened on the blush build, where 89–94% left the picture barely visible.

     Derived, not guessed: against a #151515 darkest-plausible pixel, the muted
     type clears 4.5:1 at 66%. That is the floor, and a floor is not a shipping
     value — the blush build measured 4.47–4.50:1 at its own minimum, which is
     the floor to within rounding rather than a confident pass. So the same
     headroom is added: 74% at the left where the type sits, easing to 70%.

     Both stops are LIGHTER than the blush build's final 76/72, so more of the
     photograph shows here than ever showed on Singapore. */
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--signboard) 74%, transparent) 0%,
    color-mix(in srgb, var(--signboard) 70%, transparent) 78%,
    color-mix(in srgb, var(--signboard) 70%, transparent) 100%);
}

/* MUST stay below the rule above. A media query adds no specificity, so when
   this lived higher up the file the base gradient simply won and phones never
   got the flat wash at all — the right-hand end of the gradient put hero body
   type at 3.12:1 and the small labels at 2.35:1 on every phone. */
@media (max-width: 620px) {
  .hero::after {
    background: color-mix(in srgb, var(--signboard) 76%, transparent);
  }
}

.hero-inner { position: relative; max-width: 940px; margin-inline: auto; padding-inline: var(--gut); }
.hero-thesis, .hero-panel { will-change: transform; }
.hero .brand { color: var(--on-board-mute); }

.hero h1 {
  font-family: var(--display);
  font-variation-settings: "wdth" 120;
  font-weight: 800;
  font-size: var(--step-3);
  letter-spacing: -0.02em;
  line-height: 0.96;
  text-transform: uppercase;
  text-wrap: balance;
  margin-top: var(--s4);
  max-width: 15ch;
}
/* The instruction tier. Sits between the claim and the lede, and as of v24 is
   identical to it — the two lines are one instruction split in half, so the
   second is no longer set quieter. Sentence case, body face: an all-caps
   sentence this long would break §4. */
.hero-sub {
  margin-top: var(--s4);
  max-width: 40ch;
  font-weight: 500;
  font-size: clamp(17px, 1rem + 0.4vw, 21px);
  line-height: 1.32;
  color: var(--on-board);
}
/* Identical to .hero-sub in size, weight, leading and colour. The two lines are
   one instruction split across two sentences, so making the second quieter than
   the first was working against the copy. */
.hero-lede {
  margin-top: var(--s3); max-width: 40ch;
  font-weight: 500;
  font-size: clamp(17px, 1rem + 0.4vw, 21px);
  line-height: 1.32;
  color: var(--on-board);
}
.hero-panel { margin-top: var(--s5); }

/* The lit menu panel on the signboard (§8) */
.panel {
  display: flex; align-items: stretch;
  background: var(--enamel);
  box-shadow: 5px 5px 0 color-mix(in srgb, var(--ink) 22%, transparent);
}
.panel:focus-within { box-shadow: 5px 5px 0 var(--ember); }
.panel label { display: flex; align-items: center; padding-left: var(--s3); color: var(--slate); }
.panel input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  padding: 19px var(--s3);
  font-size: var(--step-1); font-weight: 300; color: var(--ink);
}
.panel input::placeholder { color: var(--slate); }
.panel button.go {
  flex: 0 0 auto; display: flex; align-items: center;
  padding: 0 clamp(18px, 3vw, 30px);
  background: var(--ember); color: var(--on-ember);   /* ink would be 2.16:1 */
  transition: background 0.15s linear, color 0.15s linear;
}
.panel button.go:hover { background: var(--ember-deep); color: var(--on-ember); }
/* White, not ink. The -5px offset draws the ring INSIDE the ember fill, so both
   sides of it are #a62c00 — ink there measures 2.16:1, the same figure this
   file already records four lines up for text on that fill. White is 7.04:1.
   This is the submit button on the hero and the closing band, i.e. the primary
   keyboard path through the homepage. */
.panel button.go:focus-visible { outline-offset: -5px; outline-color: var(--on-ember); }

@media (max-width: 620px) {
  .panel { flex-wrap: wrap; }
  .panel input { padding: 15px var(--s2); font-size: 17px; }
  .panel button.go { width: 100%; justify-content: center; padding: 14px; }
}

.hero-try { margin-top: var(--s3); display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3); align-items: baseline; }
.hero-try .lbl { color: var(--on-board-mute); }
/* These are the only interactive elements sitting directly on the washed
   photograph, so nothing here may depend on the wash. The old border used
   --signboard-ctl (1.97:1 over the photo) and both hover and focus used ember
   (2.75:1) — the underline is the only thing marking these as buttons. */
.hero-try button {
  color: var(--on-board-mute);
  border-bottom: 1px solid var(--on-board-mute);
  transition: color 0.15s linear, border-color 0.15s linear;
}
.hero-try button:hover { color: var(--ink); border-bottom-color: var(--ink); }
.hero-try button:focus-visible { outline-color: var(--ink); }

/* ═══ SECTION FRAME ════════════════════════════════════════════════════════ */
.sec { padding: clamp(52px, 8vw, 96px) 0; }
.sec--sunk { background: var(--enamel-sunk); }

.eyebrow { display: flex; align-items: center; gap: var(--s3); color: var(--slate); }
.eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--enamel-edge); }

.sec h2 { font-size: var(--step-2); margin-top: var(--s4); max-width: 20ch; }
.sec-note { margin-top: var(--s3); max-width: 56ch; font-weight: 300; }

/* ═══ THE STALL PLATE — the signature element (§6) ═════════════════════════ */
.plates { margin-top: var(--s5); }

.plate {
  display: grid;
  grid-template-columns: 54px minmax(0, 1fr);
  background: var(--enamel);
  border: 1px solid var(--enamel-edge);
  transition: transform 0.15s ease-out;
}
.plate + .plate { margin-top: var(--s2); }
.plate:hover { transform: translateX(2px); }

.plate-no {
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 17px;
  background: var(--plate);
  color: var(--ink);
  font-family: var(--display);
  font-variation-settings: "wdth" 66;
  font-weight: 700; font-size: 15px; letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  transition: background 0.15s linear;
}
.plate:hover .plate-no { background: var(--plate-hot); }

.plate-body { padding: var(--s3) var(--s4) var(--s3) var(--s3); min-width: 0; }
.plate-top { display: flex; gap: var(--s3); align-items: flex-start; }

 /* Present only when the business actually has a photograph. The tint is what
    shows while that photo is in flight; if it never arrives the whole slot is
    removed by loadPhotos() and the card becomes type only. There is no
    placeholder illustration by design — see app.js. */
.plate-thumb {
  display: block; flex: 0 0 auto;
  width: 76px; height: 76px;
  border: 1px solid var(--enamel-edge);
  background: var(--enamel-sunk);
  overflow: hidden;
}
.plate-thumb img { width: 100%; height: 100%; object-fit: cover; }

.plate-info { min-width: 0; flex: 1; }

.plate-name {
  font-family: var(--display);
  font-variation-settings: "wdth" 118;
  font-weight: 700;
  font-size: var(--step-1);
  line-height: 1.02;
  letter-spacing: -0.015em;
  text-transform: uppercase;
}
.plate-name a { text-decoration: none; }
.plate-name a:hover { color: var(--ember-deep); text-decoration: underline; text-underline-offset: 3px; }

.plate-cat { color: var(--slate); margin-top: var(--s1); display: block; }

 /* ── RESERVED: offer badge ────────────────────────────────────────────────
    Not rendered anywhere yet. Documented here so the slot is designed rather
    than bolted on later: when a business carries a deal ("20% off the bill"),
    the badge belongs at the START of the meta strip, before the rating — it is
    the one piece of information that changes a decision, and the strip is
    already the line the eye lands on after the name.

    Ember is the site's only saturated colour and is already the CTA; a badge in
    the same colour would compete with "Get directions". Use the plate colour
    with ink type instead: it is unmistakably ours, reads at 8.76:1, and ties
    the offer to the rank plate on the same card.

    .plate-meta .offer {
      background: var(--plate); color: var(--ink);
      padding: 3px 8px; margin-right: 2px;
    }

    Two things to settle before this ships: an offer is a commercial
    relationship, so it must be disclosed as one. The homepage's old "no paid
    placement" row was removed on 9 Aug 2026 precisely because it conflicted
    with the legal pages, so nothing now contradicts a paid badge — but if a
    claim ever returns it must be about RANKING only. And an expired offer is worse
    than no offer, so it needs a validity date from the source, not a flag. */

/* ── The decision line: rating and distance ───────────────────────────────
   Lifted out of the meta strip. 16px against the 22px business name above and
   the 11.7px run below, so it reads as the second tier of three rather than
   competing with the name. The star is 17px because a star glyph reads
   optically smaller than a digit at the same size, and the unit stays at 12px
   because the number is what gets scanned, not the "m". */
.plate-hl {
  display: flex; align-items: center;
  gap: 13px;
  margin: 6px 0 8px;
  font-family: var(--display);
  font-variation-settings: "wdth" 88;
  font-weight: 700;
  font-size: 16px;
  line-height: 1;
  letter-spacing: -0.005em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.plate-hl .hl-star {
  font-style: normal;
  color: var(--turmeric-star);
  font-size: 17px;
  margin-right: 6px;
}
/* Not decoration, so it does not use --enamel-edge: at 1.20:1 that rule is
   invisible to many people and the separation it is credited with would not
   actually happen. --enamel-edge-ctl is 3.91:1. */
.plate-hl .hl-sep { width: 1px; height: 15px; background: var(--enamel-edge-ctl); flex: 0 0 auto; }
.plate-hl .hl-dist .unit {
  font-size: 12px; font-weight: 600; margin-left: 2px; letter-spacing: 0;
}

/* Meta strip — what is left after rating and distance were promoted out:
   open state · review count · price */
.plate-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s1) var(--s3);
  margin-top: var(--s2);
  color: var(--slate);
}
 /* .rat and .dist were promoted out of this strip — see .plate-hl above. */
.plate-meta .open { color: var(--open); }
.plate-meta .shut { color: var(--closed); }

.plate-addr  { margin-top: var(--s2); font-size: 14px; font-weight: 300; color: var(--slate); }
.plate-desc  { margin-top: var(--s2); font-size: 14px; font-weight: 300; color: var(--slate); max-width: 62ch; }
.plate-hours { margin-top: var(--s1); color: var(--slate); }

.plate-acts { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }

.btn {
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 8px 16px;
  border: 1px solid var(--enamel-edge-ctl);
  color: var(--slate);
  text-decoration: none;
  transition: border-color 0.15s linear, color 0.15s linear, background 0.15s linear;
}
.btn:hover { border-color: var(--ink); color: var(--ink); }
.btn--go { background: var(--ember); border-color: var(--ember); color: var(--on-ember); }
.btn--go:hover { background: var(--ember-deep); border-color: var(--ember-deep); color: var(--on-ember); }

@media (max-width: 560px) {
  .plate { grid-template-columns: 38px minmax(0, 1fr); }
  .plate-body { padding: var(--s3) var(--s3) var(--s3) var(--s2); }
  .plate-thumb { width: 60px; height: 60px; }
  .plate-name { font-size: var(--step-0); }
}

/* ═══ RESULTS VIEW ═════════════════════════════════════════════════════════ */
#view-results { display: none; }
body.results-mode #view-home { display: none; }
body.results-mode #view-results { display: block; }

.res-head { border-bottom: 1px solid var(--enamel-edge); background: var(--enamel-sunk); }
.res-head-in {
  max-width: var(--shell); margin-inline: auto;
  padding: var(--s4) var(--gut);
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap;
}
.res-q {
  font-family: var(--display);
  font-variation-settings: "wdth" 118;
  font-weight: 700; font-size: var(--step-2);
  text-transform: uppercase; letter-spacing: -0.02em; line-height: 1;
}
.res-count { color: var(--slate); margin-top: var(--s2); }

/* Where "me" is. One line, one button, a list that stays shut until asked. */
.res-from { position: relative; margin-top: var(--s1); }
.from-lbl { color: var(--slate); }
.from-btn {
  color: var(--ink);
  border-bottom: 2px solid var(--ember);
  padding: 2px 0;
  min-height: 32px;
}
.from-btn:hover { color: var(--ember-deep); }
.from-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
  display: flex; flex-direction: column;
  min-width: 200px;
  background: var(--enamel);
  border: 1px solid var(--enamel-edge-ctl);
  box-shadow: 5px 5px 0 color-mix(in oklch, var(--ink) 14%, transparent);
}
.from-menu[hidden] { display: none; }
.from-opt {
  text-align: left;
  padding: var(--s2) var(--s3);
  min-height: 44px;
  color: var(--ink);
  border-bottom: 1px solid var(--enamel-edge);
}
.from-opt:last-child { border-bottom: 0; }

/* Heading inside the menu. Not a button: there is nothing to click. */
.from-head {
  display: block;
  padding: var(--s2) var(--s3);
  background: var(--enamel-sunk);
  border-bottom: 1px solid var(--enamel-edge-ctl);
  color: var(--slate);
}
/* The way back to GPS, set apart from the areas above it. */
.from-opt--clear { border-top: 1px solid var(--enamel-edge-ctl); color: var(--slate); }
/* Slate on the plate colour is 3.06:1 — this option became harder to read the
   moment it was hovered or focused, and on touch the state sticks after a tap.
   The border-top already sets it apart from the areas above. */
.from-opt--clear:hover,
.from-opt--clear:focus-visible { color: var(--ink); }
.from-opt[aria-current="true"] { background: color-mix(in srgb, var(--plate) 55%, var(--enamel)); }
.from-opt:hover, .from-opt:focus-visible { background: var(--plate); }

.res-tools { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* Required Google attribution. Sits inside the results container, not the site
   footer, because the policy requires it in the same visual container as the
   data. Do not move it, hide it, or drop its opacity. */
/* Sits below the results list, not above it. Policy allows top or bottom so long
   as it is inside the same visual container as the data — so this is a placement
   choice, not a compliance one. Bottom keeps it out of the reading path while
   still being unmissable at the end of the list. */
.gattr {
  /* 880px, not --shell. The results list is 880 wide; using the page shell here
     put the logo 150px out into the left margin, floating clear of the column it
     belongs to. It must read as part of the results container, so it takes the
     results container's width. */
  max-width: 880px;
  margin-inline: auto;
  padding: 0 var(--gut) var(--s7);
  /* Bottom right, stated rather than inherited, so it cannot drift if the
     container changes. Google permits either side: "For a single line of
     content, attribution can be positioned to the right or left." */
  display: flex; align-items: center; justify-content: flex-end;
}
/* Rendered at its native 98x18. Google's brand guidance prohibits distorting,
   recolouring or shrinking the mark below its supplied size, so this carries no
   height override and no opacity. */
.gattr img { width: 98px; height: 18px; }

.photo-credit { margin-top: var(--s2); color: var(--slate); }
.photo-credit a { color: var(--slate); text-decoration: underline; text-underline-offset: 2px; }
.photo-credit a:hover { color: var(--ember-deep); }
.toggle {
  display: inline-flex; align-items: center; gap: var(--s1);
  min-height: 44px; padding: 8px 15px;
  border: 1px solid var(--enamel-edge-ctl);
  color: var(--slate);
  transition: 0.15s linear;
}
.toggle:hover { border-color: var(--open); color: var(--open); }
.toggle[aria-pressed="true"] { background: var(--open); border-color: var(--open); color: #fff; }

/* Single column. The map is gone, and the sidebar's only other widget restated
   the first four results, which are already at the top of the list. A results
   list also reads better at a measure this side of the full shell width. */
.res-body {
  max-width: 880px; margin-inline: auto;
  /* Bottom padding is now carried by .gattr, which follows this block. Leaving
     s7 here as well opened a 110px hole between the last card and the logo. */
  padding: var(--s5) var(--gut) var(--s4);
}

.res-main .plates { margin-top: 0; }

.load-more {
  width: 100%; margin-top: var(--s4);
  min-height: 52px; padding: 15px;
  border: 1px solid var(--ink); color: var(--ink);
  transition: 0.15s linear;
}
.load-more:hover { background: var(--ink); color: #fff; }

/* Loading: one ember sweep, no spinner, no skeletons (§8) */
.loading { display: none; padding: var(--s6) var(--gut); text-align: center; }
body.searching .loading { display: block; }
body.searching .res-body { display: none; }
.loading .bar {
  width: 200px; height: 2px; margin: 0 auto var(--s3);
  background: var(--enamel-edge); position: relative; overflow: hidden;
}
.loading .bar::after {
  content: ""; position: absolute; inset: 0; width: 40%;
  background: var(--ember); animation: sweep 1s linear infinite;
}
@keyframes sweep { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
.loading p { color: var(--slate); }

/* Empty state: direction, not mood (§8) */
.empty { padding: var(--s6) 0; max-width: 46ch; }
.empty h3 { font-size: var(--step-1); }
.empty p { margin-top: var(--s2); color: var(--slate); }
.empty .btn { margin-top: var(--s4); }

/* ── Sidebar ── */
/* ═══ HOME SECTIONS ════════════════════════════════════════════════════════ */
.queries { margin-top: var(--s5); columns: 3; column-gap: var(--s5); }
@media (max-width: 860px) { .queries { columns: 2; } }
@media (max-width: 520px) { .queries { columns: 1; } }

.q {
  display: block; width: 100%; text-align: left;
  break-inside: avoid;
  min-height: 44px; padding: 11px 0;
  border-bottom: 1px solid var(--enamel-edge);
  color: var(--ink); font-weight: 300;
  transition: padding-left 0.15s var(--ease), color 0.15s linear;
}
.q::before {
  content: "\2192";
  font-family: var(--display); font-variation-settings: "wdth" 66;
  font-size: 11px; color: var(--slate); margin-right: var(--s2);
}
.q:hover { color: var(--ember-deep); padding-left: var(--s2); }

.rows { margin-top: var(--s5); border-top: 1px solid var(--enamel-edge); }
.row {
  display: grid; grid-template-columns: 168px minmax(0, 1fr);
  gap: var(--s4);
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--enamel-edge);
  border-left: 2px solid transparent;
  transition: border-left-color 0.15s linear, padding-left 0.15s var(--ease);
}
.row:hover { border-left-color: var(--ember); padding-left: var(--s3); }
.row:hover .row-lbl { color: var(--ink); }
.row-lbl { color: var(--slate); padding-top: 3px; transition: color 0.15s linear; }
.row-body h3 {
  font-family: var(--display); font-variation-settings: "wdth" 116;
  font-weight: 700; font-size: var(--step-1);
  letter-spacing: -0.015em; text-transform: uppercase; line-height: 1.05;
}
.row-body p { margin-top: var(--s2); max-width: 58ch; font-weight: 300; }
@media (max-width: 700px) { .row { grid-template-columns: 1fr; gap: var(--s2); } }

/* ═══ CLOSING BAND ═════════════════════════════════════════════════════════ */
.band {
  background: var(--signboard-deep); color: var(--on-board);
  padding: clamp(52px, 8vw, 96px) 0;
  border-top: 2px solid var(--ember);
}
.band-in { max-width: 820px; margin-inline: auto; padding-inline: var(--gut); position: relative; }
.band h2 { font-size: var(--step-2); max-width: 18ch; }
.band-lede { margin-top: var(--s3); max-width: 46ch; font-weight: 300; color: var(--on-board-mute); }
.band .panel-wrap { margin-top: var(--s4); }
.band-meta { margin-top: var(--s4); display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); color: var(--on-board-mute); }
.band-meta span { display: block; }
.band-meta b { display: block; color: var(--ink); font-weight: inherit; margin-top: 3px; }

/* ═══ FOOTER ═══════════════════════════════════════════════════════════════ */
.ftr {
  background: var(--signboard-deep); color: var(--on-board-mute);
  padding: clamp(38px, 6vw, 62px) 0 var(--s5);
  border-top: 2px solid var(--ember);
}
.ftr-top { display: flex; flex-wrap: wrap; gap: var(--s4) var(--s6); align-items: flex-start; justify-content: space-between; }
.ftr-tag {
  font-family: var(--display); font-variation-settings: "wdth" 116;
  font-weight: 700; font-size: var(--step-1);
  text-transform: uppercase; letter-spacing: -0.015em; color: var(--ink);
}
.ftr-tag em { font-style: normal; color: var(--ember); }
.ftr-links { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); }
.ftr-links a { color: var(--on-board-mute); text-decoration: none; transition: color 0.15s linear; }
.ftr-links a:hover { color: var(--ember); }   /* 5.19:1 on the pink */
.ftr-fine {
  margin-top: var(--s5); padding-top: var(--s4);
  border-top: 1px solid var(--signboard-edge);
  display: flex; flex-wrap: wrap; gap: var(--s1) var(--s4);
  justify-content: space-between;
  color: var(--on-board-mute);
}

/* ═══ LEGAL PAGES ══════════════════════════════════════════════════════════ */
.legal { max-width: 760px; margin-inline: auto; padding: clamp(40px, 7vw, 76px) var(--gut) var(--s7); }
.legal h1 { font-size: var(--step-2); }
.legal h2 {
  font-size: var(--step-1); margin-top: var(--s5);
  padding-top: var(--s3); border-top: 1px solid var(--enamel-edge);
}
.legal p, .legal li { margin-top: var(--s2); color: var(--slate); }
.legal ul { margin-top: var(--s2); padding-left: 20px; }
/* :not(.btn) matters. The 404 page and the legal pages both put a
   <a class="btn btn--go"> inside .legal, and ".legal a" (0,1,1) outranks
   ".btn--go" (0,1,0) — so the button took the link colour as its TEXT while
   keeping ember as its background: #7d2100 on #a62c00, measured 1.43:1.
   Unreadable, and it had been shipping on every legal page. */
.legal a:not(.btn) { color: var(--ember-deep); }
.legal .updated { color: var(--slate); margin-top: var(--s2); }

/* Carried over from the v1 legal templates so the clause markup keeps working. */
.legal .page-badge {
  display: inline-block;
  font-family: var(--display); font-variation-settings: "wdth" 66;
  font-weight: 600; font-size: var(--step--1);
  letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--ink);
  background: var(--plate);
  padding: 5px 10px;
  margin-bottom: var(--s3);
}
.legal h1 { font-family: var(--display); font-variation-settings: "wdth" 118; text-transform: uppercase; }
.legal .effective-date {
  font-family: var(--display); font-variation-settings: "wdth" 66;
  font-size: var(--step--1); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--slate); margin-top: var(--s2);
}
.legal .highlight-box,
.legal .warning-box,
.legal .contact-box {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--enamel-sunk);
  border-left: 3px solid var(--ember);
  color: var(--ink);
}
.legal .warning-box { border-left-color: var(--closed); }
.legal .contact-box { border-left-color: var(--signboard-ctl); }   /* 1.87:1 -> 3.46:1 */
.legal .highlight-box p,
.legal .warning-box p,
.legal .contact-box p { color: inherit; }
.legal strong { font-weight: 500; color: var(--ink); }

/* ═══ TOAST ════════════════════════════════════════════════════════════════ */
#toast {
  position: fixed; left: 50%; bottom: 26px;
  transform: translate(-50%, 20px);
  z-index: 310;
  padding: 12px 20px;
  /* The toast has to stay dark: it appears over the pink header and the pink
     band, and a pink toast on a pink ground is not a toast. */
  background: var(--ink); color: #fff;
  border-left: 3px solid var(--ember);
  opacity: 0; pointer-events: none;
  transition: opacity 0.22s, transform 0.22s;
  max-width: calc(100vw - 40px);
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ═══ MOTION (§7 — Smooth & premium) ═══════════════════════════════════════ */
@keyframes settle {
  0%   { opacity: 0; transform: translateY(-14px); }
  64%  { opacity: 1; transform: translateY(3px); }
  100% { opacity: 1; transform: translateY(0); }
}
.hero-inner { animation: settle 0.6s var(--ease) both; }

.js .r {
  opacity: 0; transform: translateY(18px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  transition-delay: var(--d, 0ms);
}
.js .r.in { opacity: 1; transform: none; }

/* ── Lantern mark: entry + idle sway, the one permitted loop ── */
/* ═══ PARKED: the lantern animation ════════════════════════════════════════
   None of these selectors match anything in this theme. Logo direction A is a
   static mark and the animation was deliberately deferred — it gets treated
   later, in the same family as market's lantern sequence, using
   "Market.com.sg logo animation.zip" as the reference.

   Kept rather than deleted BECAUSE of that decision: this is the timing and
   easing of the sequence being copied, and it is easier to adapt than to
   rediscover. Delete it if the animation is ever abandoned instead.

   Dead until then. Nothing below fires.
   ------------------------------------------------------------------------ */
@keyframes lg-wire   { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; transform: translateX(0); } }
@keyframes lg-ignite { 0% { opacity: 0; transform: scale(.82); } 55% { opacity: 1; } 100% { opacity: 1; transform: scale(1); } }
@keyframes lg-glow   { 0% { opacity: 0; } 30% { opacity: .95; } 100% { opacity: .62; } }
@keyframes lg-breathe{ 0%, 100% { opacity: .40; } 50% { opacity: .78; } }
@keyframes lg-sway1  { 0%, 100% { transform: rotate(-3.4deg); } 50% { transform: rotate(2.6deg); } }
@keyframes lg-sway2  { 0%, 100% { transform: rotate(2.9deg); }  50% { transform: rotate(-3.1deg); } }
@keyframes lg-sway3  { 0%, 100% { transform: rotate(-2.4deg); } 50% { transform: rotate(3.6deg); } }
@keyframes lg-tassel { 0%, 100% { transform: rotate(-5deg); }   50% { transform: rotate(5deg); } }
@keyframes lg-letter { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }

.lg-swing, .lg-body, .lg-tassel { transform-box: fill-box; transform-origin: 50% 0%; }
.logo__mark .lg-wire  { animation: lg-wire .52s var(--ease) both; }
.logo__mark .lg-hooks { animation: lg-wire .52s var(--ease) .07s both; }
.lg-l1 .lg-swing  { animation: lg-sway1 5.4s cubic-bezier(.44,0,.56,1) .56s infinite; }
.lg-l2 .lg-swing  { animation: lg-sway2 6.1s cubic-bezier(.44,0,.56,1) .71s infinite; }
.lg-l3 .lg-swing  { animation: lg-sway3 5.7s cubic-bezier(.44,0,.56,1) .86s infinite; }
.lg-l1 .lg-tassel { animation: lg-tassel 5.4s cubic-bezier(.44,0,.56,1) .71s infinite; }
.lg-l2 .lg-tassel { animation: lg-tassel 6.1s cubic-bezier(.44,0,.56,1) .86s infinite; }
.lg-l3 .lg-tassel { animation: lg-tassel 5.7s cubic-bezier(.44,0,.56,1) 1.01s infinite; }
.lg-l1 .lg-body   { animation: lg-ignite .79s cubic-bezier(.2,.85,.25,1) .26s both; }
.lg-l2 .lg-body   { animation: lg-ignite .79s cubic-bezier(.2,.85,.25,1) .41s both; }
.lg-l3 .lg-body   { animation: lg-ignite .79s cubic-bezier(.2,.85,.25,1) .56s both; }
.lg-l1 .lg-halo   { animation: lg-glow 1.05s ease-out .37s both, lg-breathe 4.6s ease-in-out 1.5s infinite; }
.lg-l2 .lg-halo   { animation: lg-glow 1.05s ease-out .52s both, lg-breathe 5.2s ease-in-out 1.72s infinite; }
.lg-l3 .lg-halo   { animation: lg-glow 1.05s ease-out .67s both, lg-breathe 4.9s ease-in-out 1.95s infinite; }
/* ─── end parked ─────────────────────────────────────────────────────────── */
.logo__word span  { animation: lg-letter .45s cubic-bezier(.2,.85,.25,1) both; }
.logo__word span:nth-child(1){animation-delay:.79s}  .logo__word span:nth-child(2){animation-delay:.83s}
.logo__word span:nth-child(3){animation-delay:.88s}  .logo__word span:nth-child(4){animation-delay:.92s}
.logo__word span:nth-child(5){animation-delay:.97s}  .logo__word span:nth-child(6){animation-delay:1.01s}
.logo__word span:nth-child(7){animation-delay:1.07s} .logo__word span:nth-child(8){animation-delay:1.12s}
.logo__word span:nth-child(9){animation-delay:1.16s} .logo__word span:nth-child(10){animation-delay:1.21s}
.logo__word span:nth-child(11){animation-delay:1.27s}.logo__word span:nth-child(12){animation-delay:1.31s}
.logo__word span:nth-child(13){animation-delay:1.36s}
/* Fourteen, not thirteen. "market.com.sg" is thirteen characters and the
   stagger stopped there; "whereto.com.au" is fourteen, so the last letter
   inherited a zero delay and landed BEFORE every other one. */
.logo__word span:nth-child(14){animation-delay:1.41s}

/* ═══ TOUCH AND REDUCED MOTION ═════════════════════════════════════════════ */

/* 44px minimum on everything tappable. Utility type with no padding computes
   to 17-33px unaided, and most visitors are on a phone. */
@media (any-pointer: coarse) {
  .hdr-nav a,
  .ftr-links a,
  .hero-try button { padding-block: 14px; }
  /* "Home" measured 30px wide and "Terms" 35px — the block padding only ever
     fixed the height of short labels. */
  .ftr-links a { min-width: 44px; text-align: center; }
  /* "Search" is the shortest nav item and measured 41px wide — the block
     padding only ever fixed the height. */
  .hdr-nav a { min-width: 44px; text-align: center; }
  .ftr-links { gap: 0 var(--s4); }
  .hero-try { gap: 0 var(--s3); }

  /* The business name is the primary click-out to Google Maps and computed to
     ~16px tall. It is a link, so pad the block rather than resize the type. */
  /* 13px each side gave 41.3px, not 44. The negative margin cancels the added
     height so the card does not grow. */
  .plate-name a { display: inline-block; padding-block: 14px; }
  .plate-name { margin-block: -14px 0; }

  .hdr-search button { min-height: 44px; }
  /* min-width too: the height was bumped but the width tracks the label, and
     short names ("Bugis") computed to 41.3px — under the 44px floor. */
  .from-btn { min-height: 44px; min-width: 44px; }
  .logo { --msg-h: 44px; }   /* was 40: the lockup is a link and must meet the floor */
  .skip { padding: 14px 18px; }
}

/* Grain gated on hover, not pointer: a touchscreen laptop keeps the texture. */
@media (hover: none) { .grain { display: none; } }

/* Hover sticks after a tap on touch. A plate left shifted with its numeral lit
   reads as "selected", which is not what happened. */
@media (hover: none) {
  .plate:hover { transform: none; }
  .plate:hover .plate-no { background: var(--plate); }
  .q:hover { color: var(--ink); padding-left: 0; }
  .row:hover { border-left-color: transparent; padding-left: 0; }
  .row:hover .row-lbl { color: var(--slate); }
  .btn:hover { border-color: var(--enamel-edge-ctl); color: var(--slate); }
  .btn--go:hover { background: var(--ember); border-color: var(--ember); color: var(--on-ember); }
  .panel button.go:hover { background: var(--ember); color: var(--on-ember); }
  .plate-name a:hover { color: var(--ink); text-decoration: none; }
  .hdr-nav a:hover { color: var(--slate-light); border-bottom-color: transparent; }
  .ftr-links a:hover { color: var(--slate-light); }
  .hero-try button:hover { color: var(--on-board-mute); border-bottom-color: var(--signboard-ctl); }
  .toggle:hover { border-color: var(--enamel-edge-ctl); color: var(--slate); }
  .load-more:hover { background: none; color: var(--ink); }
}

@media (max-width: 620px) {
  .ftr-fine { justify-content: flex-start; }
  .band-meta { gap: var(--s2) var(--s4); }

  /* The header search was measuring 112 x 25 on a phone: too short to tap and
     too narrow to read a typed query back. The nav gives up its space to it,
     since the header is a search bar first and a menu second. */
  .hdr { gap: var(--s2); padding-inline: var(--s3); }
  /* The min-height has to be on the input, not just the wrapper. Padding on the
     wrapper made the form 54px while the actual tap target stayed 25px, and
     tapping the padding focused nothing. */
  .hdr-search { padding: 0 8px 0 10px; }
  .hdr-search input { min-width: 0; min-height: 44px; }
  .hdr-search button { min-height: 44px; }
  body.results-mode .hdr-nav,
  body.past-hero .hdr-nav { display: none; }
  /* Visually hidden, still the input's accessible name. */
  .hdr-search label {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .js .r { transform: none; transition: opacity 0.2s linear; }
  .js .r.in { opacity: 1; }
  .grain { display: none; }
}

/* ═══ ASKING WHERE ═════════════════════════════════════════════════════════
   The two screens that appear in the results view when we have no anchor. They
   are not error states and must not read as any: the visitor has done nothing
   wrong, we simply cannot answer "near you" until we know where you are. */
.ask {
  max-width: 640px;
  padding: var(--s6) 0 var(--s7);
}
.ask-h {
  font-size: var(--step-2);
  color: var(--ink);
  margin-bottom: var(--s2);
}
.ask p {
  color: var(--slate);
  font-size: var(--step-1);
  max-width: 46ch;
}
/* Quieter than the question — this one is temporary and resolves itself. */
.ask--wait .ask-h { color: var(--slate); font-size: var(--step-1); }

/* ── The suburb combobox ────────────────────────────────────────────────────
   Used in two places: the ask screen, and the "Distances from" control above
   the results. One widget, one set of keyboard behaviour, one thing to test. */
.sub-wrap { margin-top: var(--s4); }
.sub-lbl {
  display: block;
  color: var(--slate);
  margin-bottom: var(--s1);
}
.sub-combo { position: relative; max-width: 420px; }
.sub-combo input {
  width: 100%;
  min-height: 48px;                      /* over the 44px floor, not at it */
  padding: 12px 14px;
  background: var(--enamel);
  border: 1px solid var(--enamel-edge-ctl);   /* 3.83:1 — the only visible edge */
  border-radius: 3px;
  font-size: var(--step-0);
  color: var(--ink);
}
.sub-combo input:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
}
.sub-list {
  position: absolute;
  z-index: 60;
  left: 0; right: 0; top: calc(100% + 4px);
  margin: 0; padding: 0;
  list-style: none;
  background: var(--enamel);
  border: 1px solid var(--enamel-edge-ctl);
  border-radius: 3px;
  box-shadow: 0 10px 28px rgba(18, 41, 44, 0.13);
  max-height: 320px;
  overflow-y: auto;
}
.sub-opt {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: 12px 14px;                    /* 44px+ rows: this is a touch target */
  cursor: pointer;
  border-bottom: 1px solid var(--enamel-edge);
}
.sub-opt:last-child { border-bottom: 0; }
/* Both states, because the list is walked by keyboard as often as by mouse and
   aria-selected is the only thing a screen reader is following. */
.sub-opt:hover,
.sub-opt[aria-selected="true"] { background: var(--enamel-sunk); }
.sub-opt b { font-weight: 600; }
/* The state is not decoration. There are Newtowns in three states and five
   Springfields — without it the list is ambiguous. */
.sub-st { color: var(--slate); flex: 0 0 auto; }

/* ═══ THE FALLBACK TIER ════════════════════════════════════════════════════
   Shown when nothing cleared the bar. It has to read as a different answer to a
   different question, not as a short version of the real list — market's
   equivalent was rendered identically to a curated set, which is what made it
   dishonest rather than merely disappointing.

   Nothing here states or implies where the bar sits. */
.tier-note {
  border-left: 3px solid var(--ember);
  padding: var(--s3) 0 var(--s3) var(--s4);
  margin-bottom: var(--s4);
}
.tier-h { font-size: var(--step-1); margin-bottom: 4px; }
.tier-note p { color: var(--slate); }
/* Set back from the page so the tier is visibly a lesser thing, without
   dimming the type — greying out the text would fail contrast to make a point
   that layout can make for free. */
.plates--tier .plate {
  background: var(--enamel-sunk);
  border-style: dashed;
}
