/* ==========================================================================
   Main Street Gallatin — brand system (v10, 2026-08-01)
   --------------------------------------------------------------------------
   v8 = v7's structure with the accent swapped and the designer's second-round
   amendments applied. GOLD #FAAA3C IS RETIRED. There is no orange, amber or
   gold anywhere in this system; the only mentions left are this notice and the
   retirement notes below. Its replacement is SEAFOAM #BCBFB1.

   ##########################################################################
   THE CORE RULE — ACCENTS ARE BACKGROUND-DEPENDENT. Unchanged from v7; only
   the dark-ground accent's identity changed. Read this before touching any
   colour in this file.

     SEAFOAM #BCBFB1  is the accent for DARK grounds ONLY.
                      forest-deep 7.83 (AAA) · forest 6.21 · ink-on-it 8.17.
                      NEVER on cream / cream-tint / white: 1.59 / 1.42 / 1.87.
                      It is not "subtle" on light grounds, it is ILLEGIBLE —
                      the same failure gold had, at the same magnitude.

     WINE    #6E243A  is the accent for LIGHT grounds ONLY, and as of v8 it is
                      also the BUTTON FILL on light grounds.
                      cream 9.05 · cream-tint 8.06 · white 10.63 — all AAA.
                      NEVER on forest: 1.09:1, invisible.

   The two never trade places. Wanting seafoam on a cream card, or wine in the
   footer, means you want the other one. Negative tests in both directions live
   in scripts/contrast-check.js.
   ##########################################################################

   WHAT CHANGED IN v8

   1. SEAFOAM REPLACES GOLD, drop-in, in every role gold held: header and
      footer 3px rules, the "PERFECTLY SQUARE · EST. 1856" motto, nav hover,
      scroll-spy border, focus rings on dark, the hero rule, "Insiders" when it
      sits on a forest ground, and the nav "Join" CTA.

   2. SEAFOAM IS TONAL, NOT CHROMATIC. This is the one real difference and it
      is worth understanding before touching the hero. Gold was a warm hue
      against green — it separated by BOTH luminance and hue. Seafoam is a
      light green against dark green: the luminance separation is equal or
      better (7.83 vs gold's 7.59), but the hue separation is gone. Contrast
      maths cannot see that distinction, which is why the hero CTA question
      below is a judgment call and not a computation.

   3. BUTTONS ON LIGHT GROUNDS ARE WINE, NOT FOREST (designer, round 2).
      Forest-filled buttons read institutional rather than inviting. Every
      button on cream / cream-tint / white is a wine fill with a WHITE label:
      10.63 AAA, and the fill's own edge against cream is 9.05, so the button
      shape reads as cleanly as its text. Forest keeps its structural role
      (h2, card tops, card h3, tags) but is no longer a button colour on light.

   4. THE HERO KICKER IS CREAM, NOT SEAFOAM (designer, round 2). Seafoam is
      low-chroma and the hero photo is desaturated sepia, so seafoam would sink
      into it. Cream is 12.49 on the band against seafoam's 7.83 — and, more to
      the point, measured over the actual photograph rather than the band.

   5. THE WORDMARK HOVER IS WHITE. It used to inherit the global a:hover, which
      is wine — wine on the forest header, 1.09:1, exactly what the core rule
      forbids. It was invisible on rollover. Now cream (12.49) brightens to
      white (14.65): a luminance step, no hue change.

   6. 16px MINIMUM FONT SIZE, SITE-WIDE. Ten declarations were below it
      (captions, meta, labels, dates, footer legal, form hints, the motto).
      All raised to >=1rem. Tracking was reduced on the two uppercase labels
      that would otherwise have overflowed the mobile header.

   7. THE HERO CTA IS DELIBERATELY UNRESOLVED — two variants ship, switched by
      one attribute on <body>. See the block above .hero .button-primary.

   WHAT CHANGED IN v10

   A. THE CUPOLA SITS ON THE ROOFLINE. The header icon grew to fill the header
      and now rests on the 3px accent rule. Supersedes v5's shared baseline:
      only the icon meets the roof, the text centres independently.

   B. THE HOVER RULE, system-wide: hover ALWAYS increases contrast against its
      own background, never decreases it. Four violations fixed, two of which
      had not been noticed: prose links (10.63 -> 5.97, the worst in the
      system) and the ghost button's lightening fill (7.71 -> 5.16). Enforced
      by negative tests.

   C. THE MOTTO is weight 500 and width-matched to the wordmark by tracking
      alone, so the lockup reads as one unit and the name leads it.

   CARRIED FORWARD FROM v7, UNCHANGED:
   - --forest-deep #0A2E27 bands, --cream-tint / --cream-deep grounds,
     --wine-deep hover, cream page ground with white cards.
   - Hero photo: sepia duotone at brightness(0.9) under a 0.72/0.74/0.88
     overlay. Re-measured for v8 because the kicker's colour changed.
   - No diamonds (lines instead). No gold "Ongoing" badge. No Archivo. v6's
     courthouse icon. No alpha-faded text on any dark ground.

   TEXT-ON-DARK RULE: every text element on forest, forest-deep or the hero is
   cream, white or seafoam. Never ink, never wine, never a muted or alpha-faded
   tone. Enforced by negative tests plus a browser DOM sweep
   (scripts/audit-contrast.js) that walks every rendered element's COMPUTED
   colour against its true composited background — which is what catches
   inherited and same-token cases a static pairing list cannot.
   ========================================================================== */

/* ---------- Self-hosted fonts (latin subset) — zero external requests ------- */
@font-face {
  font-family: "Fraunces";
  src: url("/fonts/fraunces-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
/* Public Sans — the U.S. government's own typeface (USWDS). Same face
   production used for body copy. */
@font-face {
  font-family: "Public Sans";
  src: url("/fonts/public-sans-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Heritage anchor */
  --forest:      #0E4037;  /* structure on light: h2, card borders, tags */
  --forest-deep: #0A2E27;  /* the DARK BANDS: header, footer, hero ground */
  --wine:        #6E243A;  /* LIGHT-ground accent — see the core rule */
  --wine-deep:   #571C2E;  /* wine button hover */
  --cream:       #F5ECDA;  /* page background + all text on dark grounds */
  --cream-tint:  #ECDFC6;  /* alternating section ground */
  --cream-deep:  #E2D3B5;  /* card hairlines, "Recent" chip (decorative) */
  --ink:         #2A2422;  /* body text on light grounds */
  --ink-muted:   #4A443E;  /* secondary text on light. Production had #4F4842,
                              which is 6.82 on cream-tint — AA, but the only
                              text on the site not clearing AAA. Darkened 5
                              points so every ground clears AAA:
                              9.60 white / 8.18 cream / 7.29 cream-tint. */
  --paper:       #FFFFFF;  /* cards + inputs ONLY — never the page ground */

  /* DARK-ground accent. Legal on forest / forest-deep / the hero. Nowhere else. */
  --seafoam:        #BCBFB1;

  /* Extended interaction tokens (kept from v6) */
  --forest-tint: #2A6B5C;  /* functional borders on light; secondary button fill.
                              WHITE TEXT ONLY — ink on it is 2.44:1. */
  /* --wine-tint #9A4A5E was REMOVED in v10. It existed only as the prose-link
     hover, and that hover went the WRONG WAY: 10.63 -> 5.97 on white. Under
     the hover rule (hover always increases contrast) there is no legal use
     for a lighter wine on a light ground. Do not reintroduce it. */

  /* Type */
  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-body: "Public Sans", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-label: var(--font-body);

  --shadow-card: 0 1px 2px rgba(42, 36, 34, 0.05), 0 6px 18px rgba(42, 36, 34, 0.06);
  --shadow-card-hover: 0 2px 4px rgba(42, 36, 34, 0.07), 0 14px 32px rgba(42, 36, 34, 0.12);
  --radius-card: 8px;
  --section-pad: clamp(3.75rem, 5vw + 2rem, 5.5rem);

  /* Prose measure — spec §2, 65-75 characters. Applied to running text only,
     never to .narrow/.wrap, so section and card geometry is untouched. */
  --measure: 68ch;

  /* MEASURED (getBoundingClientRect), not guessed. Desktop/tablet (>600px)
     header renders at 72.59px; the ≤600px override lives in the mobile tier
     below, next to the rules that shrink the header. v7 changes header
     COLOUR only, not its box metrics, so these carry forward from v5/v6. */
  --header-height: 73px;
}

/* ---------- Reset-ish ---------- */
* { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  font-family: var(--font-body);
  font-size: 1.125rem;             /* 18px minimum — legibility for older readers */
  line-height: 1.6;                /* spec §2 floor for this audience */
  color: var(--ink);
  background: var(--cream);        /* production's warm ground; white is cards only */
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

.wrap { max-width: 68rem; margin: 0 auto; padding: 0 1.25rem; }
.narrow { max-width: 46rem; }
.nowrap { white-space: nowrap; }

h1, h2 { font-family: var(--font-display); font-weight: 600; line-height: 1.12; }
h3 { font-family: var(--font-display); font-weight: 600; line-height: 1.2; }

/* ##########################################################################
   ORPHAN CONTROL. Never leave a single word alone on the last line of a
   wrapped block where it is easy to see and fix.

   `text-wrap: pretty` (paragraphs, running copy) and `text-wrap: balance`
   (headings, short high-visibility blocks) are BROWSER-LEVEL wrapping
   hints. Firefox has not shipped `pretty` as of this writing, so it is
   progressive enhancement there -- it does nothing, it does not error.

   For SHORT lines where an orphan is unacceptable in ANY browser (headings,
   the hero tagline, button labels, card titles, section kickers), the
   source copy ALSO has a non-breaking space between the last two words
   (see index.html / join.html). That is the deterministic fix; balance/
   pretty here are the second line of defence for everything else, and the
   only defence in Firefox until it ships `pretty`.

   Deliberately NOT using the CSS `orphans` / `widows` properties: they only
   apply to multi-column and paged (print) contexts and do nothing in a
   normal scrolling block, which is what every element here is. */
h1, h2, h3,
.hero-tagline,
.eyebrow,
.button { text-wrap: balance; }

p, li,
.section-lede,
.mission-text,
.ladder p,
.card p,
.event-blurb,
.join-lede,
.join-required-note,
.join-optout,
.field-hint,
address { text-wrap: pretty; }

/* Prose measure cap (spec §2). Deliberately on the TEXT elements rather than
   on .narrow — capping the container would move card and grid geometry the
   v2-v5 layout passes settled. Headings, buttons and grids are unaffected. */
.section-lede,
.mission-text,
.ladder p,
.card p,
.event-blurb,
.join-lede,
.join-required-note,
.join-optout,
address { max-width: var(--measure); }

/* Prose links: WINE on light grounds, always underlined. Wine beside ink is
   only 1.44:1, so colour alone cannot mark a link — the underline is what
   makes it a link. Links on dark grounds are overridden to cream further
   down (header, hero, footer). */
a {
  color: var(--wine);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.15em;
}
/* ##########################################################################
   THE HOVER RULE — SYSTEM-WIDE. HOVER ALWAYS INCREASES CONTRAST AGAINST ITS
   OWN BACKGROUND. IT NEVER DECREASES IT.

     On a DARK ground  -> hover goes LIGHTER.  cream -> white.
     On a LIGHT ground -> hover goes DARKER.   wine  -> wine-deep.

   The underline (or border, or shadow) is IN ADDITION to the colour change,
   never a replacement for it. A thicker underline with a fading colour is
   still a regression.

   This also rules out a hover FILL that lightens a dark ground behind light
   text: the text keeps its colour but loses contrast, which is the same
   failure wearing a different hat. See .button-ghost:hover.

   Enforced by negative tests in scripts/contrast-check.js: every hover pair is
   asserted to be strictly greater than its resting pair.
   ########################################################################## */
a:hover {
  /* WAS --wine-tint, which took prose links from 10.63 to 5.97 on white: the
     single worst hover regression in the system. wine-deep goes the other way,
     to 13.04. */
  color: var(--wine-deep);
  text-decoration-thickness: 3px;
}

/* ---------- Focus visibility (keyboard) — spec §8 ---------- */
/* Light grounds: wine (10.63 white / 9.05 cream / 8.06 cream-tint). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible {
  outline: 3px solid var(--wine);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Dark grounds: SEAFOAM (7.83 on forest-deep). Wine here would be 1.09:1. */
.hero a:focus-visible,
.hero button:focus-visible,
.site-header a:focus-visible,
.site-header .nav-toggle:focus-visible,
.site-footer a:focus-visible {
  outline-color: var(--seafoam);
}

/* ---------- Skip link ---------- */
.skip-link {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 100;
  background: var(--forest-deep);
  color: var(--cream);
  padding: 0.6rem 1.1rem;
  font-family: var(--font-label);
  font-weight: 600;
  text-decoration: none;
  transition: top 120ms ease-out;
}
.skip-link:focus { top: 1rem; }

/* ---------- Rule separators (LINES, not production's diamonds) ------------- */
/* Chief prefers lines. Wine is the light-ground accent, so the default rule is
   wine; on the dark hero it flips to seafoam per the core rule. */
.square-rule {
  display: block;
  width: 5.5rem;
  height: 4px;
  background: var(--wine);
  border-radius: 2px;
  margin: 1.5rem 0 0;
}
.square-rule.is-centered { margin-left: auto; margin-right: auto; }
.square-rule--forest { background: var(--forest); }
.hero .square-rule { background: var(--seafoam); }   /* dark ground -> seafoam */

/* ---------- Header (forest-deep band + seafoam accents) ---------- */
.site-header {
  background: var(--forest-deep);
  color: var(--cream);
  border-bottom: 3px solid var(--seafoam);   /* production ran this rule in gold; v8 runs it in seafoam */
  position: sticky;
  top: 0;
  z-index: 50;
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  padding-top: 0.8rem;
  padding-bottom: 0.8rem;
  padding-left: 0.5rem;   /* tighten left so the brand sits closer to the edge */
}
@media (max-width: 640px) {
  .header-inner { padding-right: 0.5rem; }  /* toggle hugs the right edge on phones */
}
/* ##########################################################################
   THE CUPOLA SITS ON THE ROOFLINE.

   The header's 3px bottom accent rule reads as a roofline, and the mark is a
   courthouse cupola, so the cupola stands ON that rule. The icon grows to fill
   the header and its bottom edge lands on the top of the rule.

   THIS SUPERSEDES THE v5 SHARED-BASELINE RULE. v5 bottom-aligned the whole
   brand (icon + text) to one ground line. Now only the ICON meets the roof;
   the text block is centred independently and does NOT grow with it.

   How the icon reaches the rule without making the header taller: it is given
   a height larger than the row, then pulled down with a negative bottom margin
   equal to .header-inner's bottom padding. The negative margin cancels the
   extra height in layout, so the flex row still measures the 44px tap target
   and --header-height is unchanged, while the icon visually overflows into the
   padding and lands exactly on the rule. Verified by measurement, not by eye:
   scripts/audit-contrast.js reports the gap between the icon's bottom edge and
   the rule's top edge, and it must read 0.

   OPTICAL CUT: the mark is a three-cut set (large 48px+, medium 24-32px,
   small-16 16-20px) and the cuts are separately drawn, NOT one drawing scaled.
   icon-msg-2026.svg is the LARGE cut, which is the correct one for the sizes
   used here. If the header icon is ever taken below 48px, swap the file for
   the medium cut rather than letting the large one shrink. The full set lives
   in Drive: CannonOps/MSG-Website/Brand-Assets/Claude-Design-Icon-2026-07-31.
   ########################################################################## */
.brand {
  display: inline-flex;
  align-items: center;     /* text centres; the icon overrides to flex-end */
  gap: 0.7rem;
  text-decoration: none;
  color: var(--cream);
  /* v12: .brand is itself a flex ITEM of .header-inner, alongside the 44px
     hamburger. Without this, .brand's own cross-size defaults to its tallest
     CHILD's natural height -- the wordmark's ~36.8px, since the icon's
     negative-margin trick deliberately shrinks its own contribution to that
     calculation. That made .brand only 36.8px tall, not 44px, so the icon's
     align-self:flex-end (scoped to .brand, not to .header-inner) was anchoring
     to the WRONG box: 3.6px short of the true row bottom. Caught only by
     measuring the medium cut's fit (3.59px gap where 0.00 was expected) --
     the large cut's own margin math happened to sum to exactly 44 by
     coincidence, which masked this for both desktop breakpoints.
     Stretching .brand to the full 44px row height fixes the reference box for
     BOTH icon variants and does not move the wordmark: it was already
     centered within this same 44px band via .header-inner's own alignment;
     this just makes that explicit instead of accidental. */
  align-self: stretch;
}
/* The wordmark used to inherit the global `a:hover { color: var(--wine-tint) }`
   — wine-tint on the forest-deep header is 2.75:1, so the whole brand went
   near-invisible on rollover. Straight violation of the no-wine-on-dark rule,
   flagged by the designer. Hover is now a LUMINANCE step with no hue change:
   cream 12.49 -> white 14.65. Applies to every descendant so the motto
   brightens with it rather than staying seafoam against a white wordmark. */
.brand:hover,
.brand:hover .brand-wordmark,
.brand:hover .brand-est { color: var(--paper); }
/* ##########################################################################
   v12 -- ICON PUNCHES THROUGH BOTH PADDINGS, NOT JUST THE BOTTOM ONE.

   v10 only extended the icon DOWN through the bottom padding to reach the
   rule. That left the ENTIRE top padding (12.8px) sitting completely unused
   above the icon -- dead space the icon could have been using. v12 also
   extends it UP through most of the top padding, so it fills far more of the
   header's vertical space, spire nearly touching the top edge.

   Chief asked to "reduce the surrounding margin by ~30% and give that space to
   the icon." Read literally as "shrink .header-inner's own padding," that
   conflicts with a harder constraint a few lines down: the 44px hamburger tap
   target is fixed, and header height = padding-top + 44 + padding-bottom +
   3px border. Shrinking the shared padding SHRINKS the header (verified: cut
   both paddings 30% and desktop drops from 72.6px to 64.9px) -- but the header
   height must hold at 72.59/63.00, which this same instruction also demands.
   Those two requirements are incompatible if "margin" means the shared
   padding, so this reallocates the icon's OWN unused headroom instead:
   header-inner's padding is UNTOUCHED (still 0.8rem top+bottom here), and the
   icon now claims almost all of it via a symmetric negative-margin technique,
   which achieves "more icon, less unused margin, same header height" without
   touching the padding every other header element also depends on.

   THE MATH (verified against the rendered page, not just derived by hand):
   Let P = padding-top = padding-bottom = 12.8px, Row = 44px (hamburger floor).
   Content-box height = 2P + Row = 69.6px; the rule sits at that y-coordinate.
   Leaving a small deliberate gap g=2px at the TOP ("nearly touching", not
   flush -- flush precision is reserved for the BOTTOM, where item 1's zero-gap
   requirement lives): icon height = 2P+Row-g = 67.6px.
   margin-bottom stays -P (=-12.8px, unchanged from v10 -- still what lands the
   bottom exactly on the rule).
   margin-top = g-P = 2-12.8 = -10.8px (pulls the icon up so its rendered top
   sits g=2px below the header's own top edge, while keeping the icon's
   flex-line CONTRIBUTION at exactly Row=44px: height+margin-top+margin-bottom
   = 67.6-10.8-12.8 = 44, so header-inner's computed height cannot grow).

   ITEM 1 (icon floats above the rule despite a 0.00px DOM-box gap): the SVG
   files themselves had ~1.8-1.9 units of baked-in padding inside their own
   32x32 viewBox (measured by sampling every path/rect/circle's true ink
   extent, including stroke-width). The DOM element's bounding box was flush
   with the rule; the drawn plinth was not. Fixed at the SOURCE: all three
   optical cuts (large/medium/small-16) had their viewBox cropped to the
   measured visible-ink bbox, and their root <svg width/height> set to the
   viewBox's OWN dimensions exactly (not independently rounded -- a rounded
   root width created a fresh aspect-ratio mismatch against the viewBox that
   silently reintroduced ~2px of letterboxing on first attempt; verified by
   pixel-sampling the rendered PNG, not assumed from spec memory). Confirmed
   pixel-exact after the fix: the drawn ink now touches all four edges of the
   <img> element's own box for both the large and medium cuts.

   v13: .brand-mark-large is now the ONLY header mark at every width down to
   320px (see the phone-icon section further down) -- there is no more
   medium-cut swap, so this growth treatment applies everywhere the header
   icon appears. .site-footer .brand-mark overrides all of this back to its
   own small size. */
.brand-mark {
  height: 4.225rem;              /* 67.6px = 2*12.8(padding) + 44(row) - 2(top gap) */
  width: auto;
  flex: none;
  align-self: flex-end;
  margin-top: -0.675rem;         /* -10.8px: g(2) - P(12.8) */
  margin-bottom: -0.8rem;        /* -12.8px: -P, unchanged from v10 */
}
/* nowrap: at a 375px viewport the wordmark broke to two lines, which pushed
   the brand block past the 44px tap target and grew the header from 63px to
   72.61px. The name is a lockup; it does not wrap. */
.brand-wordmark {
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.05;
}
.brand-wordmark strong { font-weight: 700; }
/* The motto is LOCKED under the wordmark — don't change this string. SEAFOAM
   on forest-deep (7.83:1). Production ran it in gold and Chief called it the
   thing that pops; v8 keeps the role and swaps the hue. Set in the body face,
   never the logo's script. */
/* THE MOTTO. Deliberately a STANDALONE rule, decoupled from .eyebrow and any
   other tracked-uppercase kicker: this is part of the brand lockup, not a
   section label, and a future change to the kicker treatment must not move it.
   Do not fold it back into a shared selector.

   WEIGHT 500, not 700. At 16px the 700 weight made the motto read louder than
   the wordmark above it, which inverted the lockup's hierarchy. The name leads;
   the motto supports.

   SIZE stays at the 16px floor. Never take it back under 16.

   LETTER-SPACING is the only lever used to match the wordmark's rendered
   width, because the wordmark must not grow (the icon is the element that
   grew this pass). The value below was SOLVED from measurement, not guessed:
   audit-contrast.js reports both rendered widths and the per-character
   tracking delta needed to close the gap. Re-measure if either face, the
   wordmark size, or the motto string ever changes. */
.brand-est {
  display: block;
  font-family: var(--font-label);
  font-size: 1rem;
  font-weight: 500;
  /* SOLVED, not guessed. Measured at 1440: wordmark 202.88px; motto at zero
     tracking 222.23px. (202.88 - 222.23) / 28 chars = -0.691px = -0.0432em.
     Slightly negative tracking on uppercase is unusual but mild at this size,
     and it is the only lever available: the wordmark must not grow. */
  letter-spacing: -0.0432em;
  text-transform: uppercase;
  color: var(--seafoam);
  margin-top: 0.15rem;
  /* Defense in depth: the tracking above already matches this to the
     wordmark's width so it should never need to wrap, but nowrap makes that
     a guarantee rather than a derived consequence of the shrink-to-fit maths
     above. If the breakpoint math is ever wrong, this converts a 3-line
     lockup into an overflow, which is a visible, obvious bug -- not a subtle
     one. */
  white-space: nowrap;
}
.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 1.15rem;
  list-style: none;
}
/* Nav links are cream on forest-deep (12.49:1) with a SEAFOAM hover/active rule.
   Underlines are relaxed here because these are chrome, not prose — the
   border carries the state and nothing nearby competes to be mistaken for a
   paragraph link. */
.site-nav a {
  color: var(--cream);
  font-family: var(--font-label);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  padding: 0.35rem 0.1rem;
  border-bottom: 2px solid transparent;
}
/* Hover rule: cream -> white (12.49 -> 14.65). The seafoam border is IN
   ADDITION to the brightening, not instead of it. */
.site-nav a:hover { color: var(--paper); border-bottom-color: var(--seafoam); }

/* "Join" is the one real destination, so it reads as a button, and it takes
   the SAME treatment as the hero CTA: cream fill + wine label (9.05:1). Both
   sit on a dark ground, so both invert the light-ground pairing. This is the
   one button language, applied consistently.
   It was seafoam + ink (8.17) in the first v8 pass. Seafoam is tonal, so the
   pill read far quieter than an action colour should; wine on cream fixes the
   prominence without touching legibility. */
.site-nav .nav-cta {
  background: var(--cream);
  color: var(--wine);
  border-radius: 4px;
  padding: 0.4rem 1rem;
  border-bottom: 2px solid transparent;
  font-weight: 700;
}
.site-nav .nav-cta:hover {
  background: var(--paper);
  color: var(--wine-deep);         /* 13.28:1 — same hover as the hero CTA */
  border-bottom-color: transparent;
}

/* ---------- Mobile nav toggle (hidden on desktop) ---------- */
.nav-toggle {
  display: none;
  flex: none;               /* never shrink; never wrap to a second row */
  margin-left: auto;        /* pin to the right regardless of brand width */
  align-self: center;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  background: transparent;
  border: 2px solid transparent;
  border-radius: 4px;
  color: var(--cream);
  cursor: pointer;
}
.brand { min-width: 0; }    /* allow the brand to shrink before the toggle does */
.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: "";
  display: block;
  width: 1.5rem;
  height: 2px;
  background: var(--cream);
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before { position: absolute; left: 0; top: -0.5rem; }
.nav-toggle-bars::after  { position: absolute; left: 0; top: 0.5rem; }
.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after { transition: transform 140ms ease, background-color 140ms ease; }
/* open → X */
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(0.5rem) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after  { transform: translateY(-0.5rem) rotate(-45deg); }

/* ##########################################################################
   THE HAMBURGER BREAKPOINT IS DERIVED FROM THE LOCKUP, NOT A ROUND NUMBER.

   Chief's rule: the nav collapses to the hamburger EARLIER than risk the
   wordmark/motto block exceeding 2 lines (wordmark 1 line + motto 1 line).
   The wordmark is nowrap (can't wrap, only overflow) and the motto's tracking
   is tuned to match the wordmark's width exactly (see .brand-est) -- so as
   long as .brand is never flex-squeezed below that matched width, neither
   line wraps. The one thing that CAN squeeze it is the full nav sharing the
   row: if brand + nav together exceed the header's content width, .brand's
   `min-width: 0` lets it shrink below its own content, and the motto (which
   has no nowrap of its own -- see below) would wrap.

   MEASURED, not guessed, on 2026-08-01: with the current markup (56.8px
   roofline icon, current wordmark/motto strings, 6 nav links + Join),
     padding-left 8 + brand 292.19 + column-gap 24 + nav 500.86 + padding-right 20
     = 845.05px is the true minimum for full nav to coexist with an unsqueezed
   brand block.

   BREAKPOINT SET TO 900px, not 845. Chief's stated preference is to err
   toward the hamburger, and 845px is a razor's edge that a font-load timing
   difference (fallback metrics before the self-hosted woff2 swaps in -- a
   documented gotcha in this repo's CLAUDE.md) or a future copy change could
   eat through silently. The ~55px margin is deliberate slack, not imprecision.
   If the nav gains or loses an item, or the wordmark/motto strings change,
   RE-MEASURE with scripts/audit-contrast.js rather than hand-tuning this
   number -- it reports brand/nav widths and flags a wrapped nav directly.
   ########################################################################## */
@media (max-width: 900px) {
  .nav-toggle { display: inline-flex; }
  /* Nav becomes a dropdown below the header — out of the top row's flow */
  .site-nav {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--forest-deep);
    border-bottom: 3px solid var(--seafoam);
    box-shadow: 0 10px 24px rgba(10, 46, 39, 0.45);
    padding: 0.3rem 1.25rem 0.7rem;
    z-index: 60;
  }
  .site-nav.is-open { display: flex; }
  .site-nav li { width: 100%; }
  .site-nav a {
    display: block;
    padding: 0.8rem 0.2rem;
    border-bottom: 2px solid transparent;
  }
  .site-nav .nav-cta { text-align: center; margin-top: 0.4rem; }
}

/* ##########################################################################
   PHONE ICON (v13). v12 shipped the medium cut (32px) here because Chief
   flagged the icon as unreadably small at these widths. Investigated
   "give it more room" per Chief's ask (shifting the hamburger right) before
   reaching for it: turned out the room already existed without moving
   anything. v11/v12's own measurement said as much ("medium was chosen ...
   not because large didn't fit") -- clearance at 390/375 with the short
   motto was ~85px/71px, and the large cut only needs ~19px more width than
   medium at these heights, leaving ~66px/52px MEASURED with Playwright,
   still comfortably clear of the toggle. So the large cut (already used at
   401-899px) now simply continues uninterrupted below 400px instead of
   switching to a smaller cut -- one fewer breakpoint, and a visibly bigger,
   more legible icon where Chief flagged it as too small.

   The medium cut (icon-msg-2026-medium.svg) is no longer referenced from
   any page; left in place as a library asset (same precedent as the
   small-16 cut, which was never wired into shipped markup either). */
@media (max-width: 400px) {
  .brand-est-full { display: none; }
  /* Two classes, not one: the unconditional ".brand-est-short { display: none }"
     a few lines down is ALSO a single-class selector, so a bare
     ".brand-est-short { display: block }" here would be an exact specificity
     tie, decided by source order (whichever rule is physically LATER in the
     file), not by which media query is narrower. That tie would have gone the
     wrong way, hiding the short motto below 400px too. Higher specificity here
     removes the ambiguity instead of relying on rule order. */
  .site-header .brand-est-short { display: block; }
}
/* The short motto exists ONLY inside the <=400px branch above; hidden at
   every wider width. */
.brand-est-short { display: none; }

/* ##########################################################################
   v13 -- FULL MOTTO DROPPED IN THE 401-480px ZONE (genuinely crowded).

   Between 401px (where the <=400px icon-swap rule above ends) and 900px
   (where the nav collapses to the hamburger), the header shows the LARGE
   icon + full wordmark + full motto ("PERFECTLY SQUARE · EST. 1856") next
   to the hamburger/X toggle, all in the same row. Clearance between the
   motto's right edge and the toggle's left edge shrinks linearly as the
   viewport narrows (clearance = width - 379.34px, MEASURED with Playwright,
   nav open and closed -- the X and the bars occupy the same 44px box, so
   crowding is identical either way).

   MEASURED, not guessed: clearance is 120.66px at 500px (comfortable) but
   only 40.66px at 420px and 21.66px at 401px (confirmed both by computed
   geometry AND by screenshot -- the motto visibly crowds the toggle at these
   widths). 480px is the breakpoint: clearance there is 100.66px, matching
   the comfortable end of the range rather than the tight end, so the drop
   happens well before it would ever read as crowded.

   Below 480px: drop the full motto ENTIRELY (not a shortened version --
   Chief's instruction was explicit that a still-tight short form should also
   be dropped rather than truncated further). Icon + wordmark only. The
   short motto ("EST. 1856") does NOT appear here -- it's scoped to the
   <=400px block above and stays there, where the icon shrinks too and
   clearance is ~117px+, genuinely uncrowded. */
@media (min-width: 401px) and (max-width: 480px) {
  .brand-est-full { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-toggle-bars,
  .nav-toggle-bars::before,
  .nav-toggle-bars::after { transition: none; }
}

/* ---------- Hero (built around the 1910 South Side Square) ---------- */
/* PHOTO TREATMENT — production's values, restored. v6 used grayscale with no
   sepia, brightness(0.8), and a 0.78/0.85/0.92 overlay; the buildings turned
   to mud. Production's sepia duotone at brightness(0.9) under a much lighter
   0.62/0.74/0.88 overlay is what lets the Main Street storefronts actually
   read. Every foreground here is cream or seafoam (the core rule), and the
   worst-case bright patch of sky was measured from the rendered page, not
   assumed — see the handoff. */
.hero {
  position: relative;
  color: var(--cream);
  background: var(--forest-deep);
  overflow: hidden;
  text-align: center;
  padding: clamp(4.5rem, 9vw, 6.5rem) 0 clamp(4.75rem, 10vw, 7rem);
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  /* image is pre-cropped: postcard caption removed from the file itself */
  background: url("/img/hero-southside-1910.png") center 40% / cover no-repeat;
  /* v17: Design's locked filter (handoff package "Hero photo tint
     exploration.zip", Option 1 / "warm neutral sepia"). Less full grayscale
     and less saturation than v14's own guess at this same goal, which is
     why v14 still read faintly green -- this is Design's own measured
     value, not a re-derivation. */
  filter: grayscale(0.8) sepia(0.5) saturate(0.85) contrast(1.05) brightness(0.98);
  z-index: 0;
}
/* ##########################################################################
   v17 -- REBUILT FROM AN ACTUAL DESIGN HANDOFF, not a description of one.
   v14-v16 were built from Chief's text relay of what Design approved, and
   drifted further from it each round despite every individual fix being
   correctly reasoned from what I could see. This round Chief forwarded the
   real package (README + measured HTML comps + reference PNGs), which
   changes the approach entirely: NOT a full-bleed gradient (v14-v16) at any
   width, but a flat, near-invisible wash for tonal cohesion, plus two
   separate LOCALIZED radial scrims -- one dark, behind the headline/subhead
   only, one light, behind the kicker only. Everywhere else in the photo is
   untouched. See .hero-scrim-dark / .hero-scrim-light below for the scrims
   themselves; this layer is ONLY the flat wash. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(42, 36, 34, 0.12);
  z-index: 1;
}
.hero-inner { max-width: 44rem; margin: 0 auto; position: relative; z-index: 2; }
/* ##########################################################################
   THE TWO SCRIMS. Design's own comp positioned these with fixed percentages
   of a FIXED-HEIGHT mockup (511/415/549px at 1440/768/390) -- our real hero
   has no fixed height; it sizes to content, and that content's own line
   count shifts unpredictably with viewport (measured: the H1 wraps to 2
   lines at 1440 AND 375 but NOT at 768 or 390, because of how the 6.5vw
   clamp interacts with each width -- not a simple "desktop vs mobile"
   split). Copying Design's fixed percentages would misplace the scrim at
   whichever widths don't match their mockup's line-wrap assumptions.

   Fix: position both scrims as PERCENTAGES OF .hero-inner (not .hero, and
   not fixed px) -- .hero-inner's own height already adapts to however many
   lines each child wraps to, so a scrim sized this way automatically grows
   with a 2-line headline instead of needing a per-breakpoint override.
   Measured every element's top/bottom as %-of-hero-inner across ten widths
   from 320 to 1440, covering every line-wrap combination that actually
   occurs: H1's own top ranges only 12.2%-19.4% regardless of wrap state;
   the deepest content (tagline or actions, whichever wraps) never exceeds
   ~95%. The dark scrim's 8%-96% span covers that full range with margin at
   every width tested, not just the three Design happened to check. */
/* v18: SHRUNK BACK DOWN. v17 inflated both scrims (dark: 260%x160% of
   hero-inner; light: 150% wide) chasing strict WCAG pass at every single
   pixel column, including the hardest recurring edge case (the hanging
   sign bracket crossing "MISSOURI"). That made both scrims big enough to
   visibly wash out the photo -- Chief's own screenshot compared against
   Design's tightened comp and called it out directly: photo clarity comes
   first, text only needs to stay MINIMALLY legible, not pass a strict
   per-pixel floor everywhere. Sized close to Design's own literal spec
   (92%/82% of the hero, ~420x70px for the kicker) again, re-centered on the
   actual text block rather than padded out for contrast headroom. This
   trades away the "0 failing columns at every width" result from v17 for a
   visibly clearer photo, on Chief's explicit instruction -- contrast
   numbers below are reported honestly, not chased back up by re-inflating. */
.hero-scrim-dark {
  position: absolute;
  left: 50%;
  top: 2%;
  width: 105%;
  height: 76%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, rgba(42, 36, 34, 0.65) 0%, rgba(42, 36, 34, 0.4) 45%, rgba(42, 36, 34, 0) 74%);
  z-index: 0;
  pointer-events: none;
}
/* Kicker's own scrim, INVERTED (light on the still-mostly-bright sky the
   kicker sits on), sized close to Design's literal ~420x70px (desktop) /
   ~345x58px (tablet) / ~220x40px (mobile) -- NOT a percentage of
   .hero-inner's width, which is what let this balloon in v17. Centered on
   the kicker's own vertical mid-point, which (unlike the headline) is a
   FIXED distance from .hero-inner's top regardless of viewport, because
   .hero-kicker's font-size never changes with a clamp() the way the
   headline's does -- measured 15.36px (0.96rem) at 1440, 768, AND 390px,
   identically. */
.hero-scrim-light {
  position: absolute;
  left: 50%;
  top: 0.96rem;
  width: 26.25rem;
  height: 4.4rem;
  transform: translate(-50%, -50%);
  background: radial-gradient(ellipse at center, rgba(245, 236, 218, 0.6) 0%, rgba(245, 236, 218, 0.32) 50%, rgba(245, 236, 218, 0) 80%);
  z-index: 0;
  pointer-events: none;
}
/* Shrinks with the viewport, per spec: ~420x70px desktop, ~345x58px
   tablet, ~220x40px mobile -- matched to this project's existing 900px
   (nav collapse) and 600px (mobile type/spacing tier) breakpoints rather
   than inventing new ones. */
@media (max-width: 900px) {
  .hero-scrim-light { width: 21.6rem; height: 3.63rem; }
}
@media (max-width: 600px) {
  .hero-scrim-light { width: 13.75rem; height: 2.5rem; }
}
/* Both scrims are position:absolute, which paints them ABOVE static-flow
   siblings regardless of DOM order or z-index:0 -- a real bug caught by
   rendering, not just assumed: the kicker/headline/tagline/actions are
   normal-flow (no `position`), so without this rule the scrims painted OVER
   the text, not behind it, making everything look faded/grayed-out. Lifting
   the whole text stack into the positioned layer with z-index:1 (above the
   scrims' z-index:0, still below nothing else since .hero-inner's own
   z-index:2 already bounds this stacking context) restores the correct
   order: photo -> flat wash -> scrims -> text. */
.hero-kicker, .hero h1, .hero-tagline, .hero-actions {
  position: relative;
  z-index: 1;
}
/* v17: kicker is FOREST-DEEP -- Design's locked spec (measured 6.78-11.43:1
   against the real photo across all three widths in their comp, best margin
   of every color tested: cream 1.84:1 fail, forest 5.38:1, forest-deep
   6.78:1). NOTE FOR CHIEF: v15 was told explicitly to use plain --forest,
   "not forest-deep," as your own call overriding the contrast math. This
   handoff's locked spec uses --forest-deep instead, with numbers to back it.
   Implemented per the handoff since it's the most recent, most measured
   instruction, but flagging the direct conflict with the v15 ask rather
   than silently overriding it -- confirm which one should actually ship. */
.hero-kicker {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: 0.24em;
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--forest-deep);
  margin-bottom: 1.1rem;
  /* Design's exact value -- paired with .hero-scrim-light above, not a
     substitute for it (text-shadow alone was tested and rejected in the
     handoff's "Round 4": technically clears 3:1 but with ~0 margin, since
     WCAG contrast only counts text-color-vs-actual-pixel, not the shadow). */
  text-shadow: 0 1px 3px rgba(245, 236, 218, 0.75), 0 0 10px rgba(245, 236, 218, 0.5);
}
.hero-kicker::before,
.hero-kicker::after {
  content: "";
  display: inline-block;
  width: 1.5rem;
  height: 3px;                     /* LINE, not production's diamond */
  background: var(--forest-deep);  /* v17: matches the kicker text */
  margin: 0 0.9rem;
  vertical-align: 0.25em;
  /* Dashes sit outside the kicker text node, so they don't inherit its
     text-shadow -- same light halo, reimplemented as a box-shadow so the
     glow reads as one continuous treatment across the whole line. */
  box-shadow: 0 1px 3px rgba(245, 236, 218, 0.75), 0 0 10px rgba(245, 236, 218, 0.5);
}
.hero h1 {
  font-size: clamp(2.7rem, 6.5vw, 4.6rem);
  font-weight: 700;   /* v3b: gravitas — the site's one big statement */
  letter-spacing: -0.005em;
  /* v17: Design's exact value, paired with .hero-scrim-dark above. */
  text-shadow: 0 3px 20px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.35);
}
/* SOLID cream, not production's 0.94 alpha — see the text-on-dark rule. */
.hero-tagline {
  font-size: clamp(1.2rem, 2.6vw, 1.45rem);
  line-height: 1.55;
  color: var(--cream);
  max-width: 34rem;
  margin: 1.3rem auto 2.4rem;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.35);   /* v17: Design's exact value */
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}
.hero a:not(.button) { color: var(--cream); }

/* ---------- Buttons ---------- */
.button {
  display: inline-block;
  font-family: var(--font-label);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  border: 2px solid transparent;
  border-radius: 4px;
  padding: 0.8rem 1.8rem;
  cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease, transform 140ms ease, box-shadow 140ms ease;
}
.button:hover { transform: translateY(-1px); }
.button:active { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .button, .card { transition: none !important; }
  .button:hover { transform: none; }
}

/* ##########################################################################
   THE BUTTON LANGUAGE — ONE RULE, SITE-WIDE. Settled; do not re-litigate.

     Primary button on a DARK ground   = CREAM fill + WINE label   (9.05:1)
     Primary button on a LIGHT ground  = WINE fill + WHITE label  (10.63:1)

   WINE ALWAYS MEANS "ACTION". The pairing simply inverts with the background:
   on light the action is a wine field, on dark it is a wine label on a light
   field. Either way the eye learns one colour for "this is the thing to click."

   SEAFOAM IS NEVER A BUTTON COLOUR. It is for rules, the motto, nav hover,
   scroll-spy borders, and focus rings on dark grounds. A seafoam CTA was
   built, measured and rejected: it separated from the hero ground at 4.83:1
   against cream's 7.71, so it read weaker than the outline button next to it.
   ########################################################################## */

/* ###### EVERY BUTTON ON A LIGHT GROUND IS WINE (designer, round 2) ######
   Wine fill + WHITE label: 10.63:1 AAA for the text, and the fill's own edge
   against cream is 9.05 / against cream-tint 8.06 / against white 10.63, so
   the button SHAPE reads as cleanly as its label — the failure mode that
   killed production's gold badge doesn't apply here.
   Forest-filled buttons read institutional rather than inviting, so forest is
   no longer a button colour on light grounds. It keeps its structural role:
   section h2, card top borders, card h3, the Upcoming tag, focus borders.
   .button-forest and .button-secondary are RETIRED — both are aliased to the
   wine treatment below so any stale markup (or a future page) still lands on
   the right thing instead of silently reintroducing a forest button. */
.button-primary,
.button-forest,
.button-secondary {
  background: var(--wine);
  color: var(--paper);            /* WHITE label — 10.63:1 */
  border-color: var(--wine);
}
.button-primary:hover,
.button-forest:hover,
.button-secondary:hover {
  background: var(--wine-deep);
  border-color: var(--wine-deep);
  color: var(--paper);            /* 13.28:1 on wine-deep */
  box-shadow: 0 6px 16px rgba(87, 28, 46, 0.3);
}
/* v18: "See Events" sits directly on the hero photo, not a flat colour --
   Chief flagged the label as hard to read against it. A transparent ghost
   button has nothing of its own to read against; giving it a small dark
   fill (a scrim "inside the button," not just behind the text) means its
   label and border always have a known-dark backdrop regardless of what
   photo detail happens to be behind them at a given breakpoint. Lighter
   than the existing hover fill (0.25 vs 0.35) so hover still reads as a
   state change, not a jump to the same tone.
   v19: the scrim was built from --forest-deep (10, 46, 39), which reads as
   an unintended green tint on the button -- forest is a structural/brand
   token, not a neutral scrim colour, and the core rule reserves it for
   light-ground structure (h2, card tops, tags), not a dark-ground fill.
   Swapped to --ink's raw value (42, 36, 34), the same warm neutral already
   used for the hero's flat photo wash (.hero::after), at the same alpha
   steps -- but "same alpha steps" carried the v18 contrast numbers forward
   UNVERIFIED against the new colour; see v20.
   v20: MEASURED from real screenshots (not CSS tokens): v19's 0.25 rest
   alpha put cream-on-scrim at 4.16:1 (desktop) / 4.09:1 (mobile) -- both
   below the 4.5:1 AA floor this text needs (16px/600 weight, under the
   18.66px/700 large-text bar, confirmed against this file's own
   .hero-kicker precedent). Raised rest to 0.35 (was hover's old value) --
   re-measured 4.86:1 / 4.75:1, both clear with real margin, not a
   razor's-edge pass. */
.button-ghost {
  background: rgba(42, 36, 34, 0.35);
  color: var(--cream);
  border-color: var(--cream);
}
/* Hover rule violation fixed: the old 16% CREAM fill lightened the ground
   behind the label, taking cream-on-hero from 7.71 to 5.16 even though the
   text colour never changed. A lightening fill under light text is the same
   regression in disguise. Now the label and border brighten to white and the
   fill DARKENS instead.
   v20: hover alpha raised 0.35 -> 0.45 to hold the same 10-point gap above
   the new rest value (0.35) -- rest catching up to hover's old value would
   have flattened the hover state into a non-event. Re-measured:
   6.64:1 (desktop) / 6.51:1 (mobile), a real, visible jump from rest's
   4.86 / 4.75, not just numerically higher. */
.button-ghost:hover {
  background: rgba(42, 36, 34, 0.45);
  color: var(--paper);
  border-color: var(--paper);
}

/* THE HERO CTA — cream fill, wine label (9.05:1). Decided; see the button
   language block above .button-primary. The hero is a dark ground so the fill
   inverts, but the label stays wine, which is what keeps this button in the
   same family as the five wine buttons further down the page.
   A seafoam-fill variant was built and measured against this one and rejected:
   its fill separated from the hero ground at only 4.83:1 versus cream's 7.71,
   so it read weaker than the ghost button beside it and inverted the
   primary/secondary hierarchy. Do not reintroduce it. */
.hero .button-primary {
  background: var(--cream);
  color: var(--wine);
  border-color: var(--cream);
  font-weight: 700;
}
.hero .button-primary:hover {
  background: var(--paper);
  border-color: var(--paper);
  color: var(--wine-deep);        /* 13.28:1 on white */
  box-shadow: 0 6px 18px rgba(10, 46, 39, 0.5);
}

/* Disabled — fade the FILL, never the label, so contrast holds. */
.button:disabled,
.button[aria-disabled="true"] {
  opacity: 1;
  background: var(--ink-muted);
  border-color: var(--ink-muted);
  color: var(--paper);              /* 8.98:1 */
  cursor: not-allowed;
  transform: none;
}

/* ---------- Sections ---------- */
.section { padding: var(--section-pad) 0; }
/* Alternating ground. Body is cream; tinted sections step down to cream-tint,
   with cream-deep hairlines. Both are production values. */
.section-tint {
  background: var(--cream-tint);
  box-shadow: inset 0 1px 0 var(--cream-deep), inset 0 -1px 0 var(--cream-deep);
}

/* Forest anchors the section headings on light grounds (production). Wine gets
   the eyebrow directly above it, so every section header carries both. */
.section h2 {
  font-size: clamp(2rem, 4vw, 2.7rem);
  color: var(--forest);
  margin-bottom: 0.75rem;
  text-wrap: balance;
}
.section-lede {
  font-size: 1.2rem;
  color: var(--ink-muted);
  margin: 0.5rem 0 2.2rem;
  text-wrap: pretty;
}
.narrow-lede { max-width: 46rem; }

/* WINE eyebrow. v7 raises the weight 500 -> 700 (production used 700): Chief
   read these as low-contrast, and the ratio was already AAA (9.05 on cream) —
   the problem was optical, a thin small-caps line, not a contrast failure.
   NOTE this deviates from MSG-Design-Spec.md §2, which fixes kickers at weight
   500. Flagged in the handoff for reconciliation. */
.eyebrow {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 1rem;
  font-weight: 700;
  color: var(--wine);
  margin-bottom: 0.7rem;
}

.mission-text {
  font-family: var(--font-body);
  font-size: clamp(1.35rem, 2.4vw, 1.6rem);
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink);
  margin-bottom: 1.3rem;
  text-wrap: pretty;
}

/* ---------- Get Involved: the recruitment ladder ---------- */
.ladder {
  list-style: none;
  counter-reset: rung;
  margin-bottom: 2.2rem;
}
.ladder li {
  counter-increment: rung;
  position: relative;
  padding-left: 3.4rem;
  padding-bottom: 1.6rem;
}
.ladder li:last-child { padding-bottom: 0; }
/* Wine rung markers, cream label (9.05:1) — wine recurring down the page. */
.ladder li::before {
  content: counter(rung);
  position: absolute;
  left: 0;
  top: 0.1rem;
  width: 2.2rem;
  height: 2.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--wine);
  color: var(--cream);
  font-family: var(--font-label);
  font-weight: 700;
  font-size: 1rem;
}
/* Connector sits on cream-tint, where cream-deep would be ~1.1:1 and vanish.
   Forest-tint is 5.33:1 on cream — the designated functional-border token. */
.ladder li::after {
  content: "";
  position: absolute;
  left: 1.1rem;
  top: 2.5rem;
  bottom: 0.4rem;
  width: 2px;
  background: var(--forest-tint);
}
.ladder li:last-child::after { display: none; }
.ladder h3 {
  font-size: 1.3rem;
  color: var(--forest);
  margin-bottom: 0.25rem;
}
.ladder p { color: var(--ink-muted); margin: 0; }

.ladder-cta-note {
  font-family: var(--font-label);
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 0.9rem;
}

/* ---------- Cards ---------- */
.card {
  background: var(--paper);
  border: 1px solid var(--cream-deep);
  border-top: 4px solid var(--forest);
  border-radius: var(--radius-card);
  padding: clamp(1.5rem, 2.5vw, 2rem);
  box-shadow: var(--shadow-card);
  transition: box-shadow 160ms ease, transform 160ms ease;
}
.event-card:hover {
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .event-card:hover { transform: none; }
}
.card h3 {
  font-size: 1.35rem;
  color: var(--forest);
  margin-bottom: 0.7rem;
}
.card p { margin-bottom: 1.4rem; }
.card p:last-child { margin-bottom: 0; }

.updates-card { max-width: 34rem; }

/* ---------- Events ---------- */
/* Events is the only section on the page using the plain 68rem .wrap instead
   of .narrow (46rem) -- every other section's content is capped narrow, so
   it keeps generous margins from the viewport edge for free. Events needs
   the extra width for the 3-up card grid, but that leaves it hugging the
   edge with only .wrap's base 1.25rem gutter at common desktop widths,
   reading as cramped next to every narrower section. Desktop-only (matches
   the site's existing 900px tablet/mobile cutoff, used everywhere else for
   this split) extra gutter closes that gap without touching mobile/tablet,
   which were never the complaint. */
@media (min-width: 901px) {
  #events > .wrap { padding-left: 3rem; padding-right: 3rem; }
}
.event-list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: 1.4rem;
  margin-bottom: 2rem;
}
/* Production's move, restored: event cards are WINE-topped with WINE titles.
   This is the single biggest reason production's events grid had life and
   v6's didn't — five wine headings recurring down the page. */
.event-card { border-top-color: var(--wine); }
.event-card h3 { color: var(--wine); margin-bottom: 0.6rem; }
.event-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.7rem;
  margin-bottom: 0.9rem;
}
.event-date {
  font-family: var(--font-label);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.01em;
  color: var(--ink-muted);
}
/* Status badges. Production used GOLD for "Ongoing"; v7 does not, because the
   gold pill's edge against a white card is 1.93:1 — the text was legible but
   the badge shape wasn't. Wine instead. B&W GUARDRAIL: every badge also
   carries its own WORD, so colour is never the only signal. */
.event-tag {
  font-family: var(--font-label);
  font-size: 1rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--cream);
  background: var(--forest);        /* Upcoming — 9.90:1 */
  border-radius: 3px;
  padding: 0.22rem 0.55rem;
}
.event-tag--ongoing { background: var(--wine); color: var(--cream); }   /* 9.05:1 */
.event-tag--recent  { background: var(--cream-deep); color: var(--ink); } /* 10.35:1 */
/* Outline, so it stays distinct from the solid wine "Ongoing" pill beside it
   on the Farmers Market card. */
.event-tag--msg {
  background: transparent;
  color: var(--forest);
  border: 1.5px solid var(--forest);
  padding: calc(0.22rem - 1.5px) calc(0.55rem - 1.5px);
}
.event-blurb { margin-bottom: 0; }
.events-more {
  font-family: var(--font-label);
  font-weight: 700;
}

/* ---------- Forms (contact + join) ---------- */
.contact-form { max-width: 34rem; }
.field { margin-bottom: 1.3rem; }
.field label {
  display: block;
  font-family: var(--font-label);
  font-weight: 700;
  font-size: 1rem;
  color: var(--ink);
  margin-bottom: 0.4rem;
}
/* Input borders are FUNCTIONAL (they define the interactive region), so 3:1
   applies: forest-tint is 6.26 on white, 5.33 on cream. */
.field input,
.field textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--forest-tint);
  border-radius: 4px;
  padding: 0.7rem 0.8rem;
  transition: border-color 120ms ease;
}
.field input:focus-visible,
.field textarea:focus-visible {
  border-color: var(--forest);
  outline-color: var(--wine);
  outline-offset: 2px;
}
.field textarea { resize: vertical; }
/* A placeholder is NEVER a label — every input here has a real one. If one is
   ever added it must still clear AA, hence ink-muted (8.98) not a browser grey. */
.field input::placeholder,
.field textarea::placeholder {
  color: var(--ink-muted);
  opacity: 1;
}
.field input:disabled,
.field textarea:disabled {
  background: var(--cream-deep);
  color: var(--ink);              /* 10.35:1 — never grey the text */
  border-color: var(--ink-muted);
  cursor: not-allowed;
}
.field-hint {
  display: block;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1rem;
  color: var(--ink-muted);
  margin-top: 0.15rem;
}

.field-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status {
  font-family: var(--font-label);
  font-weight: 700;
  color: var(--ink);
  margin-top: 1rem;
  min-height: 1.5rem;
}
/* Both states change their WORDS as well as their colour. */
.form-status.is-success { color: var(--forest); }   /* 11.62 on white */
.form-status.is-error { color: var(--wine); }       /* 10.63 on white */

/* ---------- Footer (forest-deep band + seafoam accents) ---------- */
.site-footer {
  background: var(--forest-deep);
  color: var(--cream);
  border-top: 3px solid var(--seafoam);   /* production ran this in gold; v8 seafoam */
  padding: 3.5rem 0 1.5rem;
  font-family: var(--font-body);
}
.footer-brand-row {
  display: flex;
  align-items: flex-end;   /* same roofline fix as .brand — see its comment */
  gap: 0.7rem;
  margin-bottom: 0.6rem;
}
/* Footer mark is UNCHANGED this pass. It must explicitly undo the header's
   roofline treatment (the taller height, the flex-end self-alignment and the
   negative pull), or the shared .brand-mark rule would drag it down through
   the footer's padding too. If the roofline idea is later extended to the
   footer, this is the block to change. */
.footer-brand-row .brand-mark {
  height: 2.3rem;
  align-self: auto;
  margin-bottom: 0;
}
.footer-inner {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 2rem;
  align-items: start;
  margin-bottom: 2.5rem;
}
.footer-brand {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.3rem;
  color: var(--cream);
}
/* SEAFOAM on forest-deep (7.83:1). This is the "Connect" heading Chief
   flagged as low-contrast back in v6; it has been an accent colour since. */
.footer-heading {
  font-family: var(--font-label);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 1rem;
  color: var(--seafoam);
  margin-bottom: 0.6rem;
}
address { font-style: normal; line-height: 1.6; color: var(--cream); }
/* Footer links: SOLID cream + underlined, seafoam on hover. Production faded
   these to 0.78 alpha; v7 keeps them solid at 12.49:1. */
.site-footer a {
  color: var(--cream);
  text-decoration: underline;
  text-decoration-color: currentColor;
}
/* Hover rule violation fixed: this went cream -> seafoam, i.e. 12.49 -> 7.83,
   DARKER on a dark ground. Now cream -> white, 12.49 -> 14.65. */
.site-footer a:hover { color: var(--paper); }
.footer-link { color: var(--cream); }
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.4rem 2rem;
  border-top: 1px solid rgba(245, 236, 218, 0.26);
  padding-top: 1.2rem;
  font-size: 1rem;
  color: var(--cream);            /* SOLID — production used 0.78 alpha */
}
.footer-legal a { color: var(--cream); }
.back-to-top { font-family: var(--font-label); font-weight: 700; white-space: nowrap; }

/* ---------- Anchor offset under the sticky header ---------- */
/* Tracks the header's ACTUAL measured height via --header-height, redefined
   for ≤600px in the mobile tier below. .join-hero (join.html's #top) is
   included because it's a real anchor target (the footer's "Back to top") that
   .hero/.section never covered. v5 fix, carried forward. */
.section,
.hero,
.join-hero { scroll-margin-top: var(--header-height); }

/* Scroll-spy active state: nav link for the section in view */
.site-nav a[aria-current="true"] {
  border-bottom-color: var(--seafoam);
}

/* ==========================================================================
   Walking Tour — kept as the one section with its own flavour. Production set
   it apart with gold, but gold is illegal on this cream-tint ground (1.46:1),
   so v7 sets it apart with FOREST instead: forest eyebrow, forest card top,
   forest CTA, against the wine used everywhere else on light grounds.
   ========================================================================== */
#walking-tour .eyebrow { color: var(--forest); }
#walking-tour .card { border-top-color: var(--forest); }
#walking-tour .card h3 { color: var(--forest); }

/* ==========================================================================
   /join — Main Street Insiders signup
   ========================================================================== */

/* The "Insiders" lockup: display serif with "Insiders" in WINE (spec §5).
   Wine on cream is 9.05:1. NOTE the newsletter and print versions of this
   lockup still show flame and are out of parity — flagged in the handoff. */
.insiders-lockup {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.005em;
}
.insiders-lockup em {
  font-style: normal;
  color: var(--wine);
}
/* On any DARK ground the lockup flips to seafoam — wine there is 1.09:1. The
   site only uses this lockup on /join's light hero today, but the newsletter
   masthead and footer treatments put it on forest, so the rule exists before
   it's needed rather than after someone ships an invisible wordmark. */
.site-header .insiders-lockup em,
.site-footer .insiders-lockup em,
.hero .insiders-lockup em { color: var(--seafoam); }

.join-hero {
  background: var(--cream-tint);
  border-bottom: 1px solid var(--cream-deep);
  padding: clamp(2.75rem, 6vw, 4rem) 0 clamp(2.25rem, 5vw, 3rem);
  text-align: center;
}
.join-hero h1 {
  font-size: clamp(2.2rem, 5.5vw, 3.4rem);
  color: var(--forest);
  margin-bottom: 0.9rem;
}
.join-lede {
  font-size: clamp(1.1rem, 2.2vw, 1.3rem);
  color: var(--ink);
  max-width: 38rem;
  margin: 0 auto;
  text-wrap: pretty;
}

.join-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: start;
}
@media (max-width: 820px) {
  .join-layout { grid-template-columns: 1fr; }
}

.join-form { max-width: 34rem; }
.join-required-note {
  font-size: 1rem;
  color: var(--ink-muted);
  margin-bottom: 1.5rem;
}

/* "What you get" list — wine LINE markers (not production's diamonds) */
.join-value { list-style: none; }
.join-value li {
  padding-left: 1.9rem;
  position: relative;
  margin-bottom: 1rem;
}
.join-value li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 1.1rem;
  height: 3px;
  background: var(--wine);
  border-radius: 2px;
}
.join-value li:last-child { margin-bottom: 0; }

/* Cream panel on the page ground. Its 1px edge is forest-tint, not cream-deep:
   cream-deep on cream-tint is ~1.1:1 and would disappear entirely. */
.join-aside {
  background: var(--paper);
  border: 1px solid var(--forest-tint);
  border-top: 4px solid var(--wine);
  border-radius: var(--radius-card);
  padding: clamp(1.5rem, 2.5vw, 2rem);
}
.join-aside h2 {
  font-size: 1.6rem;
  color: var(--forest);
  margin-bottom: 1.1rem;
}
.join-optout {
  font-size: 1rem;
  color: var(--ink-muted);
  margin-top: 1.5rem;
}

/* ==========================================================================
   MOBILE DENSITY TIER (v4/v5) — phone widths ONLY, distinct from the
   desktop/tablet scale above. Chief's complaint: desktop is right, phone reads
   oversized and airy. Everything here is scoped to <=600px; nothing touches
   the 640px/900px breakpoints used above for the header edge-padding tweak and
   the nav-toggle collapse (900px, MEASURED from the lockup -- see its own
   comment block), so tablet (601-900px) keeps the desktop type and
   spacing scale, just with the hamburger nav.

   GUARDRAILS THIS BLOCK RESPECTS:
   - Body text never drops below 16px. Body is untouched at 1.125rem (18px) —
     only line-height changes here, not size.
   - No tap target drops below ~44px: .nav-toggle keeps 2.75rem (only its inner
     bar glyph shrinks); inputs keep enough vertical padding that measured
     height stays >=44px (28.8px line + 8px padding x2 + 2px border x2 ~= 48.8px).
   - Colours are untouched in this block, so the gate needs no extra pairs for
     it — re-run anyway, still green.

   v6/v7 DEVIATION FROM v5, REVERTED: v5 set line-height 1.5 here; v6/v7 raised
   it to the design spec's 1.6 floor for this audience. Chief called it back
   down to 1.5, mobile only -- desktop/tablet body line-height (1.6, base rule
   above this media query) is untouched. MSG-Design-Spec.md §2's 1.6 floor is
   now out of parity with shipped mobile CSS; flagged for reconciliation.
   ========================================================================== */
@media (max-width: 600px) {
  /* One override reaches every .section on the page (Mission, Get Involved,
     Events, Walking Tour, Updates, Contact) since they all read --section-pad
     through `.section { padding: var(--section-pad) 0; }`.
     v5: 2rem (32px) — the floor of the requested 32-40px band, after a second
     tightening pass. OUTER section padding only; inner element spacing from
     v4 is untouched. */
  :root {
    --section-pad: 2rem;
    /* MEASURED at this breakpoint — mobile header is 63px, not 73px, because
       the nav-toggle (still 44px, untouched) becomes the tallest flex child
       once visible below 900px, while the icon shrinks below it. */
    --header-height: 63px;
  }

  body { line-height: 1.5; }   /* Chief's call, back to v5 density -- see the block header note */

  /* ---------- Header: shorter row, single-line wordmark + motto ---------- */
  .header-inner { padding-top: 0.5rem; padding-bottom: 0.5rem; }
  /* v12: same both-paddings punch-through as the base rule, scaled to this
     tier's own padding (P=8px, not desktop's 12.8px). height = 2*8+44-2 = 58px;
     margin-top = g(2)-P(8) = -6px; margin-bottom stays -P = -8px (unchanged).
     Header stays 63.00px: the icon's flex-line contribution is still exactly
     44px (58-6-8=44), so header-inner's own computed height is untouched.
     v13: this is now the ONLY header-icon rule below 900px -- the icon stays
     at this same 58px size all the way down to 320px (see the phone-icon
     section further down; there is no smaller-cut swap below 400px anymore). */
  .brand-mark { height: 3.625rem; margin-top: -0.375rem; margin-bottom: -0.5rem; }
  .brand-wordmark { font-size: 1.05rem; }
  /* 16px floor applies here too, so tracking drops to 0.02em to keep the motto
     on ONE line beside the 44px hamburger at 390px. Measured, not guessed —
     see the header-height/overflow readings in the handoff. */
  /* Width-matching the wordmark is NOT achievable at this breakpoint and is
     deliberately not attempted: the wordmark drops to 1.05rem here while the
     motto is pinned at the 16px floor, so closing an 86px gap would need
     about -0.17em, which would visibly cram the letters. Tracking is set to
     0 for the tightest legible setting and the mismatch is reported. */
  .brand-est { font-size: 1rem; letter-spacing: 0; }

  /* Tap target (.nav-toggle) stays 2.75rem/44px — untouched. Only the visible
     glyph inside it shrinks, so the button reads less bulky without losing
     hit area. */
  .nav-toggle-bars,
  .nav-toggle-bars::before,
  .nav-toggle-bars::after { width: 1.2rem; }
  .nav-toggle-bars::before { top: -0.4rem; }
  .nav-toggle-bars::after { top: 0.4rem; }

  /* ---------- Hero: shorter, denser, kicker on one line ---------- */
  .hero { padding: 2rem 0 2.25rem; }
  .hero-kicker {
    letter-spacing: 0.08em;   /* was 0.24em — the line ornaments are hidden
                                 here, so tracking no longer fights them for
                                 space. Font-size/weight untouched: that pair
                                 is what keeps the cream kicker at the 3:1
                                 large-text bar over the photo. */
    margin-bottom: 0.85rem;
  }
  .hero-kicker::before,
  .hero-kicker::after { display: none; }
  .hero h1 { font-size: 2.2rem; }   /* one step down from the 2.7rem desktop floor */
  .hero-tagline { margin: 0.9rem auto 1.6rem; }
  .hero-actions { gap: 0.7rem; }
  .hero .square-rule { margin-top: 1rem; }

  /* ---------- Section rhythm: tighter heading/lede/paragraph spacing ------- */
  .section h2 { margin-bottom: 0.55rem; }
  .section-lede { margin: 0.4rem 0 1.4rem; }
  .eyebrow { margin-bottom: 0.5rem; }
  .mission-text { margin-bottom: 1rem; }

  .ladder { margin-bottom: 1.5rem; }
  .ladder li { padding-bottom: 1.1rem; }

  .card { padding: 1.25rem; }
  .event-list { gap: 1rem; margin-bottom: 1.4rem; }

  /* ---------- Contact form: tighter field rhythm, inputs stay >=44px tall -- */
  .field { margin-bottom: 0.85rem; }
  .field label { margin-bottom: 0.3rem; }
  .field input,
  .field textarea {
    padding: 0.5rem 0.65rem;   /* down from 0.7rem/0.8rem — still clears 44px;
                                  don't go lower without re-measuring. */
  }

  /* ---------- Footer (v5): address/Connect/legal bar each collapse to one
     fewer line. Done entirely with layout + generated content inside THIS
     media query, not by editing the shared HTML, so desktop's footer is
     byte-for-byte unchanged — merging the underlying <p> elements would also
     have changed how desktop's `justify-content: space-between` distributes
     them across the row. ---------- */

  /* Address: "PO Box 32" / "Gallatin, MO 64640" -> one line. The <br> is
     hidden (not removed) so desktop keeps its real line break; the comma is
     CSS-generated on the wrapping span, so desktop's text is untouched too. */
  address br { display: none; }
  .addr-line1::after { content: ", "; }

  /* Connect: "CONNECT" label + "Facebook" link -> one line. The wrapping div
     becomes a flex row only here; desktop keeps default block stacking. */
  .footer-inner > div:nth-child(2) {
    display: flex;
    align-items: baseline;
    gap: 0.4em;
  }
  .footer-heading { margin-bottom: 0; }
  .footer-heading::after { content: ":"; }

  /* Legal bar: 3 stacked/wrapped lines -> exactly 2. Switching the flex
     container to block layout lets the copyright <p> (still display:block)
     take its own full-width line, then the two remaining <p>s — set to
     display:inline so a block sibling can't force either onto its own line —
     flow together right after it on line 2. */
  .footer-legal {
    display: block;
    padding-top: 1rem;
  }
  .footer-legal p:first-child { margin-bottom: 0.35rem; }
  .footer-legal p:nth-child(2),
  .footer-legal p:nth-child(3) {
    display: inline;
  }
  .footer-legal p:nth-child(2)::after { content: " \00B7 "; }
}


/* ---------- 404 (/404.html) ---------- */
/* The cupola is drawn cream-on-forest for the dark header band, so on this
   light hero it gets its own forest-deep badge (12.49:1, its native pairing)
   rather than being recoloured. Icon stays the large cut of the three-cut
   set, above the 48px floor that cut is drawn for. */
.error-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 5.5rem;
  height: 5.5rem;
  border-radius: 50%;
  background: var(--forest-deep);
  margin-bottom: 1.25rem;
}
.error-mark img {
  display: block;
  width: auto;
  height: 3.25rem;
}
.error-next { text-align: center; }
.error-next .section-lede { margin-left: auto; margin-right: auto; }
.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-top: 1.75rem;
}
.error-crosslink {
  margin-top: 2rem;
  color: var(--ink-muted);
  max-width: 38rem;
  margin-left: auto;
  margin-right: auto;
}
