/*
 * The whole look lives in this one file, and the whole identity in the :root
 * block.
 *
 * Brand landed 2026-09-07: "Golden Hour," band composition — ratified by the
 * founder after the three-candidate pass and the composition A/B on the web
 * workbench (record: discovery/launch/03-brand-and-channels.md, amendment of
 * that date). The palette below IS that record; the old borrowed cream-and-
 * denim tokens are gone.
 *
 * The token rollout is DONE (2026-09-14). What landed on index.html first —
 * the ratified TYPE (Zilla Slab display over Instrument Sans body), the radii,
 * the golden hour bar under the header and above the footer, the wordmark, the
 * form and focus treatments — is promoted into the shared base below, so every
 * page that links this file wears the system: /austin, the legal pages,
 * /404, the claim flow and the server-rendered /j page. The homepage is no
 * longer a special case; `body.vision` further down keeps only what is
 * COMPOSITION, not system — its chapter classes, its photographs, its stage
 * grid, and the handful of spacings its composition measures differently.
 *
 * The mark is NOT ratified — the vesica/overlap was the winning candidate's
 * proposal and the founder is unsold. Every production header runs
 * wordmark-only, "Dabl," capitalized (the lowercase-logotype question closed
 * on the same date).
 *
 * Light-only on purpose (D-19): the product has no dark variant anywhere.
 */

/* ---- webfonts (self-hosted) --------------------------------------------- */
/*
 * The two ratified faces, served from /fonts/ rather than the Google Fonts CDN
 * — the amendment's "fonts self-hosted before launch", done 2026-09-08. Both
 * families are OFL 1.1, so redistribution is licence-clean; the per-file source
 * URLs and the licence record live in public/fonts/README.md.
 *
 * LATIN ONLY. The unicode-range on each face is the latin subset copied
 * verbatim from the css2 response, and it is load-bearing twice over: it stops
 * the browser fetching a face for a page that needs none of it, and — since the
 * latin-ext files are deliberately not hosted — it lets a latin-ext codepoint
 * fall through to the next font in the stack instead of rendering from a file
 * that does not contain it. The page's copy is English with typographic
 * punctuation; every character it uses is inside these ranges.
 *
 * Instrument Sans is ONE variable file for all three weights (Google serves the
 * identical URL for 400, 500 and 600), so it is declared once across a weight
 * range. Zilla Slab is static-weight and needs a file each.
 *
 * font-display: swap everywhere, matching what the CDN served: the fallback
 * stacks on --display and --font at :root below are real typefaces, and text
 * readable immediately in Georgia beats text invisible in Zilla.
 */

@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("/fonts/instrument-sans-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The 500 face is hosted but currently unrequested — every Zilla role on the
   site is 600 or 700 (ios-dev audit, 2026-09-14). Kept for future sub-roles;
   browsers fetch only faces a rule actually uses, so it costs no transfer. */
@font-face {
  font-family: "Zilla Slab";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/zilla-slab-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Zilla Slab";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/zilla-slab-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Zilla Slab";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/zilla-slab-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* ---- ground & ink ---- */
  --ground: #FFFDF8;        /* paper white — not the old cream #FBF3E7 */
  --card: #FFFFFF;
  --field: #FBF1E4;         /* the soft field: amber whisper — the map sheet,
                               the peek, the claim steps. The old --tray alias
                               for it was retired at the token rollout. */
  --ink: #211A12;
  --muted: #6E6152;         /* 5.9:1 on ground, 5.4:1 on the field */

  /* ---- accents ----
     Two ambers, and which one you get depends on size, not on taste.
     --accent-bright is the lead and is display-only: 3.2:1 on ground clears
     AA-large (>=24px, or >=18.66px bold) and nothing else. Anything read at
     body size — links, the submit button, a selected chip, form confirmation
     text — wears --accent, which clears 4.5:1. --accent is therefore the
     token every shared component already points at, and it stays the safe
     one by construction. */
  --accent: #C05510;        /* burnt amber, body-safe: 4.62:1 on #fff */
  --accent-bright: #E56A19; /* the lead, display sizes only */
  --accent-soft: #FCEBDC;   /* amber wash: chip hover, soft fills */
  --hairline: #EDE6DA;
  --pine: #1E4D40;          /* the quiet anchor: 9.4:1 — footer links */

  /* ---- the map ----
     A cartographic ramp tuned against each other (land/water figure-ground,
     two edge weights), now derived against the paper-white ground: candidate A
     of the 2026-09-14 ramp pass — the same four values de-yellowed (hue 84→77,
     chroma eased), which is the minimal correction and the founder pick for
     now. A deeper re-derivation (the value-stepped "C" direction, which would
     also move the hardcoded .fabric stroke) stays open. */
  --land: #F3E6D4;
  --water: #B4C9DD;
  --water-edge: #88A6C1;
  --tile-edge: #D8CAB6;

  /* The direction's radii, site-wide since the token rollout. */
  --radius: 18px;
  --radius-small: 12px;
  --radius-photo: 14px;

  /* The ratified type, bound here rather than on one page: Zilla Slab carries
     every display role, Instrument Sans the body. Both are self-hosted above;
     both fall back to real typefaces. */
  --display: "Zilla Slab", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --font: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --map-serif: "Iowan Old Style", Georgia, "Times New Roman", serif;
}

* { box-sizing: border-box; }

html { color-scheme: light only; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 20px 64px;
}

/* ---- header / footer ---------------------------------------------------- */
/* Wordmark alone, "Dabl," capitalized — no mark on any production surface
   until one is ratified (03-brand-and-channels.md, 2026-09-07). The header is
   a block because it holds one child; the golden hour line below it is what
   separates it from the page. */

.site-header {
  display: block;
  padding: 26px 0 0;
}

.site-header a { text-decoration: none; color: inherit; }

/* ---- the signature gesture: the golden hour line -------------------------
   A 2px bar in the lead amber. Every page gets exactly two — under the header
   and above the footer — and they are the only decoration the chassis has. It
   is also the one place the bright amber is allowed at non-type sizes: the bar
   carries no text, so no contrast minimum applies. (The vision page composes a
   third above the founder's letter; that one stays in its own section.) */

.site-header::after,
.site-footer::before {
  content: "";
  display: block;
  width: 72px;
  height: 2px;
  background: var(--accent-bright);
  border-radius: 2px;
}

.site-header::after { margin-top: 18px; }

.wordmark {
  font-family: var(--display);
  font-weight: 700;
  font-size: 26.5px;
  line-height: 1;
  letter-spacing: -0.005em;
}

/* Pine's anchor, and no grey hairline: the golden hour line is the separator,
   so every page closes on warmth rather than on a rule. */

.site-footer {
  margin-top: 56px;
  padding-top: 0;
  border-top: 0;
  font-size: 14px;
  color: var(--muted);
}

.site-footer::before { margin-bottom: 20px; }

.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-bottom: 10px;
}

.site-footer a {
  color: var(--pine);
  font-weight: 500;
  text-decoration: none;
}

.site-footer a:hover { text-decoration: underline; }

.site-footer p { margin: 0; }

/* ---- type --------------------------------------------------------------- */
/* Every heading is Zilla Slab; body copy is Instrument Sans (both bound at
   :root). Zilla is static-weight and 700 is the family's ceiling — the
   headline is pinned there and nothing anywhere asks for 800. Tracking opens
   to -0.005em on the headline because Zilla's flat slabs start touching at a
   tighter setting. The vision page's chapter headings carry their own
   optically measured sizes; these are the chassis' defaults. */

h1 {
  font-family: var(--display);
  font-size: 34px;
  line-height: 1.12;
  letter-spacing: -0.005em;
  font-weight: 700;
  margin: 28px 0 12px;
}

h2 {
  font-family: var(--display);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 40px 0 12px;
}

h3 {
  font-family: var(--display);
  font-weight: 600;
}

p { margin: 0 0 12px; }

.lede { font-size: 19px; }

.muted { color: var(--muted); }

.eyebrow {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 4px;
}

.count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

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

/* ---- cards & forms ------------------------------------------------------ */

.card {
  background: var(--card);
  border-radius: var(--radius);
  padding: 20px;
  border: 1px solid var(--hairline);
  margin: 16px 0;
}

.field {
  width: 100%;
  font: inherit;
  color: inherit;
  background: var(--ground);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-small);
  padding: 12px 14px;
  margin: 6px 0;
}

.field::placeholder { color: #A2947F; }

.field:hover { border-color: #DFD5C4; }

/* A focused field takes the amber on its border as well as in the ring, and
   lifts to paper white — tighter offset than the anchor ring below, because
   the border is doing half the work. */
.field:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
  background: var(--card);
}

a:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Anchors wear this too — Join, Claim this group, Get dabl on the App Store —
   and outside a card an inline <a> collapses to its text and keeps its
   underline. The three declarations that make an anchor sit like a button are
   all no-ops on a real <button>. */
.btn {
  display: block;
  font: inherit;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-small);
  padding: 14px 20px;
  margin-top: 8px;
  cursor: pointer;
  width: 100%;
  transition: background-color 120ms ease;
}

.btn:hover { background: #A9490D; color: #fff; }

/* Ink, not accent, on the wash. The old denim cleared 6.8:1 against its pale
   blue; the burnt amber manages only 3.97:1 against the amber wash, and no
   tint fixes it — #C05510 needs a background at luminance 0.973 (paper white)
   to clear 4.5:1, so the foreground is the only lever. Ink is the safe,
   layout-neutral port; what a secondary button should actually look like in
   this system is still an open question. (.form-done below no longer has the
   problem: it wears the field wash, not the amber one.) */
.btn.secondary {
  background: var(--accent-soft);
  color: var(--ink);
}

.btn:active { transform: translateY(1px); }

@media (prefers-reduced-motion: reduce) {
  .btn:active { transform: none; }
  .btn { transition: none; }
}

.btn[disabled] { opacity: 0.6; cursor: default; }

.form-note { font-size: 13px; color: var(--muted); margin-top: 10px; }

/* The confirmation borrows the letter's field rather than the amber wash: the
   amber is spent on the bar and the button, and a filled amber panel reading
   back at you is one hit too many. */
.form-done {
  background: var(--field);
  color: var(--ink);
  border-radius: var(--radius-small);
  padding: 16px 18px;
  font-size: 16px;
  font-weight: 400;
}

.form-error { color: #9C3B23; font-size: 14px; margin-top: 8px; }

/* Honeypot: hidden from people, present for bots. Not display:none — some
   bots skip invisible fields. */
.hp {
  position: absolute;
  left: -5000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---- group rows & pick lists -------------------------------------------- */
/* The landing page's own group list is gone (the map's drill-down replaced
   it), but the claim flow's group step and every "nothing matches" line still
   wear these. */

.group-list { list-style: none; margin: 0; padding: 0; }

.group-list li { margin: 10px 0; }

.group-row {
  display: block;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 14px 18px;
  text-decoration: none;
  color: inherit;
}

.group-row .name { font-weight: 700; }

.group-row .meta {
  font-size: 14px;
  color: var(--muted);
  margin-top: 2px;
}

.list-empty { color: var(--muted); padding: 12px 2px; }

/* ---- reveal peek (T-0 teaser) ------------------------------------------- */
/* Faces you cannot resolve: the grid is in the app, and this page says so by
   refusing to show it. Blurred, inert, decorative. */

.peek {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 20px 0 24px;
  filter: blur(6px);
  pointer-events: none;
}

.peek-face {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--field);
  flex: none;
}

.peek-face:nth-child(4) { background: var(--accent); }

/* ---- group page --------------------------------------------------------- */

.group-hero { margin-top: 24px; }

.group-hero h1 { margin-bottom: 6px; }

.start-line { font-size: 16px; color: var(--muted); margin-bottom: 2px; }

.count-line { font-size: 19px; margin: 14px 0 0; }

.claim-banner {
  background: var(--field);
  border-radius: var(--radius);
  padding: 16px 20px;
  margin: 20px 0;
}

.claim-banner strong { display: block; margin-bottom: 6px; }

/* The pocket cutout: the landing map cropped to this group's neighborhood.
   group.js draws it; the land/water/creek/fabric rules in the map section
   above are class-scoped, not scoped to #map, so they reach inside here on
   purpose — one neighborhood, drawn the same way in both places. */

#mini-map {
  max-width: 100%;
  height: 200px;
  margin: 18px 0 6px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--land);
}

/* No pocket on the group, or the geometry never arrived. The page reads fine
   without it, so it takes up no room at all. */
#mini-map:empty { display: none; }

.mini-map-svg { display: block; width: 100%; height: 100%; }

.mm-hit {
  fill: var(--accent-soft);
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
}

.mm-dim {
  fill: var(--land);
  stroke: var(--tile-edge);
  stroke-width: 1.2;
  stroke-linejoin: round;
  opacity: 0.55;
}

.mm-pin { fill: var(--accent); stroke: #fff; }

/* The facepile: presence without people. Every circle is blank on purpose —
   the page shows that a group has members, never who they are. */

.presence {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 14px 0 0;
}

.presence .count-line { margin: 0; }

.faces { display: inline-flex; align-items: center; }

.m-face {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--field);
  box-shadow: 0 0 0 2px var(--card);
  margin-right: -5px;
  flex: none;
}

.m-more {
  margin-left: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--field);
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Share is a control, not a second button — Join is the only button here. */
.share-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 14px;
  padding: 6px 0;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
}

.share-link svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.share-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* /austin's share ask sits under the claim CTA and must lose to it on sight:
   muted rather than accent, a size down, and no box of its own. Claiming is
   still the only thing this page asks for in button costume. */
.share-map {
  margin-top: 8px;
  font-size: 14px;
  color: var(--muted);
}

.share-map:hover { color: var(--ink); }

/* ---- the map (the hero) -------------------------------------------------- */
/* Ported from the approved prototype. The SVG is drawn by map.js from
   /map-data.json; every rule below styles a node that file emits. The map
   bleeds past the wrap's gutters on purpose — the radial fade mask is the
   edge treatment, so a hard margin would fight it. */

.city-line { margin-top: 24px; }

.city-line h1 { margin: 4px 0 8px; }

.city-line h1 .num {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

#map-frame {
  margin: 10px -20px 0;
  position: relative;
  min-height: 68px; /* the pill still needs somewhere to sit if the map never draws */
}

/* ---- the search pill ----------------------------------------------------- */
/* A map control, not a form field. It floats on the map's top-left, which is
   open water at every width — the Domain's label is the nearest thing below it
   and clears the pill's deepest reach. The width cap is what keeps its right
   edge off the compass, which the map draws at x 938 of the 1000-unit viewBox:
   93% of the frame less the 12px inset and a margin. */

.map-search {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  width: min(340px, calc(93% - 20px));
}

.map-search-icon {
  position: absolute;
  left: 15px;
  top: 13px;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--muted);
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}

.map-search-input {
  width: 100%;
  font: inherit;
  font-size: 16px; /* below 16 and iOS zooms the whole page on focus */
  color: inherit;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 11px 16px 11px 42px;
  /* The tiles' drop shadow, in CSS. */
  box-shadow: 0 2.5px 8px rgba(92, 74, 51, 0.22);
  -webkit-appearance: none;
  appearance: none;
}

.map-search-input::-webkit-search-cancel-button { -webkit-appearance: none; }

.map-search-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Sits at z-index 2 inside the frame — under the claim bar (6), the scrim (8)
   and the sheet (9), all of which should cover it if they ever coincide. */
.map-suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  box-shadow: 0 2.5px 8px rgba(92, 74, 51, 0.22);
  padding: 6px;
}

.suggest-head {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 8px 10px 4px;
}

.suggest-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 10px;
  cursor: pointer;
}

.suggest-row:hover,
.suggest-row.active { background: var(--accent-soft); }

.suggest-name { font-weight: 600; font-size: 15px; }

.suggest-meta {
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex: none;
}

.suggest-empty { color: var(--muted); font-size: 15px; padding: 10px; margin: 0; }

#map { display: block; width: 100%; height: auto; }

.land { fill: var(--land); }

.fabric {
  fill: none;
  stroke: #E7D8BE;
  stroke-width: 1.1;
  stroke-linejoin: round;
}

.creek {
  fill: none;
  stroke: var(--water-edge);
  stroke-width: 2.4;
  opacity: 0.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.water {
  fill: var(--water);
  stroke: var(--water-edge);
  stroke-width: 2;
  stroke-linejoin: round;
}

.w-label {
  font-family: var(--map-serif);
  font-style: italic;
  font-size: 25px;
  letter-spacing: 0.13em;
  fill: #647FA1;
  text-anchor: middle;
  paint-order: stroke;
  stroke: var(--ground);
  stroke-width: 5px;
  stroke-linejoin: round;
}

.w-label.small { font-size: 21px; }

.pocket { cursor: pointer; }

.pocket .tile {
  stroke: var(--tile-edge);
  stroke-width: 1.6;
  stroke-linejoin: round;
  filter: url(#lift);
  transition: stroke 0.18s ease, opacity 0.18s ease;
}

/* Outline is never used on an SVG shape: Chromium paints it as the path's
   axis-aligned bounding box, which shows up as stray straight lines cutting
   across the drawing. A pocket's focus ring is this stroke, which follows the
   shape. */
.pocket:hover .tile,
.pocket:focus-visible .tile {
  stroke: var(--accent);
  stroke-width: 2.5;
}

/* :focus, not :focus-visible — the sheet's close returns focus to the pocket
   programmatically, which lands as plain :focus, and Chromium's UA sheet
   paints its own `outline: auto` blue ring there (as the path's bounding box,
   i.e. stray lines across the map). :focus covers :focus-visible. */
.pocket:focus { outline: none; }

.pocket.selected .tile { stroke: var(--accent); stroke-width: 3; }

/* Search results: hits keep a ring, everything else steps back. */
.pocket.hit .tile { stroke: var(--accent); stroke-width: 3; }

.pocket.dim .tile { opacity: 0.4; }

#labels { pointer-events: none; }

/* The cream halo that lets a label sit on top of any fill. */
#labels text {
  paint-order: stroke;
  stroke: var(--ground);
  stroke-width: 6px;
  stroke-linejoin: round;
}

/* The pocket labels are set in the body face, and the hosted Instrument Sans
   is a 400–600 variable file: 700 and 800 clamp to 600 and then get a
   synthetic smear on top, which on a 26px tabular number reads as a blur. So
   the map's labels ask for the weight the file actually has. */
.p-name {
  font-family: var(--font);
  font-weight: 600;
  font-size: 17px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  fill: rgba(42, 32, 24, 0.8);
  text-anchor: middle;
}

.p-count {
  font-family: var(--font);
  font-weight: 600;
  font-size: 26px;
  fill: var(--accent);
  text-anchor: middle;
  font-variant-numeric: tabular-nums;
}

/* A dated (or open) pocket is filled with the accent, so its label inverts
   and drops the halo. */
.p-label.dated .p-name { fill: var(--ground); }

#labels .p-label.dated text { stroke: none; }

.p-date {
  font-family: var(--font);
  font-weight: 600;
  font-size: 26px;
  fill: var(--ground);
  text-anchor: middle;
}

.compass text {
  font-family: var(--map-serif);
  font-size: 26px;
  fill: var(--muted);
  text-anchor: middle;
}

.compass path { stroke: var(--muted); stroke-width: 2; fill: none; }

.scalebar path { stroke: var(--muted); stroke-width: 2; fill: none; }

.scalebar text {
  font-family: var(--font);
  font-size: 20px;
  fill: var(--muted);
  text-anchor: middle;
  letter-spacing: 0.04em;
}

/* Load-in: tiles and labels settle in map order (--i is set per pocket). */
@media (prefers-reduced-motion: no-preference) {
  .pocket,
  .p-label {
    opacity: 0;
    animation: settle 0.5s ease-out forwards;
    animation-delay: calc(var(--i) * 55ms + 150ms);
  }

  @keyframes settle {
    from { opacity: 0; transform: translateY(7px); }
    to { opacity: 1; transform: none; }
  }
}

.map-note { font-size: 14px; color: var(--muted); margin: 14px 0 0; }

/* ---- the primary action -------------------------------------------------- */
/* One button, three places: under the hero's city line, at the foot of the
   drill-down sheet, and in the sticky bar. Claiming is the only thing this
   page asks for, so it never appears in a second costume. Weight, radius and
   padding are .btn's — the two are the same control wearing two names, and
   since the token rollout they match. */

.cta {
  display: block;
  width: 100%;
  margin-top: 16px;
  font: inherit;
  font-weight: 600;
  font-size: 17px;
  text-align: center;
  text-decoration: none;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-small);
  padding: 14px 20px;
  cursor: pointer;
}

.cta:hover { color: #fff; }

/* The bar sits under the scrim and sheet (z-index 8 and 9) on purpose: while
   the sheet is up, map.js stands the bar down anyway, and the ordering means a
   mistake there degrades to "covered", not "floating over the dialog". */
.claim-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  background: var(--accent);
  padding: 14px 20px calc(14px + env(safe-area-inset-bottom));
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1), visibility 0s linear 0.24s;
}

.claim-bar.show {
  transform: none;
  visibility: visible;
  transition: transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1), visibility 0s;
}

.claim-bar-link {
  display: block;
  text-align: center;
  text-decoration: none;
  color: #fff;
  font-weight: 600;
  font-size: 17px;
}

.claim-bar-link:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

/* ---- get-updates dialog -------------------------------------------------- */
/* Centred at every width — unlike the sheet, this is not a map gesture, so it
   never wants to be a phone drawer. */

.dialog {
  position: fixed;
  z-index: 9;
  left: 50%;
  top: 50%;
  width: 420px;
  max-width: calc(100vw - 40px);
  max-height: 85vh;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 22px 24px 24px;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, calc(-50% + 12px));
  transition:
    opacity 0.2s ease,
    transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1),
    visibility 0s linear 0.24s;
}

.dialog.open {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, -50%);
  transition:
    opacity 0.2s ease,
    transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1),
    visibility 0s;
}

.dialog h2 { margin: 0 0 6px; }

.dialog p { font-size: 15px; }

.dialog label { display: block; font-size: 15px; margin-top: 10px; }

@media (prefers-reduced-motion: reduce) {
  .claim-bar,
  .claim-bar.show,
  .dialog,
  .dialog.open { transition: none; }
}

/* ---- drill-down sheet ---------------------------------------------------- */

.scrim {
  position: fixed;
  inset: 0;
  background: rgba(42, 32, 24, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
  z-index: 8;
}

.scrim.open { opacity: 1; pointer-events: auto; }

.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9;
  max-width: 640px;
  margin: 0 auto;
  background: var(--card);
  border-radius: var(--radius) var(--radius) 0 0;
  border: 1px solid var(--hairline);
  border-bottom: 0;
  padding: 14px 20px calc(22px + env(safe-area-inset-bottom));
  transform: translateY(105%);
  transition: transform 0.26s cubic-bezier(0.2, 0.9, 0.3, 1);
  max-height: 76vh;
  overflow-y: auto;
}

.sheet.open { transform: none; }

.grab {
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: var(--hairline);
  margin: 0 auto 14px;
}

/* The sheet's title is the one place a heading goes heavier than the chassis'
   600 — 700 is Zilla's ceiling, and a drawer wants a firmer first line. */
.sheet h2 {
  font-size: 23px;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 2px;
}

.sheet h3 {
  font-size: 15px;
  font-weight: 700;
  color: var(--muted);
  margin: 18px 0 6px;
}

.sheet .opens {
  color: var(--accent);
  font-weight: 700;
  font-size: 15px;
  margin: 0 0 10px;
}

.close {
  position: absolute;
  top: 12px;
  right: 14px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 0;
  background: var(--ground);
  color: var(--muted);
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}

/* Namespaced: the claim flow owns .chip/.chips for its tappable place
   suggestions, and these are read-only numbers. Same shape, different job. */
.sheet-chips { display: flex; gap: 8px; margin: 10px 0 4px; flex-wrap: wrap; }

.sheet-chip {
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 14px;
  font-weight: 700;
  border-radius: 999px;
  padding: 6px 12px;
  font-variant-numeric: tabular-nums;
}

.sheet-chip.plain { background: var(--field); color: var(--ink); font-weight: 600; }

.sheet-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 12px 14px;
  margin: 8px 0;
  text-decoration: none;
  color: inherit;
}

.sheet-row .g-name { font-weight: 700; font-size: 16px; }

.sheet-row .g-meta {
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.sheet-row .g-chevron { color: var(--muted); font-size: 22px; flex: none; }

/* Tapped, waiting on the group page's function to wake up. The row steps back
   and its count line becomes the progress line — the treatment the old group
   list carried, scoped to the sheet row that inherited the same cold start. */
.sheet-row.loading { opacity: 0.55; }

.sheet-row.loading .g-meta { color: var(--accent); font-weight: 700; }

.sheet-row.loading .g-meta::after {
  content: "";
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-left: 8px;
  border: 2px solid var(--accent);
  border-top-color: transparent;
  border-radius: 50%;
  vertical-align: -1px;
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  /* Still a ring, just not a turning one. */
  .sheet-row.loading .g-meta::after {
    animation: none;
    border-top-color: var(--accent);
    opacity: 0.4;
  }
}

.sheet-note { color: var(--muted); font-size: 15px; margin: 8px 0; }

.suggest { color: var(--muted); font-size: 15px; margin: 8px 0 0; }

.suggest a { color: var(--accent); font-weight: 600; }

.close:focus-visible,
.cta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .sheet { transition: none; }
  .scrim { transition: none; }
}

/* ---- details (collapsible cards) ---------------------------------------- */

details.card > summary {
  cursor: pointer;
  font-weight: 700;
  list-style: none;
}

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

details.card > summary::after { content: " +"; color: var(--muted); }

details[open].card > summary::after { content: " –"; }

/* ---- small screens ------------------------------------------------------ */

@media (max-width: 420px) {
  h1 { font-size: 29px; }
  .wrap { padding: 0 16px 56px; }
  #map-frame { margin: 10px -16px 0; }
}

/* ---- desktop ------------------------------------------------------------ */
/* Everything below this line is additive and lives in one block: the site is
   mobile-first and the phone layout above is the contract. At this width the
   single column wasted both margins and pushed the map — the whole point of
   the page — under the fold, so the rail scrolls and the map stays put.
   `.landing` scopes the grid to the home page; .wrap, main and .lede are
   shared with /invite, the legal pages and the server-rendered group pages,
   which stay one column. */

@media (min-width: 1024px) {
  h1 { font-size: 40px; }

  .lede { font-size: 20px; }

  .landing .wrap { max-width: 1120px; }

  /* Header and footer are siblings of main inside .wrap, so they already span
     the full width — only main becomes the two-column grid. Every rail item
     auto-flows down column 1 in DOM order; the map is pinned to column 2.
     No row-gap: vertical rhythm still comes from the existing margins. */
  .landing main {
    display: grid;
    grid-template-columns: 440px minmax(0, 1fr);
    column-gap: 56px;
    align-items: start;
  }

  .landing main > * { grid-column: 1; }

  /* The span has to outrun the rail's item count for the map's grid area to
     be the full height of the grid — that area is what sticky travels inside.
     The rail is six items today; thirty is headroom, and empty rows are zero
     height. */
  .landing .map-col {
    grid-column: 2;
    grid-row: 1 / span 30;
    align-self: start;
    position: sticky;
    top: 24px;
  }

  /* The mobile full-bleed is a phone treatment — inside a column it would just
     overlap the rail. The frame takes the height budget as its width and
     centres itself, so its box is exactly the map's box: that is what keeps
     the search pill's inset anchored to the map's corner rather than floating
     in the empty column beside it.

     Height budget first because the SVG carries an aspect ratio (viewBox
     1000x1478) but no intrinsic size, so max-height alone will not drive the
     width. max-height stays as the backstop. */
  .landing #map-frame {
    width: min(100%, (100vh - 96px) * 0.676);
    margin: 0 auto;
  }

  .landing #map {
    width: 100%;
    height: auto;
    max-height: calc(100vh - 96px);
  }

  .landing .map-note { text-align: center; }

  /* The sheet stops being a sheet: a centred card, radiused all round, that
     fades and lifts instead of sliding up from an edge that is no longer
     near it. The delayed `visibility` is what keeps the closed card out of
     the tab order without cutting the fade-out short. */
  .sheet {
    top: 50%;
    bottom: auto;
    left: 50%;
    right: auto;
    margin: 0;
    width: 480px;
    max-width: calc(100vw - 48px);
    max-height: 80vh;
    border-radius: var(--radius);
    border-bottom: 1px solid var(--hairline);
    padding: 22px 24px 24px;
    opacity: 0;
    visibility: hidden;
    transform: translate(-50%, calc(-50% + 12px));
    transition:
      opacity 0.2s ease,
      transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1),
      visibility 0s linear 0.24s;
  }

  .sheet.open {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, -50%);
    transition:
      opacity 0.2s ease,
      transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1),
      visibility 0s;
  }

  /* A drag handle on a centred card is a lie about how it closes. */
  .sheet .grab { display: none; }

  /* The hero's claim button never leaves the rail here, so the bar has no
     job — and a fixed bar across a 1120px page reads as an ad. */
  .claim-bar { display: none; }

  /* Group pages are not .landing and get no grid — the cutout just earns a
     little more room. */
  #mini-map { height: 240px; }

  /* The base reduced-motion rule sits earlier in the file, so the sheet
     transitions declared just above would win it back. Restate it. */
  @media (prefers-reduced-motion: reduce) {
    .sheet,
    .sheet.open { transition: none; }
    .scrim { transition: none; }
  }
}

/* ---- vision page (body.vision) ------------------------------------------ */
/*
 * The stage-indexed homepage (02-web-launch-surface.md, amendment 2026-09-06),
 * wearing the identity ratified 2026-09-07: "Golden Hour," band composition,
 * Zilla Slab over Instrument Sans. The record is the amendment at the end of
 * discovery/launch/03-brand-and-channels.md; the candidate this section is
 * productionized from is discovery/launch/direction/type-zilla.html, and the
 * pages it was chosen against are its siblings there (the audition pages were
 * archived out of public/ once this shipped).
 *
 * Everything below is scoped to `body.vision`, and since the token rollout
 * (2026-09-14) it is COMPOSITION ONLY. The header padding, the wordmark, the
 * golden hour bar, the footer, the radii, .field, .btn, .form-done, .form-error
 * and the focus rings were the seam between the old chassis and the new system,
 * and they are gone from here: they live in the shared base above and every
 * page wears them. What is left is what only this page's composition asks for
 * — its chapters, its photographs, its stage grid, its letter and its door,
 * plus the handful of spacings this composition measures differently (the
 * form's field margin, the button's drop, the footer's tighter approach).
 *
 * TYPE. The @font-face blocks live at the top of this file and the two faces
 * are bound to --display / --font at :root, so every page renders them. What
 * is this page's own is the SIZING: display roles are set at OPTICAL PARITY
 * rather than nominal size. Zilla Slab 700 renders an x-height
 * of 0.451/em against the audition reference's 0.440, so the whole display
 * scale carries a 0.976 correction and the page's internal hierarchy is the
 * composition's exactly. That is why the sizes below are odd numbers
 * (62.5px, 25.5px, 29.5px, 26.5px) — they are measured, not chosen. Zilla is
 * static-weight, so every display weight here is 500/600/700 exactly; 700 is
 * the family ceiling and the headline is pinned there.
 *
 * COLOUR. The lead amber appears at display size only — the pivot, and the
 * signature rule. Everything read at body size takes --accent, the deep
 * amber that clears 4.5:1. Three amber rules on the whole page (under the
 * header, above the letter, above the footer) are its only decoration.
 *
 * The mark is unratified and there is none: the header is the wordmark alone.
 *
 * PHOTOGRAPHS. Three <img> nodes, rotating together as a bundle (the ratified
 * pass-4 set, 2026-09-13): index.html ships hero-coffee plus pair set B, and an
 * inline table swaps the whole bundle — hero-gym / hero-dogs / hero-trail in
 * the band, and with trail, pair set A. So eight files pass through three
 * slots, and every one of them needs crops here. Crops are keyed by filename
 * rather than by slot, which is what lets the swap be a two-line src
 * assignment.
 *
 *   band, phone (4/3) hero-coffee | hero-gym | hero-dogs | hero-trail
 *   band, stage (5/3) the same four
 *   pair wide (4/3)   set A pair-zilker-picnic · set B pair-zilker-dusk  }  >=900
 *   pair narrow (4/5) set A pair-floatilla     · set B pair-springs      }  only
 *
 * THE PAIR IS DESKTOP-ONLY (the mobile pass, M3 ratified 2026-09-14; log in
 * discovery/launch/direction/notes.md). The phone used to carry all three in a
 * snap-scrolling edge-bleed strip; it now shows the hero alone, full-bleed at
 * 4:3, and the two pair frames are display:none below 900 — not shrunk, not
 * reordered, not shown. Every photograph on a phone is a toll paid in scroll
 * before the argument resumes, and only one of the three was earning it: the
 * strip put a gallery between the pivot and the beats and emptied the back half
 * of the page.
 *
 * display:none buys the LAYOUT back but not the bytes: measured 2026-09-14,
 * Chrome 152 still fetches both hidden files on a 500px viewport, because an
 * image with no layout box has nothing for the lazy observer to defer against.
 * The markup keeps loading="lazy" anyway and carries the note; the transfer is
 * a known, unchanged cost (the strip downloaded all three too).
 *
 * So the page has two compositions. The phone: headline → pivot → one
 * full-bleed band → beats + mantra → one continuous warm wash carrying the
 * letter and the door, with the footer back on cream under it. The stage at
 * 900: the twelve-column asymmetric composition, band and pair and all, exactly
 * as it has been. 900px is still the single breakpoint, and everything above
 * this line is the phone.
 */

body.vision {
  /* The type and the radii are :root's since the token rollout; nothing is
     rebound here. */
  /* The phone's hero band and closing wash are both 100vw inside a padded
     column. This is the guard that stops a sub-pixel rounding — or a browser
     drawing classic scrollbars, where 100vw counts the scrollbar and the text
     column does not — from turning that into a horizontal scroll. Set on the
     body rather than on html so it stays scoped to this page: with html's own
     overflow still visible, the body's value propagates to the viewport, which
     is exactly the element that must not scroll sideways. */
  overflow-x: hidden;
}

/* ---- stage --------------------------------------------------------------- */

/* The closing wash carries its own bottom padding on the phone and the footer
   sits on cream under it, so this is the page's last breath and nothing more.
   The >=900 block restates .wrap's padding in full. */
body.vision .wrap { padding-bottom: 56px; }

/* Flex, not block, so the zone rhythm below is exact — flex items do not
   collapse margins with each other or out of the container, and the phone's
   two-tier spacing is stated in margins throughout. DOM order IS reading order
   (headline → hero → beats → letter → ask), so nothing is reordered: the
   `order` values this rule used to carry came off with the photo strip
   (2026-09-14), which was the one block that had to travel. At 900 every block
   is placed on the grid explicitly and order would not have mattered anyway. */
body.vision main {
  display: flex;
  flex-direction: column;
  padding-top: 8px;
}

/* Rhythm is two-tier on the phone: ~88–92px between zones (hero → beats,
   mantra → close) and ~34–48px within one (header → headline, pivot → hero).
   Every chapter states its own, so there is no shared .chapter default to
   reason around; the >=900 block restates all four. */
body.vision .ch-feeling { margin-top: 34px; }

/* ---- the third golden hour line ------------------------------------------
   The chassis draws two on every page — under the header, above the footer
   (see the shared header/footer section). This page composes a third above the
   founder's note, and that one is this page's alone: it belongs to the letter,
   not to the furniture. Same 72×2 bar in the lead amber. */

body.vision .v-note::before {
  content: "";
  display: block;
  width: 72px;
  height: 2px;
  background: var(--accent-bright);
  border-radius: 2px;
}

/* ---- footer -------------------------------------------------------------- */
/* Everything else about the footer is the chassis'. Only the approach is this
   composition's: the closing band carries its own 72px of bottom padding, so
   this margin is the gap after a ground change rather than the shared 56px
   zone break. */

body.vision .site-footer { margin-top: 40px; }

/* ---- chapter 1: the feeling ---------------------------------------------- */
/* Zilla Slab at 700 — its heaviest, and the whole reason the headline is
   pinned there. Low contrast, flat unbracketed slabs, a narrow fit, so it
   sets more words per line and reads cooler and more technical than a
   didone. Tracking opens to -0.005em because those flat slabs start touching
   at a tighter setting; it is stated in em, so it survives the optical
   scaling above untouched. */

body.vision .ch-feeling h1 {
  font-family: var(--display);
  font-size: clamp(35px, 4.69vw, 62.5px);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.005em;
  text-wrap: balance;
  margin: 0;
  color: var(--ink);
}

/* The one line every visitor sees identically under the rotating headline, and
   the page's single largest hit of amber. Upright — an italic here would tip
   the whole page back toward the nostalgia the direction is escaping. At
   25.5px/600 it is large text (>=24px), so the lead amber's 3.2:1 clears
   AA-large; this is the one body-copy-adjacent place the bright amber is
   used, and it is only legal because of that size. */
body.vision .v-pivot {
  font-family: var(--display);
  font-size: 25.5px;
  font-weight: 600;
  font-style: normal;
  line-height: 1.25;
  letter-spacing: -0.005em;
  color: var(--accent-bright);
  margin: 20px 0 0;
}

/* ---- chapter 2: what it is ----------------------------------------------- */

/* A zone break: the band ends and the argument resumes. 88px on the phone, 96
   on the stage. */
body.vision .ch-what { margin-top: 88px; }

body.vision .v-beat {
  margin: 0 0 22px;
  font-size: 17px;
  line-height: 1.55;
  max-width: 46ch;
}

/* Instrument Sans 600 — the lead does the emphasis on weight alone, inline,
   never on its own line and never in colour. */
body.vision .v-beat strong {
  font-weight: 600;
  color: var(--ink);
}

/* The mantra closes the beats from inside their own column — left-aligned with
   them, never centred, never floating between sections. Quiet, and deliberately
   not a heading: it is the shape of the product stated by what it leaves out. */
body.vision .v-mantra {
  margin: 28px 0 0;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.09em;
  color: var(--muted);
}

/* The divider's pull-line styles were removed with the slot (founder,
   2026-09-14; copy-lines.md, "Divider slot — removed"). The slot is empty at
   vision stage and returns at campaign stage carrying a real Austinite's
   voice, which will want its own treatment — not this one. The beats' zone
   margin below already carries the handoff to the letter. */

/* ---- chapters 3 and 4: the close ------------------------------------------
 * On the phone these two chapters are ONE object: a single full-bleed --field
 * wash running from above the letter to the footer, holding the letter bare on
 * the wash and then the door. Ratified 2026-09-14 (M3; log in
 * discovery/launch/direction/notes.md).
 *
 * THE DIAGNOSIS IT FIXES. The letter used to be a rounded --field card, then a
 * cream gap, then the ask's full-bleed --field band: the same warm colour
 * arriving twice with a stutter of paper between. The page changed ground,
 * changed back, and changed again to say one thing, and the letter's card edge
 * read as an ending — a false one, with two more movements after it. Merged,
 * the shift happens once and does not shift back until the footer, and the
 * letter and the ask read as the one act they always were: Nathan speaks, then
 * Nathan asks.
 *
 * Full-bleed by the same mechanics as the hero band: 100vw, negative inline
 * margins that cancel .wrap's side padding at any width, and padding-inline set
 * to the exact inverse so every line inside lands back on the text column's own
 * left edge. The zone break above it is cream — the wash's own top edge is the
 * arrival, so it needs no rule and no gap to announce itself — and the gap
 * between the two objects is padding INSIDE the band, never a margin between
 * two washes, so the surface cannot break.
 *
 * At 900 the .v-close wrapper is display:contents and this whole treatment is
 * off: chapters 3 and 4 are grid items again, the letter is a card, the ask is
 * the stage band. See the >=900 block.
 */

body.vision .v-close {
  margin-top: 92px;
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding: 40px calc(50vw - 50%) 72px;
  background: var(--field);
}

/* The mailto inside the band goes pine at both widths: on the wash, body-size
   amber cannot reach 4.5:1 and the foreground is the only lever; inside the
   white card it is simply the colour the desktop band has always used. 8.6:1
   on the field. */
body.vision .v-close a { color: var(--pine); }

/* The band's own 40px of top padding is the letter's entrance. */
body.vision .ch-austin { margin-top: 0; }

/* The letter is the one warm surface — and on the phone the wash IS that
   surface, so the letter loses its box and keeps only its type: no card, no
   border, no radius, no padding of its own. The bar above and the signature
   below are the whole of its structure. Set in the sans, not the serif: a serif
   note card is the single most nostalgic move available here, and the whole
   point of this direction is warmth without the antique. */
body.vision .v-note {
  font-size: 17px;
  line-height: 1.6;
}

/* In flow rather than absolutely placed: with the card gone there is no box to
   hang the rule off, and a bottom margin states the same 24px more plainly.
   The >=900 block puts it back on the card's outside edge. */
body.vision .v-note::before { margin-bottom: 24px; }

body.vision .v-note p { margin: 0 0 16px; }

body.vision .v-sig {
  font-family: var(--display);
  font-weight: 600;
  font-size: 18.5px;
  font-style: normal;
  margin: 24px 0 2px;
}

body.vision .v-sig-role {
  font-family: var(--font);
  font-size: 14px;
  color: var(--muted);
  margin: 0;
}

/* ---- the ask: the door ----------------------------------------------------
 * On the phone the whole chapter is the card — one raised white object on the
 * wash, and the only rounded inset on it, which is the point: the one thing
 * lifted off the warm ground is the thing you are being asked to use.
 *
 * WHY THE ASK IS INSIDE IT. The previous cut put "Austin, first." and the ask
 * paragraph on the bare wash between the signature and the form card, which
 * read as a third movement — the letter ended, something else began, and the
 * form arrived after it as a fourth. The problem was never the gap; it was that
 * the band held three objects where the act has two. The title, the ask and the
 * mailto are not a section of their own, they are what you say at the door
 * before you hold it open. Desktop has always known this (at 900 the ask sits
 * beside the form as one unit), and the single column says it the only way it
 * can: the ask is inside the card, above the first label.
 *
 * So the band holds exactly two objects, the letter and the door, separated by
 * the page's full zone break (92px) with the wash continuous beneath it.
 * Hierarchy inside the card carries what the wash used to: the title keeps its
 * section-opening presence at 27px display, the ask runs at body size, and a
 * 32px break under the mailto lands before the Email label so preamble and
 * fields read as two parts of one object.
 */

body.vision .ch-ask {
  margin-top: 92px;
  background: var(--card);
  border: 1px solid #E4D9C6;
  border-radius: var(--radius);
  padding: 22px;
}

/* The card's title. Fixed rather than the stage's clamp — on the phone it is
   measured against the card's 22px padding, not against the viewport, and the
   clamp bottomed out at 23.5px there anyway, which read as a form legend rather
   than as the card's title. */
body.vision .v-lead {
  font-family: var(--display);
  font-size: 27px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.008em;
  margin: 0 0 14px;
}

/* On the phone the form has no box of its own: the section around it is the
   card, and a second border inside the first would read as a panel in a panel.
   The >=900 block gives the box back, because there the two are separate
   columns of the band. */
body.vision .v-form-card {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin-top: 0;
}

/* Same flavour as the claim flow's step labels, without importing claim.css. */
body.vision .v-form-card label {
  display: block;
  font-size: 15px;
  font-weight: 600;
  margin-top: 16px;
}

body.vision .v-form-card form > label:first-child { margin-top: 0; }

/* The field's own treatment — placeholder, hover, focus — is the chassis'. This
   margin is not: it is the gap under this form's labels. */
body.vision .field { margin: 7px 0 0; }

body.vision .v-textarea {
  line-height: 1.45;
  min-height: 88px;
  resize: vertical;
}

/* The chips are this page's control, so they carry the shared ring themselves. */
body.vision .v-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Role chips: single-select, never empty — "Just keep me posted" ships
   preselected in the markup (2026-09-16). app.js keeps the
   state; aria-pressed is the source of truth in the DOM. Outline-ink at rest
   — the hairline border the rest of the site uses disappears against this
   ground — and amber-filled when selected. */
body.vision .v-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 18px 0 4px;
}

body.vision .v-chip {
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink);
  border-radius: 999px;
  padding: 9px 16px;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

body.vision .v-chip:hover { background: var(--accent-soft); }

body.vision .v-chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

body.vision .v-chip[aria-pressed="true"]:hover { background: var(--accent); }

/* The button itself is the chassis'. Only its drop is this card's: the send
   sits further from the last field here than the shared 8px. */
body.vision .btn { margin-top: 22px; }

/* Same, for the error line: colour and size are shared, the gap is the card's. */
body.vision .form-error { margin: 10px 0 0; }

/* The bottom margin is the within-card break on the phone: preamble above,
   fields below. It collapses to nothing useful on the stage, where the ask is
   its own column, so the >=900 block takes it back off. */
body.vision .v-mailto { font-size: 15px; margin: 18px 0 32px; }

/* ---- the photographs ------------------------------------------------------
 * The strip is gone (2026-09-14). Both containers are display:contents at every
 * width now: .photo-strip holds no box, so its three <img> children are laid
 * out by whatever is around them — flex items in main's column on the phone,
 * grid items on the stage — and .photo-pair likewise. That is what lets one set
 * of <img> nodes be a single band on a phone and a band plus an offset pair on
 * a desktop without duplicating a node or loading a file twice.
 *
 * On the phone only the hero renders. The pair is display:none below 900 (see
 * the section preamble: lazy + display:none means those two files are never
 * fetched), and the hero is the full-bleed band directly under the pivot.
 */

body.vision .photo-strip,
body.vision .photo-pair { display: contents; }

/* Shared image treatment. The aspect and the radius here are the PAIR's — they
   are the values the narrow pair slot still runs on at 900, where it takes no
   override of its own — and the hero overrides both below. */
body.vision .photo-strip img {
  display: block;
  width: 100%;
  /* height:auto so the HTML height attribute yields to aspect-ratio —
     without it the attribute wins and the image renders attribute-tall. */
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-photo);
}

/* THE PAIR IS DESKTOP-ONLY. Not shrunk into a miniature pair and not reordered:
   at phone scale two side-by-side frames are illegible as photographs, and one
   frame beside the other at unequal size reads as an accident rather than as a
   composition. Below 900 they do not render and are not in the accessibility
   tree either — the phone's page genuinely has one photograph on it. (It still
   downloads them; see the section preamble.) The >=900 block turns them on. */
body.vision .photo-pair img { display: none; }

/* ---- the hero band, full bleed on the phone -------------------------------
   Edge to edge of the viewport, no radius, no caption, no frame — the picture
   IS the band, the same gesture the stage makes at 900 with the corners back
   on. The negative inline margin cancels .wrap's side padding at any width (the
   wrap tightens 20px → 16px at 420, and calc(50% - 50vw) follows it without a
   second rule, which is why the strip's old 420px block is gone).

   PROVISIONAL 4:3, pending the mobile-window ruling. It is deliberately neither
   the stage band's 5/3 nor the retired strip's 4/5: at 390 a 5/3 crop stands
   234px tall and reads as a letterbox, and 4/5 pushes the beats off the fold.
   4:3 is the holding shape; the crops below are transcribed into it from the
   ratified pass-4 rects rather than re-eyeballed. */
body.vision .photo-strip > .hero-photo {
  margin-top: 44px;
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  aspect-ratio: 4 / 3;
  border-radius: 0;
}

/* Crops are per file, not per slot, so a rotation swap needs no other change:
   whichever file the inline table puts in a slot, its own rule follows it.
   All eight are stated here and the 900px block below overrides only where a
   desktop slot changes the aspect (the band's 5/3, the pair's wide frame at
   4/3).

   THE FOUR HEROES, AT THE PHONE BAND'S 4:3. Each of these is the ratified
   pass-4 band rect (photo-manifest.md, "crop rects") re-solved for 4:3 rather
   than re-eyeballed: hold the rect's centre, widen the window to 4:3, and read
   off y0 / (H − h). They are no longer the old strip's provisional 4/5
   holdings.

     hero-coffee  3000×2400 source; 4:3 leaves 150px of trim against the band's
                  300, and the band's centre lands dead middle. 50%.
     hero-gym     4000×6000, the severe one. The ratified band is "band A" —
                  the class itself: the wall of flags, the instructor, the rig
                  (y0 1450, h 2400, centre 2650). At 4:3 the window is 3000 tall
                  of a 3000 trim, so holding that centre gives y0 1150 → 38%.
                  Two points up the frame from the band's 40%, same class in it.
     hero-dogs    2860×2210. The band sits near flush-bottom (91%) on the bench,
                  the dogs and the grass; at 4:3 only 65px of trim is left and
                  the ratified centre is below what the window can reach, so it
                  goes flush: 100%.
     hero-trail   4000×3000 — 4:3 native, so at this aspect NOTHING is cropped
                  and this value renders no differently from any other. It is
                  written as the band's ratified 100% rather than left blank so
                  the file's one crop ruling is stated in both places.

   The mobile fork that had no founder ruling (hero-trail parked flush-left at
   4/5) is closed by the aspect change: there is no horizontal trim to rule on. */
body.vision .photo-strip img[src$="hero-coffee.jpg"] { object-position: 50% 50%; }
body.vision .photo-strip img[src$="hero-gym.jpg"]    { object-position: 50% 38%; }
body.vision .photo-strip img[src$="hero-dogs.jpg"]   { object-position: 50% 100%; }
body.vision .photo-strip img[src$="hero-trail.jpg"]  { object-position: 50% 100%; }
/* The chapter-2 pair, set A (rides with trail) then set B (everything else).
   These are ≥900-only now — the pair does not render below it — but they stay
   here rather than inside the media block because the narrow slot's aspect
   (4/5) is stated here too, and a crop should sit with the frame it is cut
   for. Wide frames crop left-to-right, tall frames top-to-bottom. */
body.vision .photo-strip img[src$="pair-zilker-picnic.jpg"] { object-position: 12% 50%; }
body.vision .photo-strip img[src$="pair-floatilla.jpg"]     { object-position: 50% 50%; }
body.vision .photo-strip img[src$="pair-zilker-dusk.jpg"]   { object-position: 8% 50%; }
body.vision .photo-strip img[src$="pair-springs.jpg"]       { object-position: 50% 50%; }

/* ---- motion -------------------------------------------------------------- */
/* One fade, on page load, on the hero type. Nothing here is scroll-triggered. */

@keyframes v-rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

body.vision .ch-feeling { animation: v-rise 320ms ease-out both; }

@media (prefers-reduced-motion: reduce) {
  body.vision .ch-feeling { animation: none; }
  /* The button's own transition is stood down by the shared rule earlier in
     this file; the chips are this page's. */
  body.vision .v-chip { transition: none; }
}

/* ---- 900px: the asymmetric stage ------------------------------------------
 * The one breakpoint. Everything above this line is the phone page; everything
 * below composes it onto a twelve-column, 1060px stage where no chapter is
 * centred and no two columns match. Every block is placed by explicit
 * grid-column / grid-row, so DOM order and visual order are stated rather than
 * implied — and so the photo strip can dissolve (display:contents) and hand its
 * three images to two different chapters without any being duplicated.
 *
 * Rows, top to bottom:
 *   1  the headline, taking the whole stage
 *   2  the hero photograph, as a full-stage band under it
 *   3  chapter 2: the beat column, and the offset photo pair beside it
 *   4  Austin: the letter, and the city line beside it
 *   5  the ask, on its band
 *
 * Row 4 was the divider's pull-line until the slot came off the page
 * (founder, 2026-09-14); the rows below it moved up rather than leaving a
 * dead number. When the slot returns at campaign stage it needs a row of its
 * own again, between 3 and what is now 4.
 */

@media (min-width: 900px) {
  body.vision .wrap {
    max-width: 1060px;
    padding: 0 32px 96px;
  }

  body.vision main {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: 24px;
    row-gap: 0;
    align-items: start;
  }

  /* The photo containers are display:contents at every width (stated in the
     base block), so their children are items on this grid: the hero in chapter
     1 and the pair in chapter 2. The pair, hidden on the phone, comes back —
     this is the only place on the page it renders. */
  body.vision .photo-pair img { display: block; }

  /* And the phone's closing wrapper dissolves the same way, so chapters 3 and 4
     are grid items placed by grid-row below exactly as they were before the
     wrapper existed. There is no closing band at this width: the letter is a
     card again and the ask is the stage band. */
  body.vision .v-close { display: contents; }

  /* --- chapter 1: the band --- */
  /* The headline takes the whole stage. 753px rather than a 15ch measure:
     Zilla Slab at 62.5px across 996px of stage wants the longer line — at
     15ch the longest rotation line stacks five deep and leaves a ragged
     column of air. The measure is relative to both face and size, so this
     column renders ~692px wide after the optical correction. */
  body.vision .ch-feeling {
    grid-column: 1 / 13;
    grid-row: 1;
    margin-top: 44px;
  }

  body.vision .ch-feeling h1 { max-width: 753px; }

  /* The band, and the composition's face. Placed by role, not by file,
     because the file rotates: full stage width, one row down, under the
     pivot; 5/3 so it reads as a horizon rather than as a stretched
     thumbnail, and the chapter radius rather than the strip's smaller one —
     at this size 14px looks like an accident. */
  body.vision .photo-strip > .hero-photo {
    grid-column: 1 / 13;
    grid-row: 2;
    aspect-ratio: 5 / 3;
    border-radius: var(--radius);
    margin-top: 40px;
    /* The phone's full bleed comes off: the band is a grid item that spans the
       stage, not a 100vw escape from a text column. */
    width: 100%;
    margin-inline: 0;
  }

  /* Crops for the 5/3 band only — the strip's own 4/5 crops still govern the
     phone. These four are the ratified pass-4 bands, transcribed rather than
     eyeballed: each percentage is the rect's y-offset over the trim it leaves,
     y0 / (H − h). hero-gym is the severe one — a 2:3 portrait cut down to 5:3,
     so 3600px of the 6000 is thrown away and the band lands on "band A", the
     class itself: flags, instructor and rig in frame (1450/3600 = 40%).
     hero-dogs sits almost flush-bottom on the grass and the dogs (450/494 =
     91%); hero-trail is flush-bottom outright (600/600), which is what keeps
     the deck and the cyclist and drops the empty sky. The selector carries
     .hero-photo so it outranks the per-file rules above. */
  body.vision .photo-strip > .hero-photo[src$="hero-coffee.jpg"] { object-position: 50% 50%; }
  body.vision .photo-strip > .hero-photo[src$="hero-gym.jpg"]    { object-position: 50% 40%; }
  body.vision .photo-strip > .hero-photo[src$="hero-dogs.jpg"]   { object-position: 50% 91%; }
  body.vision .photo-strip > .hero-photo[src$="hero-trail.jpg"]  { object-position: 50% 100%; }

  /* --- chapter 2: a narrow beat column, an offset pair --- */
  /* The beats stop being three columns and become one, so the eye reads them
     top-to-bottom against the photos rather than across them. */
  body.vision .ch-what {
    grid-column: 1 / 6;
    grid-row: 3;
    margin-top: 96px;
  }

  body.vision .v-beat { margin: 0 0 24px; }

  /* The pair keeps a 7:4 proportion inside the seven columns the beat column
     leaves it. The narrow photo sits at the top of the block and the wide one
     is pushed 48px down from it — one margin, no rotation, no overlap, no
     absolute positioning. That drop is the only asymmetric gesture in this
     chapter. */
  body.vision .photo-pair {
    grid-column: 6 / 13;
    grid-row: 3;
    margin-top: 96px;
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
    column-gap: 20px;
    align-items: start;
  }

  /* The wide slot is 4:3 and the pair rotates through it, so both large frames
     — set A's Zilker picnic and set B's Zilker dusk — need the aspect and the
     drop. The picnic frame is 4:3 native and is the one crop-free image on the
     page; the dusk frame is 3:2, an 11% left-to-right trim, and it is centred
     per the ratified rect. Both state object-position because the strip's own
     rules above are tuned for the phone's 4/5 and would otherwise leak into
     this slot and shove the dusk skyline off-frame. */
  body.vision .photo-pair img[src$="pair-zilker-picnic.jpg"],
  body.vision .photo-pair img[src$="pair-zilker-dusk.jpg"] {
    aspect-ratio: 4 / 3;
    margin-top: 48px;
    object-position: 50% 50%;
  }

  /* The narrow slot keeps the strip's own 4/5, so only its crop needs stating —
     and it already is, above, with the file. Set A's floatilla is a 4:3
     landscape squeezed into 4/5, a hard left-to-right crop held centred (the
     ratified rect is dead centre: the waders, the rafts and the skyline all sit
     in the middle third). Set B's Barton Springs is a 3:4 portrait, so the trim
     is top-to-bottom and slight, and 50% is the rect. */

  /* --- chapter 3: the Austin row, flipped --- */
  /* The letter takes the wide left column and the city line answers it from a
     narrow right one, dropped so it starts against the letter's first
     paragraph. Source order still reads heading-then-letter. */
  /* The letter stands alone since the Austin column collapsed into the ask
     (2026-09-08): the card keeps the two-column era's ~7fr measure and centers
     on the stage — the narrow measure is doing warmth work; don't widen it. */
  body.vision .ch-austin {
    grid-column: 1 / 13;
    grid-row: 4;
    margin-top: 96px;
  }

  /* The letter gets its box back: on the stage it is a warm card on cream, not
     bare text on a wash, so the background, the radius and the positioning
     context the rule hangs off all return here. */
  body.vision .ch-austin .v-note {
    max-width: 620px;
    margin: 0 auto;
    padding: 34px 32px;
    font-size: 18px;
    position: relative;
    background: var(--field);
    border-radius: var(--radius);
  }

  /* And the golden hour rule goes back outside the card's top-left corner
     rather than sitting in flow above the first paragraph. */
  body.vision .v-note::before {
    position: absolute;
    top: -24px;
    left: 0;
    margin-bottom: 0;
  }

  /* --- chapter 4: the band --- */
  /* The page closes on a ground shift: the ask sits on the amber whisper, edge
     to edge of the stage, with a white form card on it. The side margins are
     exactly .wrap's 32px padding and are cancelled by matching padding, so the
     band reaches the viewport edges below 1060px and the stage edges above it
     — and nothing ever exceeds the container. */
  /* The chapter stops being the phone's white card and becomes the band it has
     always been here: the card treatment below it moves back onto the form
     card, and the border and radius the phone gave this element come off. */
  body.vision .ch-ask {
    grid-column: 1 / 13;
    grid-row: 5;
    margin: 96px -32px 0;
    padding: 64px 32px 72px;
    background: var(--field);
    border: 0;
    border-radius: 0;
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    column-gap: 48px;
    align-items: start;
  }

  body.vision .ch-ask .ask-col { padding-top: 4px; }

  /* The ask is its own column here, so the title takes the stage's clamp back
     and the mailto drops the phone's within-card break. */
  body.vision .v-lead {
    font-size: clamp(23.5px, 4.39vw, 29.5px);
    margin-bottom: 12px;
  }

  body.vision .v-mailto { margin-bottom: 0; }

  /* On the amber-wash band, body-size amber can't reach 4.5:1 (only the
     foreground is a lever here) — the mailto goes pine, which already colours
     this page's footer links. 8.6:1 on the field. */
  body.vision .ch-ask a { color: var(--pine); }

  /* The form gets its box back — here it is the white card on the band, beside
     the ask rather than under it. The warmer #E4D9C6 border is the one the card
     needs against --field; the hairline disappears on it. */
  body.vision .ch-ask .v-form-card {
    margin-top: 0;
    padding: 26px;
    background: var(--card);
    border: 1px solid #E4D9C6;
    border-radius: var(--radius);
  }

  /* Figure and ground inside the band: the fields stay paper white against the
     white card, so the card reads as a card and not as a hole in the wash. */
  body.vision .ch-ask .field { background: var(--ground); }

  /* The band's own 72px of bottom padding is the space before the footer. */
  body.vision .site-footer { margin-top: 40px; }
}
