/* ==========================================================================
   PORCH LIGHT — Porch Flock design system
   Node B5, 2026-09-19. Supersedes the FIELD GUIDE direction entirely.

   Every value below is sampled-band-checked or computed; 37 text pairs were
   verified at 0 failures by design/B5-contrast-check.py. Do not introduce a
   colour that is not in this block, and do not warm the neutrals: the whole
   reason the previous direction failed is that its ground sat at chroma
   spread 18 against a professional corpus that sits at 0-8.
   ========================================================================== */

:root {
  /* --- Neutrals. Spread (max-min of RGB channels) noted; pro band is 0-8. --- */
  --ground:      #F4F4F1;  /* 3   page ground */
  --surface:     #FFFFFF;  /* 0   cards, elevated panels */
  --sunk:        #E9E9E4;  /* 5   PHOTO WELLS. Photographs never sit on white. */
  --hairline:    #DCDCD5;  /* 7   decorative rules ONLY — 1.25:1, never a sole boundary */
  --border:      #8B8B80;  /* 11  control borders — 3.44:1 on surface */
  --ink:         #1A1A17;  /* 3   body + headings — 15.83:1 on ground */
  --ink-2:       #53534C;  /* 7   secondary — 7.03:1 */
  --ink-3:       #6E6E66;  /* 8   meta, captions — 4.66:1 */
  --dusk:        #16211F;  /* 11  the dark band */
  --dusk-2:      #24322F;  /* 14  cards on the dark band */

  /* --- Chroma. ONE primary accent. The corpus formula is near-neutral
         chrome plus exactly one saturated accent (21/21 sampled, median
         spread 192). Do not add a second decorative hue. --- */
  --porch:       #0F5F73;  /* 100  links, primary fill, focus — 6.56 on ground */
  --porch-press: #0B4B5C;
  --porch-tint:  #E4F0F3;
  --porch-lift:  #5FC6DE;  /* on --dusk ONLY */

  --light:       #9C5508;  /* the porch lamp: activity, "new", presence */
  --light-tint:  #FBEEDC;
  --light-lift:  #F2A93B;  /* on --dusk ONLY */

  --live:        #1B6B3F;  /* confirmed / fits / verified */
  --live-tint:   #E3F1E7;
  --live-lift:   #4FBE7E;  /* on --dusk ONLY */

  --alert:       #A4231C;
  --alert-tint:  #FAE7E5;
  --alert-lift:  #F08076;  /* on --dusk ONLY */

  --on-porch:    #FFFFFF;  /* label on a filled --porch / --live / --alert control */
  --on-porch-2:  #C7E3EA;  /* 35  secondary text ON a --porch ground — 5.37:1. Node B8.
                                  A tint of the one accent, not a second hue. Labels,
                                  captions and hints only; prose on --porch is white. */
  --ink-dusk:    #C9CCC9;  /* secondary text ON the dusk band — 11.55:1 */

  /* --- Type. Base 18px against an incumbent category shipping 14px. --- */
  --display: "Schibsted Grotesk", "Helvetica Neue", Arial, sans-serif;
  --ui: Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: "Fragment Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --t-meta: .8125rem;  /* 13px — MONO ONLY, never prose */
  --t-cap:  .9375rem;  /* 15 */
  --t-sm:   1rem;      /* 16 — prose floor */
  --t-body: 1.125rem;  /* 18 */
  --t-lead: 1.25rem;   /* 20 */
  --t-h4:   1.5rem;    /* 24 */
  --t-h3:   1.875rem;  /* 30 */
  --t-h2:   2.375rem;  /* 38 */
  --t-h1:   3rem;      /* 48 — mobile */

  /* --- 4px space grid --- */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px; --s10:128px;

  --gutter: 20px;
  --band: 64px;

  /* --- Radius: four values, in a system --- */
  --r-control: 8px;
  --r-card: 12px;
  --r-media: 20px;
  --r-pill: 999px;

  /* --- Shadow: three tokens, no literals. Corpus discipline is
         hairline-over-shadow (Figma and Supabase inset-ring; Geneva ships zero). --- */
  --ring: inset 0 0 0 1px var(--hairline);
  --lift: 0 1px 2px rgba(26,26,23,.06), 0 4px 12px rgba(26,26,23,.05);
  /* The halo is a tint of the one accent, so it has to travel with the accent
     when the season changes. Node B10 pulled it out of --focus for that reason. */
  --porch-ring: rgba(15,95,115,.35);
  --focus: 0 0 0 3px var(--porch-ring), 0 0 0 1px var(--porch);
  /* The default ring is --porch on --porch inside a saturated block, i.e. invisible.
     Every --porch ground switches to this one. Node B8. */
  --focus-porch: 0 0 0 3px rgba(255,255,255,.45), 0 0 0 1px var(--on-porch);

  --ease: cubic-bezier(0.2, 0, 0, 1);
  /* --d3 IS THE ENTRANCE TOKEN and node B11 is the recalibration of it.
     B10 shipped 320ms; the founder's verdict on the result was "I dont really
     see motion". 320ms is the right length for a STATE change and too short for
     an ENTRANCE, so it is the one value that moves. B5 §4.3's scale is intact:
     --d1 and --d2 are untouched, because control feedback was never the
     problem. */
  --d1: 120ms; --d2: 200ms; --d3: 520ms;   /* B11: entrance 320ms -> 520ms */

  /* The resting half of the card-lift transition. A shadow only animates
     smoothly between two shadows with the same number of layers, so the card's
     rest state carries --lift at zero alpha rather than no shadow at all. */
  --lift-off: 0 1px 2px rgba(26,26,23,0), 0 4px 12px rgba(26,26,23,0);

  /* Entrance travel. One value, so the rhythm is the same on every section.

     B11: 14px -> 32px. THE REAL DIAGNOSIS, and it is not just the distance.
     --ease is cubic-bezier(0.2, 0, 0, 1), a very hard decelerate: at the
     halfway point in TIME it has already covered 87.6% of the DISTANCE.
     B10 ran the fade on that same curve over that same 320ms, so the element
     was still near-transparent through the only part of the travel that had
     any speed in it, and fully opaque only for the last 12% of the distance.
     That is why 14px looked like nothing. Three changes, all of them needed:
       (a) the distance more than doubles;
       (b) the FADE FINISHES FIRST (--d-fade 240ms against 520ms of travel), so
           the element is solid while it is still visibly moving;
       (c) the travel gets its own, gentler curve (--ease-rise) which is only
           75% of the way home at the halfway point instead of 88%. */
  --rise: 32px;                                      /* B11: was 14px */
  --d-fade: 240ms;                                   /* B11: fade ends early */
  --ease-rise: cubic-bezier(0.25, 0.46, 0.45, 0.94); /* B11: easeOutQuad */

  /* --- B11: THE AWNING EDGE. --awn-n is a COUNT, not a period, so the lobes
         always divide the full viewport width exactly and there is never a
         chopped half-scallop at the right edge. Depth and count both step up
         with the breakpoint, holding the lobe at roughly 2.8:1 wide-to-deep at
         every size the page is actually read at. --- */
  --awn-h: 20px;   /* valance depth  */
  --awn-n: 7;      /* lobes across the full width */

  /* --- B11: THE GOOSE MOTIF. One tile, 200x200 units, carrying TWO geese in a
         half-drop (diagonal) lattice so the repeat does not read as a grid. The
         bird is 37% of the tile, which leaves roughly a bird-width of air
         between neighbours: closer than that and it stops being a surface and
         starts being a novelty print, which is the cute failure.
         It is a MASK, not an image: the colour comes from background-color, so
         the motif is drawn in the live seasonal accent with no second copy of
         the artwork and no hard-coded hex. Same profile, same direction, every
         instance - the mark is never mirrored. --- */
  --print-tile: 190px;
  --goose-tile: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20200%20200%22%3E%3Cg%20transform=%22translate(15.612,13.000)%20scale(0.87059)%20translate(-13,-13)%22%3E%3Cpath%20d=%22M23.5%2027.2C21.0%2026.4%2018.4%2026.0%2016.2%2026.8C15.2%2027.2%2015.3%2028.3%2016.4%2028.7C18.4%2029.4%2020.6%2030.2%2022.6%2031.4C24.0%2032.2%2025.4%2033.2%2026.6%2034.4C27.9%2030.0%2030.8%2026.6%2034.8%2025.2C40.6%2023.2%2046.6%2026.0%2048.6%2031.4C49.6%2034.2%2049.4%2037.0%2048.4%2039.4C47.2%2042.2%2045.6%2044.6%2044.6%2047.4C43.4%2050.8%2043.4%2054.2%2044.8%2057.4C46.4%2061.0%2049.6%2063.4%2053.4%2064.8C60.4%2067.4%2068.2%2068.0%2074.6%2071.8C77.4%2073.5%2079.8%2075.8%2081.4%2078.6C82.0%2079.6%2083.4%2079.4%2083.4%2078.2C83.4%2074.0%2082.0%2069.8%2079.4%2066.4C82.2%2067.0%2085.0%2067.8%2087.4%2069.4C88.4%2070.0%2089.6%2069.0%2089.0%2068.0C86.6%2063.8%2082.6%2060.8%2078.2%2059.0C72.4%2056.6%2066.0%2056.0%2060.0%2054.2C55.0%2052.7%2050.2%2050.2%2047.6%2045.6C45.8%2042.4%2045.6%2038.6%2046.6%2035.0C47.2%2032.8%2048.2%2030.6%2048.6%2028.4C49.4%2024.0%2047.2%2019.6%2043.2%2017.6C38.8%2015.4%2033.4%2016.6%2030.2%2020.2C28.4%2022.2%2027.2%2024.6%2026.6%2027.2C25.6%2027.0%2024.6%2027.0%2023.5%2027.2%20Z%22/%3E%3Cpath%20d=%22M44.2%2048.0C41.0%2052.0%2039.4%2057.2%2039.6%2062.4C39.9%2070.6%2045.0%2077.6%2052.6%2080.6C58.6%2083.0%2065.4%2082.8%2071.2%2080.2C77.6%2077.4%2082.4%2071.8%2084.0%2065.0C84.6%2062.4%2084.6%2059.8%2084.0%2057.2C79.6%2059.2%2074.8%2060.2%2070.0%2060.6C62.6%2061.2%2055.0%2060.0%2048.8%2055.8C46.8%2054.4%2045.2%2052.4%2044.2%2050.0%20Z%22/%3E%3Cpath%20d=%22M56.8%2080.4%20L55.6%2088.2%20L62.4%2088.2%20L61.2%2080.4%20Z%22/%3E%3Cellipse%20cx=%2258.8%22%20cy=%2291.4%22%20rx=%2217.6%22%20ry=%224.6%22/%3E%3C/g%3E%3Cg%20transform=%22translate(115.612,113.000)%20scale(0.87059)%20translate(-13,-13)%22%3E%3Cpath%20d=%22M23.5%2027.2C21.0%2026.4%2018.4%2026.0%2016.2%2026.8C15.2%2027.2%2015.3%2028.3%2016.4%2028.7C18.4%2029.4%2020.6%2030.2%2022.6%2031.4C24.0%2032.2%2025.4%2033.2%2026.6%2034.4C27.9%2030.0%2030.8%2026.6%2034.8%2025.2C40.6%2023.2%2046.6%2026.0%2048.6%2031.4C49.6%2034.2%2049.4%2037.0%2048.4%2039.4C47.2%2042.2%2045.6%2044.6%2044.6%2047.4C43.4%2050.8%2043.4%2054.2%2044.8%2057.4C46.4%2061.0%2049.6%2063.4%2053.4%2064.8C60.4%2067.4%2068.2%2068.0%2074.6%2071.8C77.4%2073.5%2079.8%2075.8%2081.4%2078.6C82.0%2079.6%2083.4%2079.4%2083.4%2078.2C83.4%2074.0%2082.0%2069.8%2079.4%2066.4C82.2%2067.0%2085.0%2067.8%2087.4%2069.4C88.4%2070.0%2089.6%2069.0%2089.0%2068.0C86.6%2063.8%2082.6%2060.8%2078.2%2059.0C72.4%2056.6%2066.0%2056.0%2060.0%2054.2C55.0%2052.7%2050.2%2050.2%2047.6%2045.6C45.8%2042.4%2045.6%2038.6%2046.6%2035.0C47.2%2032.8%2048.2%2030.6%2048.6%2028.4C49.4%2024.0%2047.2%2019.6%2043.2%2017.6C38.8%2015.4%2033.4%2016.6%2030.2%2020.2C28.4%2022.2%2027.2%2024.6%2026.6%2027.2C25.6%2027.0%2024.6%2027.0%2023.5%2027.2%20Z%22/%3E%3Cpath%20d=%22M44.2%2048.0C41.0%2052.0%2039.4%2057.2%2039.6%2062.4C39.9%2070.6%2045.0%2077.6%2052.6%2080.6C58.6%2083.0%2065.4%2082.8%2071.2%2080.2C77.6%2077.4%2082.4%2071.8%2084.0%2065.0C84.6%2062.4%2084.6%2059.8%2084.0%2057.2C79.6%2059.2%2074.8%2060.2%2070.0%2060.6C62.6%2061.2%2055.0%2060.0%2048.8%2055.8C46.8%2054.4%2045.2%2052.4%2044.2%2050.0%20Z%22/%3E%3Cpath%20d=%22M56.8%2080.4%20L55.6%2088.2%20L62.4%2088.2%20L61.2%2080.4%20Z%22/%3E%3Cellipse%20cx=%2258.8%22%20cy=%2291.4%22%20rx=%2217.6%22%20ry=%224.6%22/%3E%3C/g%3E%3C/svg%3E");
}

/* ==========================================================================
   THE SEASONAL ACCENT — node B10, 2026-09-20.

   WHAT THIS IS. The hobby's whole ritual is changing the outfit on a calendar
   [R1 §6: "the core ritual - changing the outfit - is itself the seasonal
   marker"]. The site does the same thing the member does. It is the only piece
   of flair here that the audience research asks for by name.

   WHY IT DOES NOT BREAK B5 §3.1. The professional formula is near-neutral
   chrome plus EXACTLY ONE saturated accent (21 of 21 brands sampled). This
   system never shows two. It substitutes the whole accent ramp for a window of
   the year and substitutes it back. At any instant the page is still one
   accent on neutral chrome.

   WHY ONLY TWO OVERRIDES. R1 §6 measures the calendar by SKU density and finds
   twin peaks: "Oct 31 Halloween - Highest SKU count - PEAK #1" and "Dec
   Christmas - PEAK #1-tie". July 4th is named "the clear third" and is
   deliberately NOT given a season: its colours are red, white and blue, and red
   is --alert. Jan-Mar is the off-season R1 identifies ("theft stories cluster
   Jan-Mar - the off-season engagement driver is community drama, not product"),
   so it keeps the default. A brand that redresses twice a year is dressing for
   an occasion; one that redresses twelve times has no identity to return to.

   WHY NOT LITERAL HALLOWEEN ORANGE AND CHRISTMAS RED. Because both are already
   spoken for and mean something:
     --light  #9C5508  amber   = new activity / presence
     --alert  #A4231C  red     = will not fit / error
   A site-wide orange in October says "new activity" on every surface; a
   site-wide red in December says "error". B5 §4.1 already records that --live
   and --alert sit within 0.8 of each other against --ground and lean on hue to
   separate, so hue here is load-bearing and cannot be spent twice. We take the
   OTHER half of each holiday's canonical pair instead - Halloween's purple and
   the blue of a winter night - and leave the orange to the photographs, which
   are full of pumpkins and mums and carry the season on their own.

   HOW THE RAMPS WERE BUILT. Every season is ISOLUMINANT with the default: each
   accent is solved to white-on-accent 7.23:1 and accent-on-ground 6.56:1, the
   same two numbers --porch already hits, and each tint, press, lift and -2 tint
   is solved to its teal counterpart's ratio. Consequences: every contrast pair
   in the system holds identically in every season, and the measured saturated
   share of a render does not move when the season does, because chroma spread
   stays in the same band (100 / 131 / 127, all far above the 40 threshold
   scripts/page-colour.py counts). The derivation reproduced #0F5F73 exactly
   from teal's own target ratios before it was trusted on a new hue.

   Every pair below is registered and computed in design/B5-contrast-check.py.
   ========================================================================== */

/* HARVEST - 1 September to 15 November. Miles Kimball's own season is
   "Autumn/harvest/Halloween" [R5 §5.2]; R5 records Halloween decoration
   searches picking up in September and peaking the first week of October. */
:root[data-season="harvest"] {
  --porch:       #8C23A6;  /* 131  hue 288 - 6.56 on ground, 7.23 under white */
  --porch-press: #721982;  /* 105  9.65 under white */
  --porch-tint:  #F5EBFA;  /*  15  6.24 with --porch on it */
  --porch-lift:  #ED99FF;  /* 102  8.34 on --dusk. ON --dusk ONLY. */
  --on-porch-2:  #F1D5F7;  /*  34  5.37 on --porch, 7.17 on --porch-press */
  --porch-ring:  rgba(140,35,166,.35);
}

/* LIGHTS - 16 November to 6 January. R5 §5.2: gifting searches jump after
   Halloween and Christmas interest spikes November-December. It ends at
   Epiphany, the traditional date the decorations come down, so the site is back
   in teal before the January off-season R1 describes. */
:root[data-season="lights"] {
  --porch:       #3150B0;  /* 127  hue 225 - 6.56 on ground, 7.23 under white */
  --porch-press: #273F8C;  /* 101  9.65 under white */
  --porch-tint:  #E9EEF7;  /*  14  6.21 with --porch on it */
  --porch-lift:  #91B9FF;  /* 110  8.33 on --dusk. ON --dusk ONLY. */
  --on-porch-2:  #D5DEF7;  /*  34  5.38 on --porch, 7.18 on --porch-press */
  --porch-ring:  rgba(49,80,176,.35);
}

/* --alert, --light and --live are NEVER redefined by a season. Status colour is
   not seasonal, and the moment it is, the registry's flags stop meaning
   anything. Verified in design/B5-contrast-check.py and by CIEDE2000 under
   normal, protanopic, deuteranopic and tritanopic simulation: no seasonal
   accent lands closer to any status colour than the shipped default teal
   already does. */

@media (min-width: 768px)  { :root { --gutter:32px; --band:80px;
                                     --awn-h:26px; --awn-n:11; --print-tile:210px; } }
@media (min-width: 1024px) { :root { --gutter:48px; --band:128px; --t-h1:4rem;
                                     --awn-h:30px; --awn-n:15; --print-tile:240px; } }

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

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

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--ui);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Every measurement, count and price in a COLUMN aligns. Not display figures:
   a tabular comma sits in a full digit cell and reads as "364 , 886". */
.num, .mono, table, time { font-variant-numeric: tabular-nums; }

h1, h2, h3, h4 { font-family: var(--display); font-weight: 700; margin: 0; text-wrap: balance; }
h1 { font-size: var(--t-h1); line-height: 1.05; letter-spacing: -.025em; }
h2 { font-size: var(--t-h2); line-height: 1.2;  letter-spacing: -.015em; }
h3 { font-size: var(--t-h3); line-height: 1.28; letter-spacing: -.01em; }
h4 { font-size: var(--t-h4); line-height: 1.35; letter-spacing: -.005em; }
p  { margin: 0; }

a { color: var(--porch); text-underline-offset: .18em; text-decoration-thickness: 1px; }
a:hover { color: var(--porch-press); }

/* The focus ring is never removed. */
:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-control); }

img, picture, svg { max-width: 100%; }
img { display: block; height: auto; }

.wrap { width: 100%; max-width: 1200px; margin-inline: auto; padding-inline: var(--gutter); }
.prose { max-width: 68ch; }

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--porch); color: var(--on-porch); padding: var(--s3) var(--s4);
  border-radius: 0 0 var(--r-control) 0; z-index: 50; text-decoration: none;
}
.skip:focus { left: 0; }

/* --- Labels: 13px mono, uppercase, tracked. Never prose. ------------------ */
.label {
  font-family: var(--mono); font-size: var(--t-meta); line-height: 1.4;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
  margin: 0 0 var(--s3);
}

/* --- Buttons. Primary CTA is min 48px; every target is at least 44px. ----- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 48px; padding: 0 var(--s5);
  font-family: var(--ui); font-size: var(--t-body); font-weight: 600;
  border-radius: var(--r-control); border: 1px solid transparent;
  text-decoration: none; cursor: pointer;
  transition: background var(--d1) var(--ease), color var(--d1) var(--ease);
}
.btn-primary { background: var(--porch); color: var(--on-porch); }   /* 7.23:1 */
.btn-primary:hover { background: var(--porch-press); color: var(--on-porch); }
.btn-quiet { background: transparent; color: var(--ink); border-color: var(--border); }
.btn-quiet:hover { background: var(--porch-tint); color: var(--porch-press); }
.btn-dusk { background: var(--ground); color: var(--dusk); }
.btn-dusk:hover { background: var(--on-porch); color: var(--dusk); }

.actions { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }
@media (max-width: 599px) { .actions .btn { width: 100%; } }

/* --- Status: colour is NEVER the only carrier. Symbol + word + colour.
       --live and --alert are within 0.8 of each other against --ground and
       would be indistinguishable in greyscale. The word is mandatory. ------ */
.pill {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-family: var(--mono); font-size: var(--t-meta); letter-spacing: .04em;
  text-transform: uppercase; padding: var(--s1) var(--s3);
  border-radius: var(--r-pill);
}
.pill-live  { background: var(--live-tint);  color: var(--live); }
.pill-light { background: var(--light-tint); color: var(--light); }
.pill-porch { background: var(--porch-tint); color: var(--porch); }

/* --- Overflow guard. Grid and flex items default to min-width:auto, so one
       unbreakable string (a citation URL) can force its track wider than the
       viewport and push the whole page into horizontal scroll. This is the
       single most common cause of a mobile layout that "almost" fits. ------- */
.facts > *, .cards > *, .three > *, .split > *, .footer-grid > *,
.masthead .wrap > *, .actions > * { min-width: 0; }

/* --- Bands ---------------------------------------------------------------- */
.band { padding-block: var(--band); }
.band-surface { background: var(--surface); }
.band-dusk { background: var(--dusk); color: var(--ground); }
.band-dusk h2, .band-dusk h3 { color: var(--surface); }
.band-dusk a { color: var(--porch-lift); }
.band-dusk .label { color: var(--light-lift); }

/* --- Photographs sit in a sunk well, never on white. ---------------------- */
.well {
  background: var(--sunk);
  border-radius: var(--r-media);
  overflow: hidden;
  box-shadow: var(--ring);
}
.credit {
  font-family: var(--ui); font-size: var(--t-cap); color: var(--ink-3);
  margin-top: var(--s3);
}

/* --- Header --------------------------------------------------------------- */
.masthead { border-bottom: 1px solid var(--hairline); background: var(--ground); }
.masthead .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); min-height: 72px;
}
.wordmark {
  display: inline-flex; align-items: center; gap: var(--s3);
  font-family: var(--display); font-weight: 700; font-size: 1.375rem;
  letter-spacing: -.02em; color: var(--ink); text-decoration: none;
}
.wordmark svg { width: 34px; height: 34px; color: var(--ink); flex: none; }
.masthead nav ul { display: flex; align-items: center; gap: var(--s5); list-style: none; margin: 0; padding: 0; }
.masthead nav a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); text-decoration: none;
}
.masthead nav a:hover { color: var(--porch); }
@media (max-width: 599px) { .nav-wide { display: none; } }

/* --- Hero ----------------------------------------------------------------- */
.hero { padding-top: var(--s7); padding-bottom: var(--s6); }
.hero h1 { max-width: 15ch; }
.hero .lead {
  font-size: var(--t-lead); line-height: 1.55; color: var(--ink-2);
  max-width: 56ch; margin-top: var(--s5);
}
.hero .actions { margin-top: var(--s6); }
.hero-media { margin: var(--s7) 0 0; }

/* Full-bleed on mobile, contained media radius from tablet up. */
@media (max-width: 767px) {
  .hero-media .well { border-radius: 0; margin-inline: calc(var(--gutter) * -1); }
  /* The mobile <source> is a different crop (1700x1344) from the one the <img>
     width/height attributes describe (1600x679). Without this the browser
     reserves the wide aspect and the page jumps when the image loads. */
  .hero-media img { aspect-ratio: 1700 / 1344; object-fit: cover; }
}

/* --- Facts ---------------------------------------------------------------- */
.facts { display: grid; gap: var(--s5); grid-template-columns: 1fr; margin-top: var(--s7); }
@media (min-width: 768px) { .facts { grid-template-columns: repeat(3, 1fr); gap: var(--s6); } }
.fact .n {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: var(--t-h2); line-height: 1.1; letter-spacing: -.015em;
  color: var(--porch); margin-bottom: var(--s3);
}
.fact p { color: var(--ink-2); font-size: var(--t-sm); }

/* --- Feature cards -------------------------------------------------------- */
.cards { display: grid; gap: var(--s4); grid-template-columns: 1fr; margin-top: var(--s7); }
@media (min-width: 640px) { .cards { grid-template-columns: repeat(2, 1fr); gap: var(--s5); } }
@media (min-width: 1024px) { .cards { grid-template-columns: repeat(4, 1fr); } }
.card {
  background: var(--surface); border-radius: var(--r-card);
  padding: var(--s5); box-shadow: var(--ring);
  display: flex; flex-direction: column; gap: var(--s3);
}
.band-surface .card { background: var(--ground); }
.card h3 { font-size: var(--t-h4); }
.card p { color: var(--ink-2); font-size: var(--t-sm); }
.card .icon { width: 28px; height: 28px; color: var(--porch); }

/* --- Split ---------------------------------------------------------------- */
.split { display: grid; gap: var(--s6); align-items: center; margin-top: var(--s7); }
@media (min-width: 900px) { .split { grid-template-columns: 1fr 1fr; gap: var(--s8); } }
.split-copy p + p { margin-top: var(--s4); }
.split-copy p { color: var(--ink-2); }
/* p has no default margin here, so the first paragraph after a heading needs
   one explicitly or it sits flush against the heading. */
.split-copy h2 + p, .split-copy h3 + p { margin-top: var(--s4); }

/* --- Quotes --------------------------------------------------------------- */
.three { display: grid; gap: var(--s4); grid-template-columns: 1fr; margin-top: var(--s6); }
@media (min-width: 900px) { .three { grid-template-columns: repeat(3, 1fr); gap: var(--s5); } }
blockquote {
  margin: 0; padding: var(--s5); background: var(--ground);
  border-radius: var(--r-card); box-shadow: var(--ring);
  border-left: 3px solid var(--light);
}
blockquote p { font-size: var(--t-sm); }
blockquote cite {
  display: block; margin-top: var(--s3); font-style: normal;
  font-family: var(--mono); font-size: var(--t-meta); color: var(--ink-3);
  overflow-wrap: anywhere;
}

/* --- Coverage ------------------------------------------------------------- */
.coverage {
  background: var(--surface); border-radius: var(--r-card);
  padding: var(--s6); box-shadow: var(--ring); margin-top: var(--s7);
}
.coverage .big {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: clamp(3rem, 12vw, 4.5rem); line-height: 1; letter-spacing: -.03em;
  color: var(--ink); margin-block: var(--s3);
}
.meter { height: 10px; background: var(--sunk); border-radius: var(--r-pill); overflow: hidden; margin-block: var(--s5); }
.meter span { display: block; height: 100%; background: var(--porch); border-radius: var(--r-pill); }

/* --- Footer --------------------------------------------------------------- */
.site-footer { background: var(--dusk); color: var(--ground); padding-block: var(--band) var(--s8); }
.site-footer a { color: var(--porch-lift); }
.footer-grid { display: grid; gap: var(--s6); grid-template-columns: 1fr; }
@media (min-width: 768px) { .footer-grid { grid-template-columns: 2fr 1fr 1fr; gap: var(--s8); } }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.site-footer .wordmark { color: var(--surface); }
.site-footer .wordmark svg { color: var(--ground); }
.dusk-lede { color: var(--ink-dusk); }
.footer-lede { color: var(--ink-dusk); max-width: 44ch; }
.fine {
  margin-top: var(--s8); padding-top: var(--s5);
  border-top: 1px solid rgba(244,244,241,.14);
  font-size: var(--t-cap); color: var(--ink-dusk); display: grid; gap: var(--s3);
}

/* --- Motion: honoured. Discord, Figma, Framer, PostHog and Cal.com honour
       this; AllTrails, Strava, Meetup, Nextdoor and Goodreads do not.
       The full reduced-motion block now lives at the END of this file, after
       the B10 motion layer, so that it is the last word in the cascade. ----- */

/* ==========================================================================
   REGISTRY COMPONENTS — node B6. Used by sizes.html.
   Everything below resolves to the PORCH LIGHT tokens declared at the top of
   this file. No raw hex, no second decorative hue.
   ========================================================================== */

/* The registry page was authored against the older `.wrap-wide` container. */
.wrap-wide { width: 100%; max-width: 1200px; margin-inline: auto; padding-inline: var(--gutter); }

.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;
}

/* `.key` was the old label class; it maps onto the mono label. */
.key {
  font-family: var(--mono); font-size: var(--t-meta); line-height: 1.4;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
  font-weight: 400;
}
.lede-note { color: var(--ink-2); }
.footnote { font-size: var(--t-cap); color: var(--ink-3); }
main .lede-note + .lede-note,
main .rules-of-the-page + .rules-of-the-page { margin-top: var(--s4); }

main > .wrap-wide > h1 { margin-top: var(--s7); max-width: 18ch; }
main section { margin-top: var(--s8); }
main section > h2 { margin-bottom: var(--s4); }
main section > h3 { margin-top: var(--s6); margin-bottom: var(--s3); }
main section h2 + .lede-note, main section h3 + .lede-note { margin-top: var(--s3); }

/* --- Coverage figure ------------------------------------------------------ */
.coverage {
  background: var(--surface); border-radius: var(--r-card);
  padding: var(--s6); box-shadow: var(--ring); margin-top: var(--s6);
}
.coverage-list { list-style: none; margin: 0; padding: 0; }
.fig {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: clamp(2.5rem, 10vw, 3.75rem); line-height: 1;
  letter-spacing: -.03em; color: var(--ink); margin-block: var(--s2) var(--s1);
}
.fig-sub { color: var(--ink-2); }
.coverage-list .support { margin-top: var(--s4); color: var(--ink-2); font-size: var(--t-sm); }
.fig-inline { font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--ink); }
.sort-rule { font-family: var(--mono); font-size: var(--t-meta); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3); margin-top: var(--s7); }

/* --- The table ------------------------------------------------------------
   B5 §4.3 permits a table its own horizontal scroll container; the PAGE must
   still never scroll sideways. Below 760px the rows become cards instead,
   using the data-key attributes the markup already carries. ---------------- */
.table-wrap {
  margin-top: var(--s4); background: var(--surface);
  border-radius: var(--r-card); box-shadow: var(--ring); overflow-x: auto;
}
.table-wrap:focus-visible { box-shadow: var(--focus); }
table.reg { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
table.reg caption {
  text-align: left; padding: var(--s5) var(--s5) var(--s3);
  font-family: var(--mono); font-size: var(--t-meta); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
table.reg th, table.reg td {
  text-align: left; padding: var(--s3) var(--s4); vertical-align: top;
  border-bottom: 1px solid var(--hairline);
}
table.reg thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--ground); color: var(--ink);
  font-family: var(--ui); font-size: var(--t-meta); font-weight: 600;
  letter-spacing: .02em; white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
tr.rowgroup th {
  background: var(--sunk); color: var(--ink);
  font-family: var(--display); font-size: var(--t-body); font-weight: 700;
  letter-spacing: -.01em;
}
table.reg .name { font-weight: 600; color: var(--ink); }
table.reg .maker { color: var(--ink-2); overflow-wrap: anywhere; }
/* Measurements are a column that must align, so tabular is correct here —
   unlike the front page's display figures, where a tabular comma reads as
   "364 , 886" because it occupies a full digit cell. */
/* This column holds a number for most rows but a full sentence of commentary
   for several, so it stays in the UI face. tabular-nums still aligns the
   figures; mono here turned the prose rows into a tall thin ribbon. */
table.reg .value { font-variant-numeric: tabular-nums; color: var(--ink); }

/* Column proportions. Without these the browser gives the prose columns the
   same width as the numeric ones and the rows grow to 600px tall. */
table.reg { min-width: 1100px; table-layout: fixed; }
table.reg th:nth-child(1), table.reg td:nth-child(1) { width: 15% ;  }
table.reg th:nth-child(2), table.reg td:nth-child(2) { width: 13% ; }
table.reg th:nth-child(3), table.reg td:nth-child(3) { width: 12% ; }
table.reg th:nth-child(4), table.reg td:nth-child(4) { width: 16% ; }
table.reg th:nth-child(5), table.reg td:nth-child(5) { width: 10% ; }
table.reg th:nth-child(6), table.reg td:nth-child(6) { width: 6% ;  }
table.reg th:nth-child(7), table.reg td:nth-child(7) { width: 6% ;  }
table.reg th:nth-child(8), table.reg td:nth-child(8) { width: 6% ;  }
table.reg th:nth-child(9), table.reg td:nth-child(9) { width: 16% ; }
/* break-word + hyphens breaks "circumference" at a syllable rather than
   mid-cluster the way overflow-wrap:anywhere does. */
table.reg thead th { white-space: normal; overflow-wrap: break-word; hyphens: auto; }
table.reg .src { font-size: var(--t-cap); }
table.reg .src a { overflow-wrap: anywhere; }
.when { display: block; margin-top: var(--s1); font-family: var(--mono);
  font-size: var(--t-meta); color: var(--ink-3); }
.unknown { font-family: var(--mono); font-size: var(--t-meta); color: var(--ink-3); }
.self-mark { font-family: var(--mono); font-size: var(--t-meta); letter-spacing: .04em; color: var(--porch); }

/* --- Status: symbol + word + colour, never colour alone. --live and --alert
   are 5.92 and 6.72 against --ground and are indistinguishable in greyscale,
   so the word and the symbol both carry the meaning, and the row rule adds a
   third, non-colour channel. ---------------------------------------------- */
.flag {
  display: inline-flex; align-items: center; gap: var(--s1); white-space: nowrap;
  font-family: var(--mono); font-size: var(--t-meta); letter-spacing: .02em;
  padding: 2px var(--s2); border-radius: var(--r-pill);
}
.flag-published { background: var(--live-tint);  color: var(--live); }
.flag-conflict  { background: var(--light-tint); color: var(--light); }
.flag-reported  { background: var(--sunk);       color: var(--ink-2); }
.flag-none      { background: var(--alert-tint); color: var(--alert); }

tr.is-published th[scope="row"] { box-shadow: inset 3px 0 0 var(--live); }
tr.is-conflict   th[scope="row"] { box-shadow: inset 3px 0 0 var(--light); }
tr.is-reported   th[scope="row"] { box-shadow: inset 3px 0 0 var(--ink-3); }
tr.is-none       th[scope="row"] { box-shadow: inset 3px 0 0 var(--alert); }

/* --- Legend --------------------------------------------------------------- */
.legend { background: var(--surface); border-radius: var(--r-card);
  padding: var(--s6); box-shadow: var(--ring); }
.legend ul { list-style: none; margin: var(--s4) 0 0; padding: 0; display: grid; gap: var(--s4); }
.legend li { display: grid; gap: var(--s2); }
@media (min-width: 700px) { .legend li { grid-template-columns: 170px 1fr; gap: var(--s4); align-items: start; } }
.legend .what { color: var(--ink-2); font-size: var(--t-sm); }

/* --- Notes ---------------------------------------------------------------- */
.note { margin-top: var(--s5); }
.bullets { color: var(--ink-2); padding-left: 1.2em; display: grid; gap: var(--s2); }
.verbatim {
  font-family: var(--mono); font-size: var(--t-cap); background: var(--sunk);
  padding: var(--s4); border-radius: var(--r-control); overflow-wrap: anywhere;
}
.conflicted { color: var(--light); font-weight: 600; }
.plain { color: var(--ink-2); }

/* --- Measurement diagram — the one place illustration is permitted. ------- */
.diagram {
  background: var(--sunk); border-radius: var(--r-media); box-shadow: var(--ring);
  padding: var(--s6); margin-top: var(--s5); display: flex; justify-content: center;
  color: var(--ink);
}
.diagram svg { width: 100%; max-width: 360px; height: auto; }

/* --- Contribute form ------------------------------------------------------ */
.plate { background: var(--surface); border-radius: var(--r-card);
  padding: var(--s6); box-shadow: var(--ring); margin-top: var(--s5); }
.grid-2 { display: grid; gap: var(--s4); grid-template-columns: 1fr; }
@media (min-width: 700px) { .grid-2 { grid-template-columns: 1fr 1fr; gap: var(--s5); } }
.field { display: grid; gap: var(--s2); }
.field label { font-weight: 600; font-size: var(--t-sm); }
.hint { display: block; font-weight: 400; color: var(--ink-3); font-size: var(--t-cap); margin-top: 2px; }
.field input, .field textarea {
  font-family: var(--ui); font-size: var(--t-body); color: var(--ink);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-control); padding: var(--s3) var(--s4); min-height: 48px; width: 100%;
}
.field textarea { min-height: 220px; font-family: var(--mono); font-size: var(--t-cap); line-height: 1.5; }
.field input:focus-visible, .field textarea:focus-visible { outline: none; box-shadow: var(--focus); }
.slip .field + .field { margin-top: var(--s5); }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s5); }
.slip .btn { background: var(--porch); color: var(--on-porch); border: 1px solid transparent; }
.slip .btn:hover { background: var(--porch-press); }
.slip .btn-secondary { background: transparent; color: var(--ink); border-color: var(--border); }
.slip .btn-secondary:hover { background: var(--porch-tint); color: var(--porch-press); }
.status-line { margin-top: var(--s3); font-size: var(--t-cap); color: var(--live); min-height: 1.4em; }

.sources-list { color: var(--ink-2); padding-left: 1.2em; display: grid; gap: var(--s3); }
.sources-list a { overflow-wrap: anywhere; }

/* --- Below 760px the table becomes cards, using the data-key labels the
   markup already carries, so nothing scrolls sideways on a phone. --------- */
@media (max-width: 1023px) {
  .table-wrap { background: transparent; box-shadow: none; overflow-x: visible; }
  table.reg caption { padding-inline: 0; }
  table.reg thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  table.reg { min-width: 0; table-layout: auto; }
  /* The desktop column percentages must be cancelled or they apply to the
     blocks and wrap every cell one character per line. */
  table.reg th:nth-child(n), table.reg td:nth-child(n) { width: auto; }
  /* A table-caption inside a display:block table shrink-to-fits. */
  table.reg caption { display: block; }
  table.reg, table.reg tbody, table.reg tr, table.reg th, table.reg td { display: block; width: auto; }
  table.reg tr.rowgroup th {
    border-radius: var(--r-control); margin: var(--s6) 0 var(--s3); padding: var(--s3) var(--s4);
  }
  table.reg tbody tr:not(.rowgroup) {
    background: var(--surface); border-radius: var(--r-card); box-shadow: var(--ring);
    padding: var(--s4); margin-bottom: var(--s4);
  }
  table.reg tbody tr:not(.rowgroup) th,
  table.reg tbody tr:not(.rowgroup) td { border-bottom: 0; padding: var(--s2) 0; }
  table.reg tbody tr:not(.rowgroup) th[scope="row"] {
    box-shadow: none; font-family: var(--display); font-size: var(--t-h4);
    line-height: 1.35; letter-spacing: -.005em; margin-bottom: var(--s2);
  }
  /* The column header becomes the field label on each card. */
  table.reg tbody td[data-key]::before {
    content: attr(data-key); display: block;
    font-family: var(--mono); font-size: var(--t-meta); letter-spacing: .04em;
    text-transform: uppercase; color: var(--ink-3); margin-bottom: 2px;
  }
  table.reg tbody td[data-key="Flag"]::before { content: none; }
}

/* ==========================================================================
   SATURATED BLOCKS — node B8, 2026-09-20.

   B5 §3.1's formula is near-neutral chrome PLUS ONE VIOLENTLY SATURATED
   ACCENT (21/21 brands sampled, median chroma spread 192). Only the first
   half shipped: measured on live renders, --porch was under 1% of pixels and
   appeared in the top seven colours of NEITHER page, which is why a palette
   that is numerically correct (ground spread 3, inside the professional 0-8
   band) still read as undifferentiated.

   Everything below makes the accent STRUCTURAL rather than decorative: the
   masthead, the proof-by-number band, the coverage figure, the row-group
   headers and the method section are --porch grounds carrying real content.
   Nothing here warms the light chrome — --ground is untouched at spread 3.
   The answer to "it looks beige" is more saturation, never more warmth.

   Every pairing introduced here is in design/B5-contrast-check.py with its
   computed ratio. The load-bearing ones: --on-porch on --porch 7.23:1,
   --on-porch-2 on --porch 5.37:1, --on-porch on --porch-press 9.65:1.
   --ink on --porch is 2.41:1, --ink-3 on --porch is 2.13:1 and --porch on
   --dusk is 2.28:1 — none of those three combinations is ever used.
   ========================================================================== */

/* --- The masthead is the first saturated surface on every page, and the one
       the phone sees before anything else. The goose knocked out of a filled
       --porch field is also the authored small-size mark (B5 §4.5). -------- */
.masthead { background: var(--porch); border-bottom-color: var(--porch-press); }
.masthead .wordmark, .masthead .wordmark svg { color: var(--on-porch); }
.masthead nav a { color: var(--on-porch-2); }
.masthead nav a:hover { color: var(--on-porch); }
.masthead :focus-visible { box-shadow: var(--focus-porch); }

/* Found by rendering, not by reading: `.band-dusk a` (0,1,1) was out-specifying
   `.btn-dusk` (0,1,0), so the closing CTA's label painted --porch-lift on a
   --ground fill at 1.9:1 — a live AA failure that predates this node. The same
   trap is why every .band-porch button rule below is written at two classes. */
.band-dusk .btn-dusk, .band-dusk .btn-dusk:hover { color: var(--dusk); }

/* --- A full-bleed --porch band. Same rhythm slot as .band-dusk. ----------- */
.band-porch { background: var(--porch); color: var(--on-porch); }
.band-porch h1, .band-porch h2, .band-porch h3, .band-porch h4 { color: var(--on-porch); }
.band-porch p, .band-porch li, .band-porch strong, .band-porch em { color: var(--on-porch); }
/* Labels, captions, dates and hints step back to the accent tint; prose never does. */
.band-porch .label, .band-porch .key, .band-porch .sort-rule, .band-porch .credit,
.band-porch .hint, .band-porch .footnote, .band-porch .when, .band-porch .unknown,
.band-porch cite { color: var(--on-porch-2); }
.band-porch a { color: var(--on-porch); }
.band-porch a:hover { color: var(--on-porch-2); }
.band-porch :focus-visible { box-shadow: var(--focus-porch); }
/* 68ch holds on a saturated ground too — long measure plus reversed text is the
   readability failure this band is most likely to cause. */
.band-porch p, .band-porch .bullets, .band-porch .plain { max-width: 68ch; }
.band-porch .well { background: var(--porch-press); box-shadow: none; }
/* The diagram keeps its --sunk well: its strokes are --porch and would vanish. */
.band-porch .diagram { background: var(--sunk); color: var(--ink); }

/* GUARDRAILS, deliberately kept though nothing on the site exercises them today:
   the white form plate and the two button variants are the elements most likely
   to be moved onto a --porch band next, and each is a silent contrast failure if
   it inherits. --ink on --porch is 2.41:1. */
/* A white plate inside a saturated band is still a white plate. */
.band-porch .plate, .band-porch .plate p, .band-porch .plate li,
.band-porch .plate label, .band-porch .plate strong { color: var(--ink); }
.band-porch .plate .hint { color: var(--ink-3); }
.band-porch .plate .status-line { color: var(--live); }
.band-porch .plate a { color: var(--porch); }
.band-porch .plate :focus-visible { box-shadow: var(--focus); }

/* Controls on a saturated ground. --ink on --porch is 2.41:1, so the quiet
   button inverts rather than keeping its ink label. */
.band-porch .btn-quiet { color: var(--on-porch); border-color: var(--on-porch-2); }
.band-porch .btn-quiet:hover { background: var(--on-porch); color: var(--porch-press); }
.band-porch .btn-primary { background: var(--on-porch); color: var(--porch); }
.band-porch .btn-primary:hover { background: var(--porch-tint); color: var(--porch-press); }

/* --- The proof-by-number band (index). The figures ARE the argument, so they
       get the loudest surface on the page. ---------------------------------- */
.band-porch .fact .n { color: var(--on-porch); }
.band-porch .fact p { color: var(--on-porch-2); }

/* --- The coverage figure sits on a deep panel on BOTH pages. -------------- */
.coverage { background: var(--porch); color: var(--on-porch); box-shadow: none; }
.coverage p, .coverage li, .coverage strong { color: var(--on-porch); }
.coverage .big, .coverage .fig, .coverage .fig-inline { color: var(--on-porch); }
.coverage .key, .coverage .label, .coverage .fig-sub, .coverage .support { color: var(--on-porch-2); }
.coverage a { color: var(--on-porch); }
.coverage :focus-visible { box-shadow: var(--focus-porch); }
/* Track 9.65:1 under the fill. The value is also printed in figures directly
   above and spoken in the meter's aria-label, so the bar is never the only
   carrier. */
.coverage .meter { background: var(--porch-press); }
.coverage .meter span { background: var(--on-porch); }
/* A filled --porch button on a --porch panel is invisible: invert it. */
.coverage .btn-primary { background: var(--on-porch); color: var(--porch); }
.coverage .btn-primary:hover { background: var(--porch-tint); color: var(--porch-press); }

/* The registry page's coverage panel is the top of the page, inside the band,
   so it drops the card treatment and runs flush. */
.page-head { padding-block: var(--band); margin-top: 0; }
.page-head h1 { margin-top: 0; max-width: 18ch; }
.page-head .coverage { background: transparent; padding: 0; margin-top: var(--s5); }
.page-head .sort-rule { margin-top: var(--s6); }
/* The registry page's section headings hang off `main section > h2`, which no
   longer matches once a section owns an inner .wrap-wide. */
.band-porch > .wrap-wide > h2 { margin-bottom: var(--s4); }

/* --- Inside the table: the two row groups and the column header are the
       section breaks, and they land as colour instead of a hairline. The data
       rows themselves stay --surface, because the table has to stay readable
       first. ------------------------------------------------------------- */
tr.rowgroup th { background: var(--porch); color: var(--on-porch); }
/* The column header stays --ground on purpose: it sits directly above the first
   row group, and two adjacent --porch strips merge into one unreadable block. */

/* --- The three verbatim quotes in note 1 are the page's evidence, not its
       chrome, and they are the one place a dark block belongs in a long
       reading column. White mono on --porch-press is 9.65:1. -------------- */
.verbatim { background: var(--porch-press); color: var(--on-porch); }
.verbatim p, .verbatim strong, .verbatim em { color: var(--on-porch); }
.verbatim cite { color: var(--on-porch-2); }

@media (prefers-contrast: more) {
  .band-porch .label, .band-porch .key, .band-porch .sort-rule, .band-porch .credit,
  .band-porch .hint, .band-porch .footnote, .band-porch .when, .band-porch .unknown,
  .band-porch cite, .coverage .key, .coverage .label, .coverage .fig-sub,
  .coverage .support, .verbatim cite { color: var(--on-porch); }
}

/* ==========================================================================
   B10 — FLAIR. Motion, expressive type, and the mark at scale.
   2026-09-20. The founder: "lets jazz up the site a bit too with flair...
   while not losing some sort of professional look."

   THE RULE THAT DECIDED WHAT WENT IN HERE. B5 §3.1 measured the professional
   formula as near-neutral chrome plus exactly ONE saturated accent, 21 of 21
   brands. Flair that adds HUES fights that finding; flair that adds MOTION,
   RHYTHM and TYPOGRAPHIC SCALE does not. So the weight of this node is on
   motion and scale, and the only new colour in the file is a substitution of
   the existing accent, never an addition to it.

   WHAT THE AUDIENCE WORK DEMANDS OF THE MOTION, specifically:
   - S3 §4 principle 3, "answer the question in the first sentence, put the joke
     after the number": NOTHING may delay a number. No count-up on the figures,
     no entrance animation above the fold, no reveal on the registry table.
   - S3 §4 principle 5, "strip every generational signifier": no parallax, no
     scroll-jacking, no blob shapes, no 3D tilt. Those read as a date stamp.
   - The page is opened from a link pasted into a Facebook group, on a phone, on
     mobile data. So: no library, no keyframe loops, one IntersectionObserver,
     transform and opacity only - the two properties a compositor can animate
     without laying the page out again.
   - B5 §4.3's tokens already existed and nothing used one: --d1 120ms control
     feedback, --d2 200ms state, --d3 320ms entrance, --ease.
   ========================================================================== */

/* --- 1. ENTRANCE -----------------------------------------------------------
   PROGRESSIVE ENHANCEMENT, stated plainly: nothing is hidden unless a script
   is actually running. flair.js sets .js-motion on <html> in the head, before
   first paint. With JavaScript off the class never arrives, every selector
   below fails to match, and .reveal is an ordinary div with every section
   visible. No content anywhere on this site exists only inside a script.

   The transition is declared on the DESTINATION state, not the source, so the
   initial paint is not itself a transition - the element simply starts hidden
   and animates once, when .is-in arrives. -------------------------------- */
html.js-motion .reveal { opacity: 0; transform: translateY(var(--rise)); }
html.js-motion .reveal.is-in {
  opacity: 1; transform: none;
  /* B11. THE FADE AND THE TRAVEL ARE NO LONGER THE SAME TRANSITION. The fade
     lands at 240ms; the 32px of travel keeps going to 520ms. For 280ms - more
     than half the entrance - the block is fully opaque AND still moving, which
     is the interval B10 did not have and the reason its motion was invisible. */
  transition: opacity var(--d-fade) var(--ease),
              transform var(--d3) var(--ease-rise);
}

/* A stagger inside the grids, with no extra markup and no JavaScript: the
   container is what gets observed, the children just carry a delay. Four steps
   of 60ms - long enough to read as a sequence, short enough that the last card
   has landed before the eye reaches it. */
html.js-motion .reveal .facts > *,
html.js-motion .reveal .cards > *,
html.js-motion .reveal .three > * { opacity: 0; transform: translateY(var(--rise)); }
html.js-motion .reveal.is-in .facts > *,
html.js-motion .reveal.is-in .cards > *,
html.js-motion .reveal.is-in .three > * {
  opacity: 1; transform: none;
  transition: opacity var(--d-fade) var(--ease),
              transform var(--d3) var(--ease-rise);
}
/* B11: 0/60/120/180ms -> 0/90/180/270ms. At 60ms apart the four cards were
   effectively simultaneous; at 90ms the last one starts after the first is a
   third of the way home, which is the smallest gap that reads as a SEQUENCE
   rather than as one block with soft edges. Last card lands at 790ms. */
html.js-motion .reveal.is-in .facts > *:nth-child(2),
html.js-motion .reveal.is-in .cards > *:nth-child(2),
html.js-motion .reveal.is-in .three > *:nth-child(2) { transition-delay: 90ms; }
html.js-motion .reveal.is-in .facts > *:nth-child(3),
html.js-motion .reveal.is-in .cards > *:nth-child(3),
html.js-motion .reveal.is-in .three > *:nth-child(3) { transition-delay: 180ms; }
html.js-motion .reveal.is-in .cards > *:nth-child(4) { transition-delay: 270ms; }

/* --- 2. CONTROL FEEDBACK ---------------------------------------------------
   Motion is used to say "this responds to you" and for nothing else. The
   photographs do NOT move on hover, deliberately: they are not interactive, and
   animating them would promise an affordance that is not there. ------------ */

/* A card that lifts has to keep its hairline ring. A shadow only interpolates
   between two shadows with the same layer count, so the rest state carries the
   lift at zero alpha (--lift-off) rather than no second shadow at all. */
.card {
  box-shadow: var(--ring), var(--lift-off);
  transition: transform var(--d2) var(--ease), box-shadow var(--d2) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .card:hover { transform: translateY(-2px); box-shadow: var(--ring), var(--lift); }
}

.btn {
  transition: background var(--d1) var(--ease), color var(--d1) var(--ease),
              border-color var(--d1) var(--ease), transform var(--d1) var(--ease);
}
/* A real pressed state. 1px, 120ms - the depth of an actual key travel, not a
   bounce. Applies on touch too, which is where most of this traffic is. */
.btn:active { transform: translateY(1px); }

/* The animated underline. The masthead nav has no underline at rest (it sits on
   a saturated ground where one would read as clutter), so the underline is the
   hover state and it grows from the left. It is drawn in currentColor, so it is
   --on-porch-2 at rest and --on-porch on hover, and it is seasonal for free. */
.masthead nav a { position: relative; }
.masthead nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 12px; height: 2px;
  background: currentColor; border-radius: var(--r-pill);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--d2) var(--ease);
}
.masthead nav a:hover::after,
.masthead nav a:focus-visible::after { transform: scaleX(1); }

/* Prose links already carry an underline; theirs thickens rather than grows, so
   the underline never stops being there. Colour is never the only signal. */
a { transition: color var(--d1) var(--ease), text-decoration-thickness var(--d2) var(--ease); }
a:hover { text-decoration-thickness: 2px; }

/* In-page anchors (How it works, How we measure, Correct us) glide rather than
   jump, so the reader keeps their place. Cancelled under reduced motion. */
html { scroll-behavior: smooth; }
[id] { scroll-margin-top: var(--s5); }

/* --- 3. EXPRESSIVE TYPE ----------------------------------------------------
   B5 §4.2 set the hero at 48px mobile / 64px desktop. Those were scale-step
   values; a hero is a specimen, not a step. This pushes the one headline on the
   site that has room to be a specimen, and leaves every other step alone, so
   the reading scale and the 18px prose floor are untouched.

   MOBILE FIRST, ACTUALLY: the min of the clamp is the value that matters, since
   the medium is a 390px phone. 52px at 390px leaves the longest word in the
   headline ("there's", 7 characters) about 190px wide inside 350px of content.
   Checked on a true 390px render, not computed and hoped for. --------------- */
.hero h1 {
  font-size: clamp(3.25rem, 8.6vw, 5.5rem);   /* 52px -> 88px */
  line-height: .96;
  letter-spacing: -.035em;
  max-width: 14ch;
}

/* The numbers get the same promotion, and for a stronger reason: S3 §4
   principle 3 says the number IS the product. Making the coverage figure the
   largest thing on its screen is that principle set in type. */
.coverage .big { font-size: clamp(3.25rem, 11vw, 5.5rem); }
.fig { font-size: clamp(2.75rem, 9vw, 4.5rem); }

/* The pull-quote. These three quotes are the page's evidence - three different
   measuring instructions from one store - and they were set at 16px, smaller
   than the prose around them, which is backwards. They now read at 20px.
   The rule also changes hue: it was --light, the AMBER THAT MEANS "NEW
   ACTIVITY", used decoratively. Decorative use of a status colour is exactly
   what erodes status semantics, so the rule is the accent now and travels with
   the season. On the registry page the same blockquote sits on --porch-press,
   where the accent would vanish, so it takes the accent's own light tint. */
blockquote { border-left: 4px solid var(--porch); }
.verbatim { border-left-color: var(--on-porch-2); }
.three blockquote p { font-size: var(--t-lead); line-height: 1.45; letter-spacing: -.005em; }

/* Section markers at scale. The 13px mono label was doing the whole job of
   announcing a section on its own. It now hangs off a short accent rule - one
   more non-colour rhythm cue down the page, in the single accent, seasonal for
   free, and it costs about 100 pixels. */
.band > .wrap > .label,
.band > .wrap-wide > .label,
.band .split-copy > .label { position: relative; padding-top: var(--s5); }
.band > .wrap > .label::before,
.band > .wrap-wide > .label::before,
.band .split-copy > .label::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: 32px; height: 3px; border-radius: var(--r-pill);
  background: var(--porch);
}
/* On a saturated or dark band the accent rule would be invisible or wrong, so
   it takes the label's own colour there. FOUND BY RENDERING, NOT BY READING:
   written as `.band-porch .label::before` this lost to `.band > .wrap > .label`
   on specificity (0,3,1 vs 0,3,1, and the base rule came first), which painted
   a 2.29:1 accent tick on the dark band. Doubling the band class makes the
   override the later rule at equal specificity. The same trap as B8's
   `.band-dusk a` out-specifying `.btn-dusk`. */
.band.band-porch .label::before,
.band.band-dusk .label::before { background: currentColor; }

/* --- 4. THE MARK AT SCALE --------------------------------------------------
   S3 §4 principle 2: "the goose is the subject. The owner is never the
   subject." The mark has only ever appeared as a 34px glyph beside a wordmark.
   Once on the page it gets to be the subject - at the close, above "There is
   nothing to join yet", where the page stops selling and just leaves the light
   on. --porch-lift is the accent's on-dark tint (8.3:1 on --dusk in every
   season), so the closing mark changes with the season too.

   It is decoration, not information: aria-hidden, no text depends on it, and
   nothing is lost if images are off. It is NOT filed and NOT print-bound;
   B1 §0.1's design-mark blocker is untouched by using it larger. ----------- */
.mark-xl {
  display: block; width: clamp(88px, 15vw, 160px); height: auto;
  color: var(--porch-lift); margin-bottom: var(--s5);
}

/* ==========================================================================
   B11 — THE AWNING EDGE AND THE GOOSE MOTIF.  2026-09-20.

   The founder, on B10: "I dont really see motion - and the color is good - so
   is the large mark. What else can we do to provide flair to the site as a
   whole?"  Three directions were chosen. Two of them are below; the third is
   the motion recalibration in the tokens and in section 1 above.

   ONE IDEA, NOT TWO. Both treatments here are the same idea - THE SATURATED
   BANDS ARE AWNING FABRIC. A porch awning is printed canvas with a scalloped
   valance cut into its lower edge, and that is exactly what a --porch band now
   is: a printed field that ends in a scalloped hem. Stating it as one material
   is what stops it becoming two ornaments, which is the difference between
   flair and kitsch. [S3 §4 principle 4] "never restyle the kitsch, never
   elevate it" - an awning is not a stylisation of this hobby, it is the thing
   the goose is standing under.

   WHAT IS DELIBERATELY NOT HERE, and why:
   - No row of geese marching across the footer. [B5 §5 refusal 4] "No mascot
     and no character," and a row of geese in profile walking one way is a
     character doing something. The same shape used as a SURFACE has no
     narrative and cannot be read as a mascot.
   - No goose print on --dusk, and none in the footer. The closing band already
     carries the mark at 88-160px; a repeat of the same silhouette behind it
     would be the same shape at two scales in one screen.
   - No scallop on the --surface band (white valance on --ground is 1.06:1,
     i.e. nothing) and none on --dusk (its bottom edge meets a footer of the
     identical colour). A treatment that has to be hunted for is not flair.
   - No second hue anywhere. [B5 §3.1] 21 of 21 sampled brands carry exactly
     one saturated accent. Both treatments are drawn in the accent's own ramp
     and therefore change with the season for free.
   ========================================================================== */

/* --- 6. THE AWNING EDGE ----------------------------------------------------
   The valance hangs BELOW the band, drawn in the band's own colour, so the
   page acquires a silhouette instead of stacking rectangles.

   IT CANNOT SHIFT LAYOUT AND IT CANNOT CLIP CONTENT. The band carries a
   margin-bottom of exactly --awn-h and the valance is absolutely positioned
   into that reserved margin. The space is reserved in the stylesheet, so it
   exists in the first painted frame; nothing is measured, nothing is inserted,
   nothing moves. Even where the next element's own top margin collapses with
   the band's, the collapsed gap is max(--awn-h, that margin), which is never
   less than the valance is deep. This matters on sizes.html, where the block
   after .page-head is a bare .wrap-wide with no top padding of its own.

   IT CANNOT OVERFLOW HORIZONTALLY. left:0/right:0 inside a band that is itself
   width:100%; the geometry is a background, so nothing can stick out sideways.

   IT SURVIVES 390px BECAUSE --awn-n IS A COUNT, NOT A PERIOD. Seven lobes
   across the viewport at 390px is a 55.7 x 20px lobe; fifteen at 1280px is
   85.3 x 30px. Both sit near 2.8:1 wide-to-deep. A fixed period tuned at
   1280px would have given a phone a comb, which is the trap the contract
   names. And because the count divides the width exactly, there is never a
   chopped half-lobe at the right-hand edge - the failure that makes a
   scalloped edge read as an accident rather than a decision.

   The ellipse is 'farthest-side', so its horizontal radius is always half the
   tile and its vertical radius is always the full valance depth: the lobes are
   tangent at every viewport width without a single media query on the shape
   itself. -------------------------------------------------------------- */
.masthead, .band-porch { position: relative; margin-bottom: var(--awn-h); }

/* The masthead's 1px --porch-press underline is retired here: the valance IS
   the boundary now, and it carries the accent against --ground at 6.56:1 -
   five times the separation the hairline was giving. A straight rule under a
   scalloped hem would also have contradicted the shape. */
.masthead { border-bottom-width: 0; }

.masthead::after,
.band-porch::after {
  content: "";
  position: absolute; left: 0; right: 0; top: 100%;
  height: var(--awn-h);
  pointer-events: none;
  background-image: radial-gradient(ellipse farthest-side at 50% 0,
                      var(--porch) 99.5%, transparent 100%);
  background-size: calc(100% / var(--awn-n)) 100%;
  background-repeat: repeat-x;
}

/* --- 7. THE GOOSE MOTIF ----------------------------------------------------
   A tone-on-tone print across the flat colour fields, and nowhere else. Flat
   colour is the only place a brand pattern belongs; over photographs or over
   the reading ground it would be interference.

   IT IS A MASK, WHICH IS THE WHOLE TRICK. The layer is a rectangle of
   --porch-press masked by the goose tile, so the motif is painted in the live
   seasonal accent - purple in harvest, blue in lights - from one copy of the
   artwork, with no hex anywhere and no second asset to keep in sync.

   WHY --porch-press AND NOT SOMETHING LOUDER. It is 1.33:1 against --porch,
   and it is the depth this system ALREADY uses tone-on-tone inside a saturated
   band: the photo wells and the coverage meter track are the same value. So
   the print is not a new decision about contrast, it is the existing one
   applied to a bigger shape. It also means the print can only ever RAISE text
   contrast, never lower it: white on --porch is 7.23:1 and on --porch-press is
   9.65:1; --on-porch-2 is 5.37:1 and 7.17:1. The worst case for every piece of
   text on these bands is the unprinted ground, which is the case already
   registered in design/B5-contrast-check.py.

   WHY LARGE AND SPARSE RATHER THAN SMALL AND DENSE. Two geese per 190-240px
   tile puts each bird at 70-89px, spaced about one bird-width apart. Small and dense would be a novelty print,
   which is cute; large and sparse is a surface, which is confident. Size is
   also what keeps it clear of [B5 §4.5]: a lockup appears once, and the thing
   that makes this ornament rather than a logo is that it appears twelve times.
   It is still a PROVISIONAL DESIGN MARK, NOT FILED - [B1 §0.1] is untouched by
   decorative use, and nothing here is print-bound or file-bound.

   NOT ON THE MASTHEAD. The masthead is 73px tall and the tile is 190-240px, so
   it could only ever show slices of a bird, which reads as an accident. The
   masthead gets the valance and stays unambiguous chrome.

   THE ONE TENSION, STATED. [B5 §5 refusal 2] bans texture: "no paper grain, no
   noise, no aged edge, no foxing," measured as zero of 34 professional pages.
   Every item in that list is an imitation of a physical SUBSTRATE, and that is
   the thing being refused - the Field Guide's paper. A step-and-repeat of the
   brand mark in one tint of the single accent is the identity system, not a
   substrate. I am reading the refusal at its stated scope rather than
   extending it, and this comment is here so the reading is visible.

   THE @supports GATE IS LOAD-BEARING. Without mask support this layer would
   paint as a solid --porch-press rectangle over the whole band. Gated, an
   unsupporting browser simply gets the plain band it has today. ---------- */
@supports ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .band-porch::before {
    content: "";
    position: absolute; inset: 0; z-index: 0;
    pointer-events: none;
    background-color: var(--porch-press);
    -webkit-mask-image: var(--goose-tile);
            mask-image: var(--goose-tile);
    -webkit-mask-size: var(--print-tile) var(--print-tile);
            mask-size: var(--print-tile) var(--print-tile);
    -webkit-mask-repeat: repeat;
            mask-repeat: repeat;
    -webkit-mask-position: center top;
            mask-position: center top;
  }
  /* Content sits above the print. The bands each have exactly one element
     child - a .wrap or a .wrap-wide - so one rule covers both pages. Without
     this the z-index:0 layer above would paint over the text. */
  .band-porch > .wrap,
  .band-porch > .wrap-wide { position: relative; z-index: 1; }
}

/* --- 5. REDUCED MOTION -----------------------------------------------------
   B5 §4.3: Discord, Figma, Framer, PostHog and Cal.com honour this; AllTrails,
   Strava, Meetup, Nextdoor and Goodreads do not. We honour it, and "honour"
   here means TRANSFORMS TOO, not just durations - a transform that snaps into
   place instantly is still a transform, and the blanket `transform: none` below
   is deliberately written so it can be verified by reading one rule.

   flair.js ALSO checks the same media query and simply does not add .js-motion
   when it is set, so a reduced-motion visitor never gets an entrance state at
   all. This block is the second of the two locks, not the only one. ------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
    transform: none !important;
  }
  /* Belt and braces: if the class ever arrived before the media query was
     evaluated, the content is visible regardless. */
  html.js-motion .reveal,
  html.js-motion .reveal .facts > *,
  html.js-motion .reveal .cards > *,
  html.js-motion .reveal .three > * { opacity: 1 !important; }
  /* The nav underline stops being a transform and becomes a plain show/hide,
     so `transform: none` above cannot leave it permanently on screen. */
  .masthead nav a::after { opacity: 0; }
  .masthead nav a:hover::after,
  .masthead nav a:focus-visible::after { opacity: 1; }
}
