/* ==========================================================================
   Chula Vista Ventures, global stylesheet

   Every token, type-scale value and component spec comes from
   reference/brand.md (CVV Visual Identity Guide, Direction 02).

   Never hardcode a colour in a page. Use a custom property.
   Contrast ratios for every pair used here are recorded in BUILD-LOG.md.

   Order: custom properties, reset, base typography, layout, components,
   nav, footer, motion, media queries.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Custom properties
   -------------------------------------------------------------------------- */

:root {
  /* Core palette */
  --navy: #0D2B4D;
  --ocean: #1565C0;
  --teal: #00A8B5;
  --green: #2E9E6F;
  --gold: #F5B400;
  --gold-hover: #DCA200;

  /* Supporting */
  --navy-deep: #081B31;
  --ink: #14202E;
  --ink-2: #4A5A6A;
  --ink-3: #7A8A99;
  --rule: #DDE3E9;
  --surface: #FFFFFF;
  --surface-2: #F4F7F9;
  --surface-mint: #EAF4F0;

  /* Form errors. Colour never carries an error on its own here: every one is
     paired with text and wired through aria-describedby. Ratios in BUILD-LOG.md */
  --error: #B3261E;
  --error-link: #8C1D18;

  /* Alert glyph for .field__error, drawn as a mask so it takes currentColor
     and needs no icon font or third-party request */
  --icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.5 15 14H1L8 1.5Z' fill='none' stroke='%23000' stroke-width='1.6' stroke-linejoin='round'/%3E%3Cpath d='M8 6v3.4' stroke='%23000' stroke-width='1.6' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='11.6' r='0.9' fill='%23000'/%3E%3C/svg%3E");

  /* The one approved gradient. Hero and footer bands only */
  --grad-bay: linear-gradient(135deg, #0D2B4D 0%, #14477A 55%, #00A8B5 100%);

  /* Alpha values over navy, so no page needs a raw colour.
     Each one is contrast-checked in BUILD-LOG.md */
  --on-navy-strong: rgba(255, 255, 255, 0.75);
  --on-navy-link: rgba(255, 255, 255, 0.78);
  --on-navy-fine: rgba(255, 255, 255, 0.55);
  --on-navy-soft: rgba(255, 255, 255, 0.5);
  --border-white: rgba(255, 255, 255, 0.7);
  --divider-white: rgba(255, 255, 255, 0.12);
  --field-white: rgba(255, 255, 255, 0.08);

  /* Hero scrim over photography, and the two white weights that sit on it.

     Strengthened 9 September 2026, from 0.72/0.30 at the 45% and 100% stops.
     Removing the hero lockup lifted every line of the hero by about 90px,
     and the lead landed on the bright sky and rooftops instead of the dark
     water it used to sit over. Re-measured against the photograph pixel by
     pixel, the lead came out 3.95:1, under the 4.5 floor for 20px text.

     Full white on the lead alone would have reached 4.84, which clears AA by
     0.34 and would not survive the hero photograph being swapped, which is
     still open. Both changes together put the worst line at 5.95. */
  --hero-scrim: linear-gradient(100deg,
    rgba(8, 27, 49, 0.84) 0%,
    rgba(13, 43, 77, 0.72) 42%,
    rgba(13, 43, 77, 0.34) 70%,
    rgba(13, 43, 77, 0.04) 100%);

  /* Below 1024px there is no right third left to give back. .hero__content is
     760px wide, so at 1024 it covers three quarters of the frame and at 768
     the hero goes auto-height and the type fills it top to bottom. A gradient
     that clears to 0.04 then puts the ghost button and the note on bare
     photograph: measured 2.80:1 and 4.30:1 at 375.

     So the narrow scrim runs down rather than across, and it only comes down
     a little. The lightening in this prompt is a desktop gain. Say that
     plainly rather than claiming a change the small screens do not get. */
  --hero-scrim-narrow: linear-gradient(160deg,
    rgba(8, 27, 49, 0.86) 0%,
    rgba(13, 43, 77, 0.74) 45%,
    rgba(13, 43, 77, 0.64) 100%);
  --on-hero-lead: rgba(255, 255, 255, 1);
  /* The spec asks for 0.6, which measures 3.47:1 at 13px over the bright water.
     Prompt 03 raised it to 0.85. Re-measured in prompt 07 against the real
     rendered pixels under the actual glyphs, 0.85 comes out 4.37:1, still just
     under AA. 0.95 measures 4.99:1. See BUILD-LOG.md */
  --on-hero-note: rgba(255, 255, 255, 0.95);

  /* Bottom-weighted scrim for the Third Avenue hero, whose arch carries its
     own words. The spec's softer version measured 1.28:1 at the eyebrow, so
     the strong band reaches 78% and only the top 10% stays open. The narrow
     variant covers more again, because the hero goes auto-height under 768px
     and the text climbs to about 15% from the top. See BUILD-LOG.md */
  --hero-scrim-arch: linear-gradient(to top,
    rgba(8, 27, 49, 0.94) 0%,
    rgba(8, 27, 49, 0.90) 58%,
    rgba(13, 43, 77, 0.80) 78%,
    rgba(13, 43, 77, 0.34) 90%,
    transparent 100%);
  --hero-scrim-arch-narrow: linear-gradient(to top,
    rgba(8, 27, 49, 0.94) 0%,
    rgba(8, 27, 49, 0.92) 70%,
    rgba(13, 43, 77, 0.86) 88%,
    rgba(13, 43, 77, 0.40) 96%,
    transparent 100%);

  /* "Why here, and why now" over the photograph, prompt 14.

     Directional, not flat. The eyebrow, the heading and the four numerals sit
     on the left, so the dark runs from the left edge and opens towards the
     right. cv-why-now is a sunset frame and the sun sits in the right third,
     which is the brightest part of any picture on this site, so the second
     layer is a bottom weight under the step copy and the button. Both are
     measured over the composited pixels rather than set by eye. Ratios in
     BUILD-LOG.md, prompt 14 */
  --whynow-scrim:
    linear-gradient(to top,
      rgba(8, 27, 49, 0.50) 0%,
      rgba(8, 27, 49, 0.30) 46%,
      rgba(8, 27, 49, 0.10) 100%),
    linear-gradient(100deg,
      rgba(8, 27, 49, 0.90) 0%,
      rgba(13, 43, 77, 0.76) 38%,
      rgba(13, 43, 77, 0.42) 72%,
      rgba(13, 43, 77, 0.26) 100%);

  /* --grad-bay ends on teal, where white text is 2.89:1 and gold 1.57:1.
     This navy scrim sits over the gradient so both pass. See BUILD-LOG.md */
  --grad-scrim: rgba(13, 43, 77, 0.6);
  /* 0.6 leaves gold at 4.01:1, fine for large numerals but under AA for a
     12px eyebrow. The plain hero uses this deeper one. See BUILD-LOG.md */
  --grad-scrim-deep: rgba(13, 43, 77, 0.78);
  --on-grad-body: rgba(255, 255, 255, 0.8);

  /* Shadows */
  --shadow-card: 0 1px 3px rgba(13, 43, 77, 0.06);
  --shadow-card-hover: 0 10px 30px rgba(13, 43, 77, 0.10);
  --shadow-nav: 0 2px 14px rgba(13, 43, 77, 0.08);

  /* Typefaces */
  --font-head: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Display serif, added by prompt 13 from Sara's layout. This overrides the
     Montserrat heading spec in reference/brand.md for headings only.

     THE FAMILY IS NOT CONFIRMED. Sara's layout shows a serif and does not
     name it. Playfair Display is the documented interim: it is free, and it
     is the family named in the August 2026 design brief the serif appears to
     come from. Changing it later is this one line. See BUILD-LOG.md
     question 61.

     The fallback stack is real serifs, not a sans, so a slow or blocked
     Google Fonts request degrades to something of the right shape rather
     than swapping the whole page back to Montserrat. */
  --font-display: "Playfair Display", "Iowan Old Style", "Palatino Linotype",
                  Palatino, Georgia, "Times New Roman", serif;

  /* Type scale */
  --size-display: clamp(40px, 6vw, 72px);
  --size-h1: clamp(34px, 4.5vw, 52px);
  --size-h2: clamp(26px, 3vw, 38px);
  --size-h3: 22px;
  --size-h4: 18px;
  --size-body: 17px;
  --size-lead: 20px;
  --size-eyebrow: 12px;
  --size-caption: 14px;
  --size-stat: clamp(36px, 4vw, 56px);

  /* Layout */
  --container: 1160px;
  --prose: 720px;
  --gutter: 24px;
  --section-y: 96px;
  --radius: 6px;
  --radius-card: 10px;
  --tap: 44px;
  --nav-h: 72px;

  /* The nav at scroll 0, before .scrolled brings it down to --nav-h. The
     mark widths pair with those two heights. Section 9 */
  --nav-h-tall: 104px;
  --nav-mark-w: 59px;
  --nav-mark-w-tall: 82px;
  --t-nav: 0.26s ease;

  /* Motion */
  --t-fast: 0.18s ease;
  --t-base: 0.2s ease;
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--size-body);
  font-weight: 400;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

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

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

ul,
ol { margin: 0; padding: 0; }

h1, h2, h3, h4, h5, h6, p, figure, blockquote { margin: 0; }

/* --------------------------------------------------------------------------
   3. Base typography
   -------------------------------------------------------------------------- */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--navy);
  line-height: 1.15;
  text-wrap: balance;
}

.display {
  font-family: var(--font-display);
  font-size: var(--size-display);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.05;
}

h1 {
  font-size: var(--size-h1);
  font-weight: 800;
  letter-spacing: -0.02em;
}

h2 {
  font-size: var(--size-h2);
  font-weight: 700;
  letter-spacing: -0.015em;
}

h3 { font-size: var(--size-h3); font-weight: 700; }
h4 { font-size: var(--size-h4); font-weight: 600; }

/* H4 type on an h3 element, where the outline needs the level but the
   design wants the smaller size. Keeps heading order sequential */
.as-h4 {
  font-family: var(--font-display);
  font-size: var(--size-h4);
  font-weight: 600;
}

p + p { margin-top: 1.1em; }

.lead {
  font-size: var(--size-lead);
  line-height: 1.6;
  color: var(--ink-2);
}

/* A lead that follows a heading needs air. Saves every page repeating it */
h1 + .lead,
h2 + .lead,
.display + .lead { margin-top: 18px; }

/* Ocean, not teal. The prompt 02 contrast table already ruled that teal is
   "icons and rules only" because it measures 2.89:1 on white, but the eyebrow
   was still carrying 12px text in it. Ocean is 5.75:1. Teal survives on dark
   backgrounds, where it measures 4.94:1. See BUILD-LOG.md */
.eyebrow {
  font-family: var(--font-body);
  font-size: var(--size-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ocean);
  margin-bottom: 14px;
}

/* Captions use --ink-2, not --ink-3. At 14px regular, --ink-3 measures
   3.55:1 on white, below AA for body-size text. See BUILD-LOG.md */
.caption {
  font-size: var(--size-caption);
  color: var(--ink-2);
}

/* --ink-3 is reserved for large text and non-essential UI */
.muted { color: var(--ink-3); }

/* Dark sections invert the heading and body colours */
.on-dark,
.on-dark h1,
.on-dark h2,
.on-dark h3,
.on-dark h4 { color: var(--surface); }

.on-dark .lead,
.on-dark p { color: var(--on-navy-strong); }

.on-dark .eyebrow { color: var(--teal); }

/* --------------------------------------------------------------------------
   4. Links and focus
   -------------------------------------------------------------------------- */

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

a:hover { color: var(--navy); }

.on-dark a { color: var(--surface); }

/* Focus ring: 2px teal at 3px offset over a navy inner ring.
   Teal alone measures 2.89:1 on white, under the 3:1 that WCAG 2.1
   SC 1.4.11 asks of a non-text indicator. The navy ring gives the teal a
   compliant neighbour on every background the site uses. See BUILD-LOG.md */
:focus { outline: none; }

:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--navy);
  border-radius: 2px;
}

/* On navy the inner ring flips to white so it stays visible */
.on-dark :focus-visible,
.section--districts :focus-visible,
.cta-band :focus-visible,
.statbar :focus-visible,
.site-footer :focus-visible {
  box-shadow: 0 0 0 3px var(--surface);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--navy);
  color: var(--surface);
  padding: 14px 20px;
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus-visible {
  left: 12px;
  top: 12px;
  color: var(--surface);
}

/* --------------------------------------------------------------------------
   5. Layout
   -------------------------------------------------------------------------- */

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

.prose { max-width: var(--prose); }

.section { padding-block: var(--section-y); }

/* A section left as an anchor for a later prompt still renders its own
   192px of padding, which reads as a hole in the page rather than as a
   placeholder. Collapse it until something is inside it */
.section:empty { padding-block: 0; }
.section--alt { background: var(--surface-2); }
.section--mint { background: var(--surface-mint); }
.section--navy { background: var(--navy); }
.section--navy-deep { background: var(--navy-deep); }

.grid { display: grid; gap: 28px; }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }

.stack > * + * { margin-top: 18px; }

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

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.btn-primary,
.btn-secondary,
.btn-ghost-white {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 15px 30px;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--t-fast), color var(--t-fast),
              border-color var(--t-fast), transform var(--t-fast);
}

.btn-primary {
  background: var(--gold);
  color: var(--navy);
  border: 2px solid var(--gold);
}

.btn-primary:hover {
  background: var(--gold-hover);
  border-color: var(--gold-hover);
  color: var(--navy);
  transform: translateY(-1px);
}

/* Outlined gold, from Sara's layout. Used on the navy districts panel and
   nowhere else so far. Gold on navy is 7.76:1, so the label clears AA
   comfortably; the border is the same colour and carries the affordance. */
.btn-outline-gold {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 26px;
  border: 2px solid var(--gold);
  border-radius: var(--radius-btn);
  background: transparent;
  color: var(--gold);
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--t-base), color var(--t-base), transform var(--t-base);
}

.btn-outline-gold:hover {
  background: var(--gold);
  color: var(--navy);
  transform: translateY(-1px);
}

.btn-outline-gold:visited { color: var(--gold); }
.btn-outline-gold:hover:visited { color: var(--navy); }

.btn-secondary {
  background: transparent;
  border: 2px solid var(--ocean);
  color: var(--ocean);
}

.btn-secondary:hover {
  background: var(--ocean);
  color: var(--surface);
  transform: translateY(-1px);
}

.btn-ghost-white {
  background: transparent;
  border: 2px solid var(--border-white);
  color: var(--surface);
}

.btn-ghost-white:hover {
  background: var(--surface);
  border-color: var(--surface);
  color: var(--navy);
  transform: translateY(-1px);
}

/* --------------------------------------------------------------------------
   7. Card
   -------------------------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 28px;
  transition: box-shadow var(--t-base), transform var(--t-base),
              border-color var(--t-base);
}

.card:hover {
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-3px);
  border-color: var(--teal);
}

/* A card that is itself a link needs the focus ring back.

   :focus-visible sets box-shadow for the navy inner ring, and .card sets
   box-shadow for its resting shadow. Both are one specificity class and
   .card comes later, so the card won and the ring came out teal-only.
   Teal alone is 2.89:1 on --surface and 2.79:1 on --surface-2, under the
   3:1 a non-text indicator needs. Restating both shadows here keeps the
   two-tone ring the brand system requires */
.card:focus-visible {
  box-shadow: var(--shadow-card), 0 0 0 4px var(--navy);
}

.card h3 { margin-bottom: 10px; }

/* --------------------------------------------------------------------------
   8. Stat bar
   -------------------------------------------------------------------------- */

/* 40px, down from 56. The sources note used to close the band and prompt 14
   moved it to the footer, which left a block of empty navy under the four
   figures. The items carry 8px of their own padding, so this is the band
   sized by what is in it. */
.statbar {
  background: var(--navy);
  padding-block: 40px;
}

/* Four fixed columns, not auto-fit. auto-fit reflowed to two or three
   columns at in-between widths, which put a left-hand gold rule at the
   start of a wrapped row and made the dividers look arbitrary */
.statbar__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  align-items: stretch;
}

/* Each cell is its own container so the numeral can size itself against the
   column rather than the viewport. All four columns are the same width, so
   all four numerals resolve to the same size and share a baseline */
/* Every cell carries the 2px border, transparent on the first, so all four
   content boxes are exactly the same width. Without that the first cell was
   2px wider, resolved a slightly larger numeral from its own width, and sat
   a pixel off the baseline of the other three */
.statbar__item {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  padding: 8px 24px;
  border-left: 2px solid transparent;
}

/* A 2px gold rule between items. align-items: stretch above makes every
   cell the full row height, so every rule is the same length */
.statbar__item + .statbar__item { border-left-color: var(--gold); }

.statbar__value {
  display: block;
  font-family: var(--font-head);
  font-size: var(--size-stat);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--surface);
  font-variant-numeric: tabular-nums;
}

/* "53 million" is nearly three times the width of "4th" and used to wrap to
   two lines at full size, which pushed its label down and broke the row. It
   never wraps now: every cell resolves the same size from its own width, so
   the longest value sets the size and all four stay level.

   19cqi is measured, not guessed. At 1440 the content box is 230px and
   "53 million" needs 277px at 56px, so 44px is the largest that fits. 19cqi
   lands at 43.7px and leaves 14px of headroom for font loading. --size-stat
   stays the ceiling, so a wide single column still renders the full size */
.statbar__grid .statbar__value {
  white-space: nowrap;
  font-size: min(var(--size-stat), 19cqi);
}

/* One line of numerals in every cell, so every label starts level */
.statbar__label {
  display: block;
  margin-top: 10px;
  font-size: 15px;
  font-weight: 400;
  line-height: 1.45;
  color: var(--on-navy-strong);
}

/* One sentence per stat, saying why the number matters. --on-navy-strong
   measures 8.63:1 over --navy, so 14px clears AA with room. Line height
   rather than font size is what gives at 375px: four extra sentences push
   the band down, and 1.5 keeps it inside two screens where 14px does not.
   See BUILD-LOG.md */
.statbar__why {
  display: block;
  margin-top: 14px;
  max-width: 32ch;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--on-navy-strong);
}

/* --------------------------------------------------------------------------
   9. Navigation
   -------------------------------------------------------------------------- */

/* THE PARTIAL SLOT MUST NOT BE A BOX, or the sticky nav below it cannot
   stick. main.js injects the nav into <div data-partial="nav">, and a sticky
   element only sticks inside its containing block: that div is exactly the
   height of the bar, so the bar left the top of the screen the moment the div
   did. Found by the prompt 10 audit, at 2000px of scroll the nav measured
   top: -2000.

   It had been declaring itself sticky since prompt 02 and never was. Three
   other rules assume it is: .site-nav.scrolled paints a shadow for a bar
   floating over content, .dmap__head offset itself 96px to clear it back when
   it was sticky, and .dmap__entry carries the same scroll-margin-top.

   The wrapper becomes the sticky element instead, so the bar it holds sticks
   to the viewport rather than to a 73px div.

   The min-height is the second half of the fix. The slot is empty until the
   fetch lands, so every page dropped `main` by the height of the bar the
   moment it did: a 0.0507 layout shift on all nine, and the only shift any of
   them had. Reserving the COMPACT height rather than the tall one is
   deliberate. The bar is 105px at scroll 0 and settles to 73px, so reserving
   105 would hold a 32px band of empty white under the nav for the whole of
   the rest of the page. This way there is never a gap, and the residual shift
   is the 32px of growth rather than the whole bar. A reader who asks for
   reduced motion gets no shift at all, because the bar never grows for them. */
[data-partial="nav"] {
  position: sticky;
  top: 0;
  z-index: 100;
  min-height: calc(var(--nav-h) + 1px);
}

.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
  transition: box-shadow var(--t-base);
}

.site-nav.scrolled { box-shadow: var(--shadow-nav); }

/* The bar starts tall with a larger mark and settles to --nav-h once the
   page moves. main.js already toggles .scrolled past 20px, so this is a
   class and two transitions rather than anything measured per frame.
   Both properties run on the same duration so they land together */
.site-nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  min-height: var(--nav-h-tall);
  transition: min-height var(--t-nav);
}

.site-nav.scrolled .site-nav__inner { min-height: var(--nav-h); }

/* Logo. The only lockup that exists is stacked, so at nav height its
   wordmark is unreadable. The mark sits beside Montserrat type instead,
   matching the footer. Swap both for cvv-lockup-navy.svg when it lands */
.site-nav__logo {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  gap: 12px;
  text-decoration: none;
  flex: 0 0 auto;
}

.site-nav__mark {
  width: var(--nav-mark-w-tall);
  height: auto;
  aspect-ratio: 59 / 36;
  object-fit: contain;
  transition: width var(--t-nav);
}

.site-nav.scrolled .site-nav__mark { width: var(--nav-mark-w); }

.site-nav__wordmark {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 13px;
  line-height: 1.15;
  letter-spacing: 0.06em;
  color: var(--navy);
  text-transform: uppercase;
}

.site-nav__link[aria-current="page"] {
  color: var(--ocean);
  box-shadow: inset 0 -2px 0 0 var(--teal);
}

.site-nav__cta {
  flex: 0 0 auto;
  padding: 12px 22px;
}

/* --------------------------------------------------------------------------
   10. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--navy-deep);
  color: var(--surface);
}

.site-footer__band {
  height: 3px;
  background: var(--grad-bay);
}

.site-footer__main {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1.4fr;
  gap: 40px;
  padding-block: 64px 44px;
}

/* Reversed lockup workaround: the mark beside Montserrat type in white.
   The full-colour lockup must never go on navy, its wordmark is navy and
   would disappear. Replace with cvv-lockup-white.svg when it lands */
.site-footer__logo {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}

.site-footer__mark {
  width: 66px;
  height: 40px;
  object-fit: contain;
}

.site-footer__wordmark {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 14px;
  line-height: 1.2;
  letter-spacing: 0.08em;
  color: var(--surface);
  text-transform: uppercase;
}

.site-footer__blurb {
  font-size: 15px;
  color: var(--on-navy-strong);
  max-width: 30ch;
}

.site-footer__col h3 {
  color: var(--surface);
  margin-bottom: 16px;
}

.site-footer__list {
  list-style: none;
  display: grid;
}

/* 44px tall, so the tap target clears the minimum without the column
   growing the way a 44px box plus a gap would */
.site-footer__list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-size: 15px;
  color: var(--on-navy-link);
  text-decoration: none;
  transition: color var(--t-fast);
}

.site-footer__list a:hover {
  color: var(--surface);
  text-decoration: underline;
}

.site-footer__note {
  margin-top: 12px;
  font-size: 12px;
  color: var(--on-navy-fine);
}

/* Newsletter */
.newsletter__intro {
  font-size: 15px;
  color: var(--on-navy-strong);
  margin-bottom: 16px;
}

.newsletter__row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.newsletter__input {
  flex: 1 1 180px;
  min-height: var(--tap);
  padding: 12px 14px;
  background: var(--field-white);
  border: 1px solid var(--border-white);
  border-radius: var(--radius);
  color: var(--surface);
  font-size: 15px;
}

.newsletter__input::placeholder { color: var(--on-navy-fine); }

.newsletter__row .btn-primary {
  flex: 0 0 auto;
  padding: 12px 22px;
}

/* Bottom bar */
.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 22px;
  border-top: 1px solid var(--divider-white);
  font-size: 14px;
  color: var(--on-navy-strong);
}

.site-footer__legal {
  list-style: none;
  display: flex;
  gap: 22px;
}

.site-footer__legal a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--on-navy-link);
  text-decoration: none;
}

.site-footer__legal a:hover {
  color: var(--surface);
  text-decoration: underline;
}

/* The stat-bar citation, moved here from the navy band by prompt 14. Same
   12px and same --on-navy-fine as the disclaimer directly below it, so the
   two read as one block of small print rather than as two weights. */
.site-footer__sources {
  padding-top: 20px;
  font-size: 12px;
  line-height: 1.7;
  color: var(--on-navy-fine);
}

/* The two stack, so the disclaimer drops its own top padding when the
   sources line is already carrying it */
.site-footer__sources + .site-footer__disclaimer { padding-top: 8px; }

/* The disclaimer. Required on every page. Never shorten it */
.site-footer__disclaimer {
  padding-block: 20px 40px;
  font-size: 12px;
  line-height: 1.7;
  color: var(--on-navy-fine);
}

/* --------------------------------------------------------------------------
   11. Reveal on scroll
   -------------------------------------------------------------------------- */

/* The hidden state is armed by main.js, never by CSS alone. If the script
   fails or is blocked, the content stays visible instead of disappearing */
[data-reveal].reveal-armed {
  opacity: 0;
  transform: translateY(18px);
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s ease, transform 0.5s ease;
}

/* --------------------------------------------------------------------------
   11b. Page sections

   Built for the Home page, written to be reused by pages 04 to 07.
   -------------------------------------------------------------------------- */

/* Hero over photography */
.hero {
  position: relative;
  display: flex;
  align-items: center;
  height: 88vh;
  min-height: 620px;
  overflow: hidden;
  background: var(--navy);
}

.hero__media {
  position: absolute;
  inset: 0;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hero__scrim {
  position: absolute;
  inset: 0;
  background: var(--hero-scrim);
}

.hero__inner {
  position: relative;
  z-index: 1;
  width: 100%;
}

.hero__content { max-width: 760px; }

/* The hero lockup is gone. It repeated the nav logo a few inches below
   itself, which read as a mistake rather than as emphasis. The nav grows at
   scroll 0 to carry that first impression instead. See section 9 */
.hero h1 { color: var(--surface); }

.hero__lead {
  max-width: 620px;
  margin-top: 20px;
  font-size: var(--size-lead);
  line-height: 1.6;
  color: var(--on-hero-lead);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 34px;
}

.hero__meta { margin-top: 48px; }

.hero__rule {
  width: 64px;
  height: 2px;
  margin-bottom: 14px;
  background: var(--gold);
}

.hero__note {
  font-size: 13px;
  line-height: 1.6;
  color: var(--on-hero-note);
}

/* Gold eyebrow, for dark sections only */
.eyebrow--gold { color: var(--gold); }

/* Centred prose block */
.measure-centre {
  max-width: var(--prose);
  margin-inline: auto;
  text-align: center;
}

.measure-centre .eyebrow { margin-bottom: 14px; }
.measure-centre h2 { margin-bottom: 22px; }
.measure-centre p { color: var(--ink-2); }

/* A centred column, but the running prose inside it reads left-aligned */
.prose-left { text-align: left; }

/* Three pillars */
.pillars {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 36px;
}

.pillar__icon {
  width: 40px;
  height: 40px;
  margin-bottom: 18px;
  color: var(--teal);
}

.pillar h3 { margin-bottom: 10px; }

.pillar p {
  color: var(--ink-2);
  margin-bottom: 10px;
}

.text-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: var(--tap);
  font-size: 15px;
  font-weight: 600;
  color: var(--ocean);
  text-decoration: none;
}

.text-link:hover {
  color: var(--navy);
  text-decoration: underline;
}

.text-link svg {
  width: 16px;
  height: 16px;
}

/* Gradient band. The scrim keeps white and gold legible on the teal end */
.section--grad {
  position: relative;
  background: var(--grad-bay);
}

.section--grad::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grad-scrim);
}

.section--grad > * {
  position: relative;
  z-index: 1;
}

/* "Why here, and why now".

   Prompt 13 put the photograph in a box beside the four steps, to close open
   question 41: text over a picture needs a scrim, and that scrim had been
   strengthened twice to hold a 12px eyebrow at 4.5:1, which is an overlay
   doing work the photograph should do.

   Prompt 14 puts the picture back behind the type, full bleed, on Alex's
   review. So the question is open again and the answer this time is the
   shape of the scrim rather than its strength: --whynow-scrim runs dark from
   the left edge where the type is and opens towards the right, with a bottom
   weight under the step row. Every role is measured over the composite at the
   point it lands, not against a token. */
.whynow {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--navy);
}

.whynow__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.whynow__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The sun sits centre-right and the water bottom-left. Holding the frame at
     its centre keeps the cyclist and the path in shot at every width */
  object-position: center;
}

.whynow__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--whynow-scrim);
}

.whynow__inner {
  position: relative;
  z-index: 2;
}

/* The heading holds the left side of the frame, where the scrim is deepest */
.whynow__head { max-width: 34ch; }

/* Four across, one row. Prompt 13 had these in a 2x2 beside the photograph;
   prompt 14 puts the photograph behind the section, so the row has the full
   container again and each step gets about 28 characters on a line. */
.steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px 28px;
  margin-top: 48px;
}

.step__num {
  display: block;
  margin-bottom: 14px;
  font-family: var(--font-head);
  font-size: 40px;
  font-weight: 800;
  line-height: 1;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.step h3 {
  color: var(--surface);
  margin-bottom: 10px;
}

.step p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--on-grad-body);
}

.steps__cta { margin-top: 40px; }

/* Three feature cards. .card supplies the shell and the padding moves inside
   to .feature-card__body, leaving the image flush to the corners */
.feature-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
}

/* The cards moved inside "A platform, not a firm" and follow its paragraph
   directly now. They take the 56px the removed three-up carried, so the
   rhythm of the section is unchanged from the reader's side */
.measure-centre + .feature-cards { margin-top: 56px; }

.feature-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

/* THE CARD IS NOT A LINK ANY MORE, so the affordances that said it was one
   come off. .card lifts 3px, swaps its shadow and turns its border teal on
   hover, and none of that is scoped to links, so these three inherited it and
   kept rising under a pointer with nothing to click. Restating the resting
   values is what cancels it, because .card:hover and this rule are the same
   specificity and this one comes later.

   Two things need no undoing. The focus ring is :focus-visible, which never
   fires on an <article>. The transition is set by [data-reveal].is-visible at
   two classes, which outranks anything written here, and it has nothing left
   to animate now that hover changes no property. */
.feature-card:hover {
  box-shadow: var(--shadow-card);
  transform: none;
  border-color: var(--rule);
}

.feature-card__media {
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
}

.feature-card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.feature-card__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: 26px 28px 20px;
}

.feature-card__body h3 { margin-bottom: 10px; }

.feature-card__body p {
  flex: 1 1 auto;
  color: var(--ink-2);
  margin-bottom: 6px;
}

/* --------------------------------------------------------------------------
   11b-ii. The districts map

   An SVG backdrop with six HTML buttons laid over it. The buttons carry the
   labels and the interaction, which means the browser's own focus ring, key
   handling and hit testing rather than a reimplementation of all three on
   SVG shapes.

   The pins are positioned as percentages of the same 720x560 viewBox the
   artwork uses, fed in from the markup as --x, --y, --w and --h. The stage
   holds that aspect ratio, so a pin sits on its block at every width with
   no measuring and no JavaScript.
   -------------------------------------------------------------------------- */

/* Prompt 13 wraps the whole section in Sara's two-column arrangement: a
   navy panel on the left carrying the heading and the button, the map on
   the right. The section itself goes navy so the panel is the ground rather
   than a box sitting on white. */
.section--districts {
  background: var(--navy);
  color: var(--surface);
}

.section--districts h2 { color: var(--surface); }

/* The heading block, centred above both columns.

   It used to be the left column of a .dmap__layout grid, running about 250px
   tall beside a column closer to 900, and it was given sticky positioning to
   fill the navy that left over. Sticky moved the heading down the section as
   you scrolled, which reads as a second oddity rather than a fix. The panel
   goes beside the map instead and the heading goes over the top of both. */
.dmap__head {
  max-width: var(--prose);
  margin: 0 auto 44px;
  text-align: center;
}

/* The H2 would otherwise run the full 1112px of the container and leave the
   eyebrow stranded above a single line, so the block is capped at --prose */
.dmap__head .dmap__rule { margin-inline: auto; }

/* The short gold rule under the heading in Sara's layout */
.dmap__rule {
  width: 64px;
  height: 3px;
  margin: 22px 0 28px;
  border-radius: 2px;
  background: var(--gold);
}

.dmap {
  display: grid;
  grid-template-columns: 1fr;
  gap: 28px;
  align-items: start;
  min-width: 0;
}

/* Two columns, and only where the map is a control.

   The container is 1160px with 24px gutters, so this grid only ever runs
   between about 1102px and 1112px of content width. The split is picked from
   what the panel holds rather than from a ratio that has to survive a spread:
   a heading, a kicker, up to five fact rows and a paragraph. At 0.85fr /
   1.15fr with a 48px gap the panel measures 441px and the map 597x465 at
   1150, and 452px against 612x476 at 1440.

   THE TALLEST PANEL IS TALLER THAN THE MAP AND WIDENING ITS COLUMN DOES NOT
   FIX THAT. Gaylord Pacific runs 629px against a 465px map at 1150. Six
   splits were measured from 0.7fr/1.3fr to 1.15fr/0.85fr and the panel loses
   height far more slowly than the map does: at 1.15fr/0.85fr the panel is
   still 578px and the map has dropped to 343px, so the difference grows from
   165px to 235px. The panel is tall because it holds five fact rows, not
   because it is narrow. This split is the one that leaves the map largest
   while keeping the panel readable, and the leftover navy sits under the map
   rather than under the heading. See BUILD-LOG.md, prompt 16.

   DOM order is stage then panel. grid-column puts the panel first visually
   and leaves the reading order alone. See the comment in index.html. */
@media (min-width: 1150px) {
  /* 630px, SET BY GAYLORD PACIFIC, measured 17 September 2026.

     Hovering a pin re-renders the panel and the six are different heights. At
     1150 they measure: Gaylord Pacific 629.1, Bayfront 499.4, Residential
     436.1, Border 421.9, University Innovation District 365.8, Broadway
     314.3. Without a floor the grid row resizes under the reader's hand and
     everything below the map jumps as the pointer crosses it. The map's own
     top never moves, because align-items is start.

     THE FLOOR IS ON THE ROW, NOT ON THE CARD. Prompt 16 asked for a
     min-height on .dmap__panel and that was built and looked at first. It
     holds the section still, and it also stretches the white card to 630px
     for every district: Broadway has no facts at all, so half its card was
     empty white, which reads as a rendering fault rather than a short entry.
     A minimum on the row does the same job, because the row is what the rest
     of the page sits on. The card stays the height of its content and the
     space under a short one is navy, which is the section's own ground.

     RE-MEASURE THIS NUMBER if a fact is added to any district, or if a detail
     string in facts.json gets longer. */
  .dmap {
    grid-template-columns: 0.85fr 1.15fr;
    grid-template-rows: minmax(630px, auto);
    gap: 48px;
  }

  /* grid-row: 1 ON BOTH, and it is not optional. The stage is first in the
     DOM and takes column 2, which leaves the auto-placement cursor past
     column 1 of row 1. Sparse packing will not go backwards, so a panel
     asking only for column 1 landed in a second row underneath an empty
     column 2, 510px below the map. Naming the row on both puts them side by
     side and leaves the DOM order alone. */
  .dmap__panel {
    grid-column: 1;
    grid-row: 1;
  }

  .dmap__stage {
    grid-column: 2;
    grid-row: 1;
  }

  /* NO PANEL, NO SECOND COLUMN. districts.js unhides the panel, so with the
     script off or failed the panel stays hidden, display: none takes it out
     of the grid and the map sat in column 2 with 452px of empty navy beside
     it. Caught by loading the page with JavaScript disabled at 1440.

     The map keeps the width it has with the panel next to it rather than
     stretching to the full 1112, where its own 720/560 ratio would make it
     865px tall. Centred under a centred heading.

     :has() is the right selector here and its fallback is the empty column
     this rule removes, which is cosmetic rather than broken. A browser old
     enough to miss :has() and running with JavaScript switched off is not a
     combination worth a second mechanism. */
  .dmap:has(.dmap__panel[hidden]) {
    grid-template-columns: minmax(0, 1fr);
    /* The 630px floor exists to stop a re-rendering panel moving the page.
       With no panel there is nothing to re-render, and the floor would only
       hang 151px of navy under the map */
    grid-template-rows: auto;
    justify-items: center;
  }

  .dmap:has(.dmap__panel[hidden]) .dmap__stage {
    grid-column: 1;
    width: 100%;
    max-width: 616px;
  }
}

.dmap__stage {
  position: relative;
  /* The photograph's own ratio, which is the 720/560 the schematic used */
  aspect-ratio: 720 / 560;
  border-radius: var(--radius-card);
  overflow: hidden;
}

.dmap__img {
  position: absolute;
  inset: 0;
}

.dmap__img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* One pin per district: a frosted pill from Sara's layout, centred on the
   district's own coordinates.

   --w and --h are gone. They sized a tile on the schematic, where a pin was
   drawn to the shape of a district. Over a photograph a pin is a label at a
   point: --x and --y place the anchor and the pill sizes itself to its text.

   ON THE FILL COLOUR. Sara's brief calls these a translucent light fill with
   a white label. Those two do not go together: white on any light ground is
   under 2:1 and no amount of blur fixes it. The instruction that settles it
   is the one that says to give the fill enough opacity to clear 4.5:1, so
   the fill is translucent NAVY. It reads as frosted glass over the picture,
   which is the effect, and the label stays white and legible.

   THE OPACITY IS SET BY THE GROUND, NOT BY THE TOKEN. Six pills over one
   aerial run from dark bay water under pin 1 to pale housing under pin 5, so
   each label is sampled against the pixels actually behind it. Ratios in
   BUILD-LOG.md, prompt 14. */
.dmap__pin {
  /* Distance from the pill's left edge to the centre of its numbered glyph:
     the left padding plus half the glyph. The transform below subtracts it,
     so it is the GLYPH that lands on --x and --y rather than the middle of a
     pill whose width depends on how long the district's name is. */
  --pin-lead: 18px;

  position: absolute;
  left: var(--x);
  top: var(--y);
  transform: translate(calc(var(--pin-lead) * -1), -50%);
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  max-width: min(46%, 168px);
  padding: 7px 14px 7px 7px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: rgba(13, 43, 77, 0.78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--surface);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.25;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(8, 27, 49, 0.28);
  transition: background var(--t-base), border-color var(--t-base);
}

/* The label runs left from the glyph instead of right.

   Pin 3 sits at 89.8% of the frame. Its pill is the longest on the map, so
   running right put "University Innovation District" past the edge of the
   stage, where overflow: hidden cut it in half. Flipping the pill keeps the
   glyph on the same coordinates and turns the label inwards. The padding
   flips with it so the glyph keeps the same 7px on its outer side. */
.dmap__pin--flip {
  flex-direction: row-reverse;
  padding: 7px 7px 7px 14px;
  text-align: right;
  transform: translate(calc(var(--pin-lead) - 100%), -50%);
}

/* Without backdrop-filter the fill is all that separates the label from the
   artwork, so it goes solid. Safari has shipped the prefixed property for
   years and Firefox the unprefixed one since 103, so this reaches old
   builds rather than any current browser, but the failure it prevents is
   white type on bare picture. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .dmap__pin { background: var(--navy); }
}

/* The pin is a link, and .on-dark a and the base link colour would both
   reach it. White whatever the ground says */
.dmap__pin:hover,
.dmap__pin:visited { color: var(--surface); }

/* Two-line labels wrap inside the pill the way Sara's do. The pill is sized
   by its content up to the max-width above, so a long name such as
   "University Innovation District" takes two or three lines rather than
   overrunning the map. */
.dmap__pin-name { min-width: 0; }

/* The tap target, which is not the same thing as the pill.

   A pill is about 30px tall, under the 44px floor, and at 375 several are
   narrow too. This centres an invisible 44x44 minimum on every one, so the
   target meets the floor while the pill stays the size it looks. */
.dmap__pin::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: var(--tap);
  min-height: var(--tap);
}

/* The gold circular glyph on the left of the pill. It keeps the numeral,
   because the no-JavaScript list below is numbered to match and dropping
   the digits would break that pairing. Navy on gold is 7.76:1. */
.dmap__pin-num {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--gold);
  color: var(--navy);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Hover is a preview and never the mechanism, so it only exists where there
   is a real pointer. A touch device fires hover states it cannot dismiss */
@media (hover: hover) and (pointer: fine) {
  .dmap__pin:hover {
    background: rgba(13, 43, 77, 0.92);
    border-color: rgba(255, 255, 255, 0.5);
  }
}

/* Gold marks the district you are on. White on gold is 1.84:1, so the label
   flips to navy, which is 7.76:1 and the same pair as the primary button.
   The glyph inverts with it so it stays visible on its own pill. */
.dmap__pin[aria-expanded="true"],
.dmap__pin[aria-expanded="true"]:hover {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--navy);
}

.dmap__pin[aria-expanded="true"] .dmap__pin-num {
  background: var(--navy);
  color: var(--gold);
}

/* The panel beside the map */
.dmap__panel {
  padding: 28px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  background: var(--surface);
}

.dmap__panel h3,
.dmap__entry h3 { margin-bottom: 6px; }

.dmap__kicker {
  margin-bottom: 18px;
  font-size: 15px;
  font-weight: 600;
  color: var(--ocean);
}

.dmap__body {
  color: var(--ink-2);
  margin-bottom: 14px;
}

.dmap__facts {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  display: grid;
  gap: 14px;
}

.dmap__fact {
  padding-left: 14px;
  border-left: 2px solid var(--gold);
}

.dmap__fact-value {
  display: block;
  font-family: var(--font-head);
  font-size: 22px;
  font-weight: 800;
  line-height: 1.1;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}

.dmap__fact-label {
  display: block;
  margin-top: 2px;
  font-size: 14px;
  color: var(--ink-2);
}

/* Anything facts.json marks as a projection says so on the face of it,
   rather than relying on a sentence elsewhere in the panel surviving an
   edit. renderStat writes data-fact-type onto the element */
.dmap__fact[data-fact-type="projection"] .dmap__fact-label::after {
  content: " A developer projection, not a result.";
  font-weight: 600;
  color: var(--ink);
}

.dmap__fact-detail {
  display: block;
  margin-top: 6px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}

/* The no-JavaScript list. districts.js hides it and takes over.

   The section went navy in prompt 13, and this list is the thing a reader
   with no JavaScript actually gets. Its body copy is --ink-2, which is
   1.55:1 on navy, so every colour in here is restated for the dark ground.
   Miss this and the fallback is invisible to precisely the reader it
   exists for. */
.dmap__list {
  list-style: none;
  margin: 56px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 36px 28px;
}

/* 96px clears the sticky nav. The hero's second button lands on #districts,
   so the section needs the same offset the six blocks already carry, or the
   heading it is aiming at sits behind the bar */
.section--districts,
.dmap__entry { scroll-margin-top: 96px; }

/* SCOPED TO .dmap__list, and the scope is the whole point. The panel beside
   the map is a white card sitting on this navy section, and an earlier pass
   of this rule hung these colours off .section--districts instead. That put
   white body copy and white fact labels on the white panel, which rendered
   as three gold numbers floating over nothing. The fallback list is on navy;
   the panel is not. Only the list gets dark-ground colours. */
.dmap__list .dmap__entry h3 { color: var(--surface); }
.dmap__list .dmap__kicker { color: var(--teal); }
.dmap__list .dmap__body,
.dmap__list .dmap__fact-label,
.dmap__list .dmap__fact-detail { color: var(--on-navy-strong); }
.dmap__list .dmap__fact-value { color: var(--gold); }
.dmap__list .dmap__fact[data-fact-type="projection"] .dmap__fact-label::after {
  color: var(--surface);
}

/* Event cards, shared with the What's On calendar in prompt 05 */
.events {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  margin-top: 40px;
  list-style: none;
}

/* The li used to carry display: flex, which took the marker with it. The
   layout moved to .event-card__inner, so the marker has to go explicitly */
.event-card { list-style: none; }

/* The card is the list item, .event-card__inner is the thing you click. An
   <a> where the event has a url and a <div> where it does not, which is why
   the layout lives on the inner rather than on .event-card itself */
.event-card__inner {
  display: flex;
  flex-direction: column;
  padding: 10px;
  margin: -10px;
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  transition: background var(--t-base);
}

/* The date block and the copy. A column above it holds the optional
   thumbnail, so this row carries the layout the card used to carry */
.event-card__row {
  display: flex;
  gap: 18px;
  align-items: flex-start;
}

/* The thumbnail is optional and the stems have three different native
   aspect ratios, so the box is fixed at 16:9 and the frame is cropped into
   it. Reserving the box here is what stops a shift on load, since the img
   carries no width and height of its own.

   The renderer removes the whole <picture> when the file is missing, so a
   card with no image has no empty box and no margin to collapse */
.event-card__media {
  display: block;
  margin-bottom: 16px;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface-2);
}

.event-card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.event-card--link .event-card__inner:hover { background: var(--surface-2); }
.event-card--link .event-card__inner:hover .event-card__name { color: var(--ocean); }

.event-card__date {
  flex: 0 0 auto;
  width: 62px;
  padding: 10px 0;
  text-align: center;
  border-radius: var(--radius);
  background: var(--surface-mint);
}

.event-card__day {
  display: block;
  font-family: var(--font-head);
  font-size: 24px;
  font-weight: 800;
  line-height: 1;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}

.event-card__month {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.event-card__body { flex: 1 1 auto; }

.event-card__name {
  font-size: var(--size-h4);
  margin-bottom: 6px;
}

.event-card__where {
  font-size: 14px;
  color: var(--ink-2);
  margin-bottom: 12px;
}

.chip {
  display: inline-block;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--surface-mint);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--navy);
}

.events__empty {
  margin-top: 40px;
  padding: 28px;
  border: 1px dashed var(--rule);
  border-radius: var(--radius-card);
  color: var(--ink-2);
}

/* The no-JavaScript nav and footer. Both live in <noscript>, so they only
   ever render when the injected partials cannot. They carry the minimum:
   a way back to the top, a way to reach /connect, the two legal links and
   the disclaimer in full. See BUILD-LOG.md question 64. */
.site-nav--noscript {
  position: static;
  padding: 18px 0;
  border-bottom: 1px solid var(--rule);
}

.site-nav--noscript .site-nav__wordmark {
  display: block;
  font-family: var(--font-head);
  font-size: 15px;
  font-weight: 700;
  color: var(--navy);
  text-decoration: none;
}

.site-footer--noscript {
  padding: 40px 0;
  background: var(--navy-deep);
  color: var(--surface);
}

.site-footer--noscript .site-footer__legal { margin-bottom: 18px; }

/* Closing CTA band */
.cta-band {
  background: var(--navy);
  text-align: center;
}

.cta-band h2 { color: var(--surface); }

.cta-band p {
  max-width: 46ch;
  margin: 16px auto 30px;
  color: var(--on-grad-body);
}

/* Full-bleed photograph behind the closing band, from Sara's layout.

   The scrim is heavy. cv-cta-palms is a sunset shot looking up through palm
   fronds and it is bright across almost the whole frame, with no dark
   quarter to sit type in the way a horizon shot gives you. A light wash
   left the heading under the floor, so this is a near-solid navy that lets
   the palms read as texture rather than as subject. Ratios measured over
   the composited result are in BUILD-LOG.md, prompt 13. */
.cta-band--photo {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.cta-band__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.cta-band__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cta-band__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(ellipse 54% 72% at 50% 50%,
    rgba(8, 27, 49, 0.74) 0%,
    rgba(8, 27, 49, 0.62) 45%,
    rgba(13, 43, 77, 0.22) 78%,
    rgba(13, 43, 77, 0.04) 100%);
}

/* The centred ellipse clears at the edges, and below 1024px the copy reaches
   them: at 375 it measured 2.39:1 on the bright fronds at the end of a line.
   The ellipse widens past the frame here, so what is left of it is the
   vertical shape and the corners stay covered. */
@media (max-width: 1024px) {
  .cta-band__scrim {
    background: radial-gradient(ellipse 130% 76% at 50% 50%,
      rgba(8, 27, 49, 0.82) 0%,
      rgba(8, 27, 49, 0.76) 55%,
      rgba(13, 43, 77, 0.58) 100%);
  }
}

.cta-band--photo > .container {
  position: relative;
  z-index: 2;
}


/* --------------------------------------------------------------------------
   11c. Opportunity page

   Written as named components. Pages 05 to 07 reuse split, pullquote,
   image-band, sequence, projection and note rather than restyling.
   -------------------------------------------------------------------------- */

/* Shorter hero for interior pages */
.hero--short {
  height: 55vh;
  min-height: 420px;
}

.hero--short .hero__lead { margin-top: 18px; }

/* Two columns: prose one side, media or a stat card the other */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: center;
}

.split--wide-left { grid-template-columns: 1.15fr 0.85fr; }

/* A photo column fills its row rather than floating short beside the prose */
.split--media { align-items: stretch; }

.split__body > * + * { margin-top: 18px; }
.split__body .eyebrow { margin-bottom: 14px; }
.split__body h2 { margin-bottom: 22px; }
.split__body p { color: var(--ink-2); }

.split__media {
  border-radius: var(--radius-card);
  overflow: hidden;
}

.split__media img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 380px;
  object-fit: cover;
}

/* Large single stat, on navy */
.stat-card {
  background: var(--navy);
  border-radius: var(--radius-card);
  padding: 48px 40px;
}

.stat-card__value {
  display: block;
  font-family: var(--font-head);
  font-size: var(--size-stat);
  font-weight: 800;
  line-height: 1;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.stat-card__label {
  display: block;
  margin-top: 16px;
  font-size: var(--size-lead);
  line-height: 1.4;
  color: var(--surface);
}

.stat-card__detail {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--divider-white);
  font-size: 15px;
  line-height: 1.6;
  color: var(--on-navy-strong);
}

.stat-card__source {
  display: block;
  margin-top: 14px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--on-navy-soft);
}

/* Pull quote, teal left rule */
.pullquote {
  max-width: 640px;
  margin-top: 40px;
  padding: 22px 0 22px 28px;
  border-left: 4px solid var(--teal);
}

.pullquote p {
  font-family: var(--font-head);
  font-size: var(--size-h3);
  font-weight: 600;
  line-height: 1.4;
  color: var(--navy);
}

/* Full-width image band */
.image-band {
  height: 420px;
  overflow: hidden;
  background: var(--navy);
}

.image-band img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Vertical sequence, gold rules */
.sequence {
  list-style: none;
  margin-top: 40px;
  border-top: 1px solid var(--rule);
}

.sequence__item {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding: 22px 0 22px 20px;
  border-bottom: 1px solid var(--rule);
  border-left: 3px solid var(--gold);
}

/* The Home page numbers its steps in gold, but that band is navy. On white,
   gold measures 2.28:1 and fails. Ocean is 5.75:1 and still reads as an
   accent. The gold stays on the rule, where it is decorative */
.sequence__step {
  flex: 0 0 auto;
  min-width: 3ch;
  font-family: var(--font-head);
  font-size: var(--size-h4);
  font-weight: 800;
  color: var(--ocean);
  font-variant-numeric: tabular-nums;
}

.sequence__text {
  font-family: var(--font-head);
  font-size: var(--size-h4);
  font-weight: 600;
  line-height: 1.4;
  color: var(--navy);
}

/* Six-up and three-up card grids on this page */
.ripple { margin-top: 44px; }

/* A figure that is a projection, not a result. Always labelled */
.projection {
  margin-top: 36px;
  padding: 20px 24px;
  background: var(--surface-2);
  border-radius: var(--radius);
}

.projection__tag {
  display: inline-block;
  margin-bottom: 10px;
  font-family: var(--font-head);
  font-size: var(--size-eyebrow);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ocean);
}

.projection p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

/* Small bordered card carrying one line of credibility */
.credibility {
  margin-top: 28px;
  padding: 22px 26px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
}

.credibility p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

/* Icon inside a card */
.card__icon {
  display: block;
  width: 32px;
  height: 32px;
  margin-bottom: 18px;
  color: var(--ocean);
}

.card p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

.card .statbar__value,
.card__stat {
  display: block;
  margin-top: 18px;
  font-family: var(--font-head);
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}

.card__stat-label {
  display: block;
  margin-top: 8px;
  font-size: var(--size-caption);
  line-height: 1.5;
  color: var(--ink-2);
}

/* Source note under a group of figures.
   The prompt asks for --ink-3. That measures 3.55:1 and fails AA at 14px,
   so this uses --ink-2 at 7.09:1. Same role, same size, legible */
.source-note {
  margin-top: 32px;
  font-size: var(--size-caption);
  line-height: 1.6;
  color: var(--ink-2);
}

.section__foot { margin-top: 36px; }
.section__foot p { color: var(--ink-2); }

/* .on-dark has to beat component colour rules of the same specificity.
   .measure-centre p sets --ink-2 and sits later in this file than
   .on-dark p, which put dark grey body copy on the gradient band at
   1.04:1. Re-stated here, last, so the dark-section signal always wins */
.on-dark p,
.on-dark .lead,
.on-dark li { color: var(--on-grad-body); }

.on-dark h1,
.on-dark h2,
.on-dark h3,
.on-dark h4 { color: var(--surface); }

/* .on-dark a turns every link white, which repaints a gold button white on
   gold: 1.84:1, and the one pair brand.md bans outright. The gold button
   keeps its navy label wherever it sits */
.on-dark .btn-primary { color: var(--navy); }

/* The eyebrow is a <p>, and .eyebrow at 0,1,0 loses to component rules like
   .split__body p and .measure-centre p. That left eyebrows rendering --ink-2
   in some blocks and teal in others. One colour everywhere, in cascade order:
   ocean on light, teal on dark, gold in a hero */
.split__body .eyebrow,
.measure-centre .eyebrow,
.platform .eyebrow,
.prose .eyebrow { color: var(--ocean); }

.on-dark .eyebrow { color: var(--teal); }

/* Gold is the brand system's eyebrow colour on a photographic or scrimmed
   ground, where --teal does not survive what is underneath it.

   The "Why here, and why now" selector changed in prompt 13. It used to be
   .section--photo, matching the band when the photograph sat behind the
   type; the photograph moved beside the type, so the hook is now the
   section class itself. Gold stays rather than reverting to teal: measured
   on the flat gradient it is 7.47:1 against teal's 4.94:1, and the eyebrow
   is 12px, which is the tightest size on the page. */
.hero .eyebrow--gold,
.hero--plain .eyebrow--gold,
.whynow .on-dark .eyebrow--gold,
.dmap__head .eyebrow--gold { color: var(--gold); }

/* --------------------------------------------------------------------------
   11d. Chula Vista page and the What is On calendar
   -------------------------------------------------------------------------- */

/* The attribute has to beat any component display rule, or a hidden pick
   card laid out as a grid stays on screen */
[hidden] { display: none !important; }

/* Hero over the Third Avenue arch.

   The arch carries its own words across the upper middle of the frame, so
   the headline sits low and the overlay is bottom-weighted. A flat scrim
   would muddy the sign without helping the text */
.hero--arch {
  height: 50vh;
  min-height: 440px;
  align-items: flex-end;
}

.hero--arch .hero__media img { object-position: center 30%; }

/* The spec gradient runs 0.92 at the foot to 0.20 at 72%. Measured against
   the composited frame it fails: the arch sits mid-crop at a 50vh letterbox,
   not in the upper band, and its white lettering lands directly under the
   headline. Worst pixels under the text are rgb(196,199,185) at the eyebrow
   and rgb(254,253,235) at the lead, so the eyebrow came out 1.28:1 and the
   H1 2.64:1. The replacement token carries the reasoning */
.hero--arch .hero__scrim { background: var(--hero-scrim-arch); }

.hero--arch .hero__inner { padding-bottom: 56px; }

/* Geography diagram, inline SVG so it inherits the palette */
.geo {
  display: block;
  width: 100%;
  max-width: 300px;
  height: auto;
  margin-inline: auto;
}

.geo__water { fill: var(--teal); opacity: 0.16; }
.geo__spine { stroke: var(--rule); stroke-width: 2; }
.geo__dot { fill: var(--navy); }
.geo__dot--focus { fill: var(--ocean); }

.geo__label {
  font-family: var(--font-head);
  font-size: 13px;
  font-weight: 700;
  fill: var(--navy);
}

.geo__label--muted { fill: var(--ink-2); font-weight: 600; }

.geo__water-label {
  font-family: var(--font-head);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  fill: var(--ink-2);
}

.geo__distance {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  fill: var(--ink-2);
}

.geo__caption {
  margin-top: 18px;
  text-align: center;
  font-size: var(--size-caption);
  line-height: 1.5;
  color: var(--ink-2);
}

/* Grouped list of what is here now */
.listing { margin-top: 44px; }

.listing__group + .listing__group {
  margin-top: 26px;
  padding-top: 26px;
  border-top: 1px solid var(--rule);
}

.listing__group h3 { margin-bottom: 10px; }

.listing__line {
  font-size: var(--size-body);
  line-height: 1.7;
  color: var(--ink-2);
}

/* Numbered list of what is already approved */
.approved { margin-top: 44px; }

.approved__item {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding: 20px 0;
  border-bottom: 1px solid var(--rule);
}

.approved__num {
  flex: 0 0 auto;
  min-width: 3ch;
  font-family: var(--font-head);
  font-size: var(--size-h3);
  font-weight: 800;
  line-height: 1.3;
  color: var(--ocean);
  font-variant-numeric: tabular-nums;
}

.approved__body h3 { margin-bottom: 6px; }

.approved__body p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   The calendar
   -------------------------------------------------------------------------- */

.calendar { margin-top: 40px; }

/* The pick. One at a time, and only when a human has written the line */
.pick {
  padding: 34px 36px;
  border-radius: var(--radius-card);
  background: var(--navy);
}

.pick__chip {
  display: inline-block;
  margin-bottom: 18px;
  padding: 6px 13px;
  border-radius: 999px;
  background: var(--gold);
  font-family: var(--font-head);
  font-size: var(--size-eyebrow);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--navy);
}

.pick__name {
  font-size: var(--size-h3);
  color: var(--surface);
  margin-bottom: 8px;
}

.pick__when {
  font-size: var(--size-caption);
  color: var(--on-navy-strong);
}

.pick__note {
  max-width: 62ch;
  margin-top: 16px;
  font-size: var(--size-body);
  line-height: 1.7;
  color: var(--surface);
}

/* Time tabs */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 36px;
  border-bottom: 1px solid var(--rule);
}

.tab {
  appearance: none;
  border: 0;
  border-bottom: 3px solid transparent;
  background: none;
  padding: 12px 18px;
  min-height: var(--tap);
  font-family: var(--font-head);
  font-size: var(--size-h4);
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  transition: color var(--t-base), border-color var(--t-base);
}

.tab:hover { color: var(--navy); }

.tab[aria-selected="true"] {
  color: var(--navy);
  border-bottom-color: var(--gold);
}

/* Category chips, multi-select.

   The spec asks for white text on --teal. That measures 2.89:1 and fails.
   Navy on the same teal is 4.94:1, so the colour stays and the label flips */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 24px;
}

.chip-filter {
  appearance: none;
  padding: 10px 16px;
  min-height: var(--tap);
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--surface);
  font-family: var(--font-body);
  font-size: var(--size-caption);
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--t-base), color var(--t-base),
              border-color var(--t-base);
}

.chip-filter:hover {
  border-color: var(--ocean);
  color: var(--navy);
}

.chip-filter[aria-pressed="true"] {
  background: var(--teal);
  border-color: var(--teal);
  color: var(--navy);
}

/* Event list. Rows, not cards, so a dense week stays readable */
.event-rows {
  list-style: none;
  margin-top: 12px;
  border-top: 1px solid var(--rule);
}

.event-row { border-bottom: 1px solid var(--rule); }

.event-row__inner {
  display: grid;
  grid-template-columns: 58px 1fr auto;
  gap: 22px;
  align-items: center;
  padding: 20px 14px;
  text-decoration: none;
  color: inherit;
  transition: background var(--t-base);
}

.event-row--link .event-row__inner:hover { background: var(--surface-2); }

.event-row__date { text-align: center; line-height: 1.1; }

.event-row__day {
  display: block;
  font-family: var(--font-head);
  font-size: 28px;
  font-weight: 800;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}

.event-row__month {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.event-row__name { margin-bottom: 4px; }

.event-row--link .event-row__inner:hover .event-row__name { color: var(--ocean); }

.event-row__meta {
  font-size: var(--size-caption);
  line-height: 1.5;
  color: var(--ink-2);
}

.event-row__flag {
  margin-top: 6px;
  font-size: var(--size-caption);
  line-height: 1.5;
  color: var(--ink-2);
  font-style: italic;
}

.event-row__tags {
  display: flex;
  align-items: center;
  gap: 12px;
  white-space: nowrap;
}

.event-row__price {
  font-family: var(--font-head);
  font-size: var(--size-caption);
  font-weight: 700;
  color: var(--ink-2);
}

/* Submit prompt */
.submit-block {
  margin-top: 36px;
  padding: 24px 28px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
}

.submit-block p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

/* Newsletter block, for the visitor not ready for a conversation */
.signup { background: var(--navy); }

.signup h2 { color: var(--surface); }

.signup__lead {
  margin-top: 14px;
  font-size: var(--size-lead);
  color: var(--on-navy-strong);
}

.signup__form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  max-width: 520px;
  margin-top: 28px;
}

.signup__input {
  flex: 1 1 240px;
  min-height: var(--tap);
  padding: 12px 16px;
  border: 1px solid var(--border-white);
  border-radius: var(--radius);
  background: var(--field-white);
  font-family: var(--font-body);
  font-size: var(--size-body);
  color: var(--surface);
}

.signup__input::placeholder { color: var(--on-navy-fine); }

.signup__note {
  margin-top: 14px;
  font-size: 12px;
  color: var(--on-navy-strong);
}

/* --------------------------------------------------------------------------
   11e. How We Work
   -------------------------------------------------------------------------- */

/* Hero with no photograph. The gradient ends on teal, where white is 2.89:1,
   so it carries the same navy scrim the gradient band uses */
.hero--plain {
  position: relative;
  display: flex;
  align-items: center;
  height: var(--hero-h, 45vh);
  min-height: 400px;
  background: var(--grad-bay);
}

/* A deeper scrim than the gradient band uses. --grad-scrim is 0.6, which puts
   gold at 4.01:1 on the teal end. That clears 3:1 for the Home page's 40px
   step numerals but not the 4.5:1 a 12px eyebrow needs, and it measured
   4.02:1 here. 0.78 clears it. See BUILD-LOG.md */
.hero--plain::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grad-scrim-deep);
}

.hero--plain > * {
  position: relative;
  z-index: 1;
}

.hero--plain h1 { color: var(--surface); }
.hero--plain .hero__content { max-width: 900px; }

/* Current focus. Tiles, kept deliberately soft: a name and a line, no
   numbers, no dates, nothing that could read as a list of deals */
.focus-tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-top: 44px;
}

.focus-tile {
  padding: 24px 26px;
  border-radius: var(--radius-card);
  background: var(--surface);
  border: 1px solid var(--rule);
}

.focus-tile__mark {
  display: block;
  width: 32px;
  height: 3px;
  margin-bottom: 16px;
  background: var(--teal);
}

.focus-tile h3 {
  font-size: var(--size-h4);
  font-weight: 600;
  margin-bottom: 8px;
}

.focus-tile p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

/* Values block on navy */
.values__lead {
  font-size: var(--size-lead);
  line-height: 1.6;
}

.values__cta { margin-top: 36px; }

/* --------------------------------------------------------------------------
   11f. Who We Are
   -------------------------------------------------------------------------- */

/* The platform statement. Carries more weight than any single profile:
   wider measure, larger type, and the section to itself */
/* Prompt 07 asks for 50vh against the plain hero's 45vh */
.hero--team { --hero-h: 50vh; min-height: 440px; }

.platform { max-width: 780px; margin-inline: auto; text-align: center; }

.platform h2 {
  font-size: var(--size-h1);
  font-weight: 800;
  letter-spacing: -0.02em;
}

.platform__body { margin-top: 28px; text-align: left; }

.platform__body p {
  font-size: var(--size-lead);
  line-height: 1.65;
  color: var(--ink-2);
}

.platform__body p + p { margin-top: 20px; }

/* One row per person. Portrait left, name and two lines right */
.people { margin-top: 48px; }

.person {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 36px;
  align-items: center;
  padding: 32px 0;
}

.person + .person { border-top: 1px solid var(--rule); }

/* Photo-ready placeholder. Swap the whole element for an <img> at the same
   180px square when the portraits land. See BUILD-LOG.md */
.person__portrait {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 180px;
  height: 180px;
  border-radius: var(--radius-card);
  background: var(--surface-mint);
}

.person__initials {
  font-family: var(--font-head);
  font-size: 44px;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--navy);
}

.person__name { margin-bottom: 4px; }

.person__role {
  font-family: var(--font-body);
  font-size: var(--size-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ocean);
  margin-bottom: 12px;
}

.person__bio {
  max-width: 56ch;
  font-size: var(--size-body);
  line-height: 1.65;
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   11g. Connect
   -------------------------------------------------------------------------- */

.hero--navy {
  display: flex;
  align-items: center;
  height: 40vh;
  min-height: 360px;
  background: var(--navy);
}

.hero--navy h1 { color: var(--surface); }

.hero--navy .hero__lead {
  max-width: 620px;
  margin-top: 18px;
  font-size: var(--size-lead);
  line-height: 1.6;
  color: var(--on-navy-strong);
}

.hero--navy .hero__content { max-width: 820px; }

/* Form left, the other two routes right */
.connect-grid {
  display: grid;
  grid-template-columns: 60fr 40fr;
  gap: 64px;
  align-items: start;
}

/* Fields */
.field + .field { margin-top: 22px; }

.field__label {
  display: block;
  margin-bottom: 7px;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}

.field__req {
  color: var(--ink-2);
  font-weight: 400;
}

.field__input,
.field__select,
.field__textarea {
  display: block;
  width: 100%;
  min-height: var(--tap);
  padding: 14px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--font-body);
  font-size: var(--size-body);
  color: var(--ink);
  transition: border-color var(--t-base);
}

.field__textarea {
  min-height: 120px;
  line-height: 1.6;
  resize: vertical;
}

.field__select {
  appearance: none;
  padding-right: 44px;
  /* Chevron drawn as a background image so no icon font is needed. The stroke
     is --ink-2, which is 7.09:1 on white */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234A5A6A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3,6 8,11 13,6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 16px 16px;
}

.field__input:hover,
.field__select:hover,
.field__textarea:hover { border-color: var(--ink-3); }

/* Error state. Never colour alone: every one of these is paired with text
   and wired to the input through aria-describedby */
.field__input[aria-invalid="true"],
.field__select[aria-invalid="true"],
.field__textarea[aria-invalid="true"] {
  border-color: var(--error);
  border-width: 2px;
}

.field__error {
  margin-top: 7px;
  font-size: var(--size-caption);
  line-height: 1.5;
  color: var(--error);
}

.field__error::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-right: 7px;
  vertical-align: -2px;
  background-color: currentColor;
  -webkit-mask: var(--icon-alert) center / contain no-repeat;
  mask: var(--icon-alert) center / contain no-repeat;
}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   anything that is not rendered */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__actions { margin-top: 28px; }

.form__note {
  margin-top: 14px;
  font-size: 13px;
  line-height: 1.5;
  /* The spec asks for --ink-3, which is 3.55:1 and fails AA at 13px.
     --ink-2 is 7.09:1 at the same size. See BUILD-LOG.md */
  color: var(--ink-2);
}

.btn-primary[disabled] {
  opacity: 0.65;
  cursor: not-allowed;
}

/* Whole-form error, for a failed send rather than a bad field */
.form__alert {
  margin-top: 20px;
  padding: 16px 18px;
  border: 2px solid var(--error);
  border-radius: var(--radius);
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink);
}

.form__alert a { color: var(--error-link); }

/* Replaces the form on success. The URL never changes */
.confirmation {
  padding: 36px 34px;
  border-radius: var(--radius-card);
  background: var(--surface-mint);
}

.confirmation h2 {
  font-size: var(--size-h3);
  margin-bottom: 12px;
}

.confirmation p {
  font-size: var(--size-body);
  line-height: 1.65;
  color: var(--ink-2);
}

.confirmation p + p { margin-top: 12px; }

/* The two other routes in */
.aside-block + .aside-block { margin-top: 24px; }

.aside-block {
  padding: 26px 28px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
}

.aside-block h3 {
  font-size: var(--size-h4);
  font-weight: 600;
  margin-bottom: 8px;
}

.aside-block p {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
  margin-bottom: 16px;
}

.aside-block__form {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.aside-block__form .field__input { flex: 1 1 160px; width: auto; }

.aside-block__list {
  list-style: none;
  margin: 0;
}

.aside-block__list li + li { margin-top: 10px; }

.aside-block__list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-size: 15px;
}

/* The disclaimer, verbatim from reference/language-guardrail.md */
.guardrail-block {
  max-width: 720px;
  margin-inline: auto;
  padding: 28px 32px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  text-align: center;
  font-size: 15px;
  line-height: 1.7;
  color: var(--ink-2);
}

/* The line that replaces a newsletter form once it has sent, and the one that
   sits under it when it has not. Both inherit their colour from the block
   they are in, so the same rule works on navy and on white */
.newsletter__status {
  margin-top: 12px;
  font-size: 15px;
  line-height: 1.6;
  color: inherit;
}

.newsletter__status--error { color: var(--error); }
.on-dark .newsletter__status--error,
.site-footer .newsletter__status--error { color: var(--gold); }

/* --------------------------------------------------------------------------
   11h. Privacy and Accessibility

   Two long text pages and nothing else. The reset strips list markers site
   wide, which is right everywhere until a page has to set out what is
   collected and who reads it. These bring them back inside .legal only
   -------------------------------------------------------------------------- */

.legal h2 {
  margin-top: 56px;
  margin-bottom: 18px;
  font-size: var(--size-h3);
}

.legal h2:first-child { margin-top: 0; }

.legal h3 {
  margin-top: 32px;
  margin-bottom: 10px;
  font-size: var(--size-h4);
  font-weight: 600;
}

.legal p { color: var(--ink-2); }

.legal ul {
  margin-top: 14px;
  padding-left: 22px;
  list-style: disc;
  color: var(--ink-2);
}

.legal li { margin-bottom: 8px; }
.legal li::marker { color: var(--ink-3); }

/* Who holds the data, set apart from the prose so it survives a skim */
.legal__party {
  margin-top: 20px;
  padding: 24px 28px;
  background: var(--surface-2);
  border-left: 3px solid var(--gold);
  border-radius: 0 var(--radius-card) var(--radius-card) 0;
}

.legal__party p { color: var(--ink); }
.legal__party p + p { margin-top: 0.5em; }

.legal__updated {
  margin-top: 56px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
  font-size: var(--size-caption);
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   12. Media queries
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
  .site-footer__main { grid-template-columns: 1fr 1fr; gap: 36px; }
  .grid--4 { grid-template-columns: repeat(2, 1fr); }
}

/* The districts layout stacks at the one breakpoint, 1150px, along with the
   hero and the why-now section. Prompt 15 moved it off 900: two breakpoints
   20px apart doing similar jobs is how a layout stops being reasonable about.

   No `order: -1` any more. That put the map above the heading back when the
   map was the control. Below 1150 the map is a picture and the numbered list
   underneath is the control, so the reading order is the natural one:
   heading, then the photograph, then the six districts. */
/* ==========================================================================
   THE 1150px BREAKPOINT, and why it is 1150 rather than a round number.

   Below this width the landing page stops compositing type over photographs.
   The hero and "Why here, and why now" both become two stacked bands, the
   picture at full brightness with no scrim and the type underneath on flat
   --navy, and the district map stops being a control.

   1150 IS MEASURED. Sweeping the prompt 14 directional gradients across
   widths, with the type sampled against the composited pixels under the inked
   glyphs, the hero holds 4.5:1 at 1150 with 0.76 to 1.21 of margin at every
   viewport height tried, and the why-now holds its worst role with 0.92. At
   1100 the why-now numeral drops to 3.11 against a 3 floor and at 1050 the
   hero note fails outright on a tall window, 3.88:1.

   THE FAILURE IS NOT A CLEAN CLIFF, which is the reason for stacking rather
   than tuning. Where the gradient breaks depends on the hero box's aspect
   ratio, not its width: the photograph is object-fit: cover, so a taller
   window re-crops it and moves a different part of the picture under the
   13px hero note. Measured at 1024 wide, the note passes at a 1366px viewport
   height and fails at 1180. No single width makes an overlay safe, and no
   gradient angle fixes it either, because the coupling is the crop.

   Below the breakpoint the type sits on a flat token, so its contrast is a
   known number rather than a measurement, and the photograph is finally
   visible on a phone.
   ========================================================================== */

@media (max-width: 1149px) {
  /* .dmap is one column here by default and the panel is hidden, so there is
     nothing to unstack. The .dmap__layout rule this block used to carry went
     with the grid it belonged to. */
  .dmap__head { margin-bottom: 36px; }

  .dmap__rule { margin: 18px 0 24px; }

  /* ---- The hero, stacked ---- */

  .hero.hero--stack {
    display: block;
    height: auto;
    min-height: 0;
    padding: 0;
    background: var(--navy);
  }

  /* Out of the absolute layer and into the flow, as the first band */
  .hero--stack .hero__media {
    position: static;
    display: block;
  }

  /* clamp rather than an aspect ratio: at 1149 a 3:2 band would be 766px tall
     and push the headline off the first screen, and at 375 it would be 250px,
     which is about right. The clamp holds the band between those two ideas */
  .hero--stack .hero__media img { height: clamp(200px, 34vw, 380px); }

  /* The whole point of the breakpoint */
  .hero--stack .hero__scrim { display: none; }

  .hero--stack .hero__inner { padding-block: 44px 56px; }
  .hero--stack .hero__content { max-width: none; }

  /* ---- Why here, and why now, stacked ---- */

  /* The picture is flush to the top of the section, so the section's own top
     padding moves onto the text block below it */
  .whynow {
    padding-top: 0;
    background: var(--navy);
  }

  .whynow__media { position: static; }
  .whynow__media img { height: clamp(200px, 32vw, 360px); }
  .whynow__scrim { display: none; }

  .whynow__inner { padding-top: var(--section-y); }
  .whynow__head { max-width: none; }

  /* ---- The map, no longer a control ---- */

  /* Open question 71. Bayfront and Gaylord Pacific are 700m apart, which on a
     312px-wide map of a 20km frame puts their centres about 11px apart. WCAG
     2.2 wants a 24px circle on each target not to reach another, and padding
     the hit area cannot fix a distance between centres.

     So below 1150 the pins do not render. display: none takes them out of the
     focus order and out of the accessibility tree in one move, the picture is
     just a picture, and the six numbered blocks underneath, which have always
     been the no-JavaScript fallback, are the control. districts.js leaves the
     list in place and marks the stage aria-hidden at these widths. */
  .dmap__pin { display: none; }
}

@media (max-width: 1024px) {
  .hero__scrim { background: var(--hero-scrim-narrow); }

  .steps { grid-template-columns: repeat(2, 1fr); gap: 36px 28px; }
  .split { gap: 40px; }
  .split--wide-left { grid-template-columns: 1fr 1fr; }
  .focus-tiles { grid-template-columns: repeat(2, 1fr); }
  .feature-cards { grid-template-columns: repeat(2, 1fr); }

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

/* Two by two, and only between the one-column stack below and four across
   above. A plain max-width block leaked into the stacked layout: the
   nth-child selectors here outrank the ones in the 768 block, so a
   single-column stack kept its gold left borders and its 28px top padding.
   A range query means the two never overlap */
@media (min-width: 769px) and (max-width: 1024px) {
  .statbar__grid {
    grid-template-columns: repeat(2, 1fr);
    row-gap: 28px;
  }

  .statbar__item:nth-child(odd) + .statbar__item { border-left-color: var(--gold); }
  .statbar__item:nth-child(odd) { border-left-color: transparent; }

  /* The second row gets a rule above it instead of beside it */
  .statbar__item:nth-child(n + 3) {
    padding-top: 28px;
    border-top: 2px solid var(--gold);
  }
}

@media (max-width: 768px) {
  :root { --section-y: 56px; }

  .grid--2,
  .grid--3 { grid-template-columns: 1fr; }

  .pillars,
  .events,
  .feature-cards,
  .focus-tiles { grid-template-columns: 1fr; }

  .measure-centre + .feature-cards { margin-top: 40px; }

  .dmap { grid-template-columns: 1fr; gap: 28px; }
  .dmap__list { grid-template-columns: 1fr; gap: 32px; }

  /* Form first, the other routes under it */
  .connect-grid { grid-template-columns: 1fr; gap: 40px; }
  .hero--navy { height: auto; min-height: 0; padding-block: 72px; }
  .confirmation { padding: 28px 24px; }
  .aside-block { padding: 22px 22px; }
  .guardrail-block { padding: 24px 22px; }

  /* Portrait above the name, both left-aligned */
  .person {
    grid-template-columns: 1fr;
    gap: 20px;
    padding: 26px 0;
  }

  .person__portrait { width: 140px; height: 140px; }
  .person__initials { font-size: 36px; }

  .platform h2 { font-size: var(--size-h2); }
  .platform__body p { font-size: var(--size-body); }

  .hero { height: auto; min-height: 0; padding-block: 96px; }
  .hero--short { padding-block: 64px; }

  /* The hero goes auto-height here, so the text fills far more of the frame
     and the eyebrow rises to about 15% from the top, into the open band.
     At 375 it measured 3.13:1 against the arch. The strong band now runs
     most of the way up. See BUILD-LOG.md */
  .hero--arch { padding-block: 72px 56px; }

  .hero--arch .hero__scrim { background: var(--hero-scrim-arch-narrow); }

  .hero--arch .hero__inner { padding-bottom: 0; }

  /* The tags column plus its nowrap ran 2px past a 360px viewport.
     Tags drop under the body instead of fighting for the same row */
  .event-row__inner {
    grid-template-columns: 48px 1fr;
    gap: 16px;
    padding: 18px 6px;
  }

  .event-row__tags {
    grid-column: 2;
    flex-wrap: wrap;
    white-space: normal;
    margin-top: 8px;
  }

  .pick { padding: 26px 22px; }
  .tabs { gap: 0; }
  .tab { padding: 12px 12px; font-size: var(--size-body); }

  .split { grid-template-columns: 1fr; gap: 36px; }
  .split__media img { min-height: 260px; }
  .split--reverse .split__media { order: -1; }

  .image-band { height: 260px; }
  .stat-card { padding: 32px 26px; }
  .pullquote { margin-top: 32px; padding-left: 20px; }
  .hero__meta { margin-top: 36px; }
  .hero__actions .btn-primary,
  .hero__actions .btn-ghost-white { flex: 1 1 auto; }

  /* One column, so the gold rule sits above each item rather than beside it */
  .statbar__grid { grid-template-columns: 1fr; }
  .statbar__item { padding: 20px 0; }

  .statbar__item + .statbar__item {
    border-left-color: transparent;
    border-top: 2px solid var(--gold);
  }
}

@media (max-width: 560px) {
  /* One column at phone. Two still fit at 768 and at 640: the binding case is
     "The catalyst", whose copy is the longest of the four */
  .steps { grid-template-columns: 1fr; gap: 28px; }
  .whynow__head { max-width: none; }

  .grid--4 { grid-template-columns: 1fr; }
  .site-footer__main { grid-template-columns: 1fr; gap: 32px; }
  .site-nav__wordmark { display: none; }
  .site-footer__bottom { flex-direction: column; align-items: flex-start; }
}

/* --------------------------------------------------------------------------
   13. Reduced motion. Disables every transform, transition and autoplay
   -------------------------------------------------------------------------- */

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

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .btn-primary:hover,
  .btn-secondary:hover,
  .btn-ghost-white:hover,
  .card:hover { transform: none; }

  /* The nav does not grow and shrink at all. The universal rule above makes
     the transition instant, and an instant jump at 20px of scroll is the
     movement this query exists to prevent. Pinned to the compact size */
  .site-nav__inner { min-height: var(--nav-h); }
  .site-nav__mark { width: var(--nav-mark-w); }

  [data-reveal],
  [data-reveal].reveal-armed { opacity: 1; transform: none; }
}
