/* ---- tokens.css ---- */
/* ============================================================
   Homeframe brand tokens.

   Lifted from Downloads/Home Frame Landing Pages (2).html so the built site and
   Elijah's After Effects comps share one source of truth. Recorded in prose in
   design-handoff.md section 6.

   These are the Homeframe brand, NOT the Command Center design language. The
   workspace greys apply to the admin panel only (rules.md).
   ============================================================ */

:root {
  /* -- blues ------------------------------------------------ */
  --base:        #062386;
  --navy:        #041a63;
  --blue:        #0a2fa8;
  --deep-top:    #071c55;
  --deep-bottom: #040e34;

  /* -- accent ----------------------------------------------- */
  --gold:        #ffc21e;
  --orange:      #ff8a1e;
  --accent:      linear-gradient(135deg, var(--gold), var(--orange));

  /* -- ink -------------------------------------------------- */
  --ink:         #f2f6ff;
  --ink-dim:     rgba(226, 236, 255, .62);
  --ink-faint:   rgba(226, 236, 255, .34);
  --ink-on-gold: #180a00;

  /* -- lines and surfaces ----------------------------------- */
  --rule:        rgba(255, 255, 255, .13);
  --rule-soft:   rgba(255, 255, 255, .07);
  --surface:     rgba(255, 255, 255, .045);
  --surface-2:   rgba(3, 12, 48, .86);

  /* The STUDIOS ribbon from the wordmark, sampled straight out of the logo PNG:
     a hot highlight along the top edge, the orange body falling to a deeper red
     at the bottom, and a dark lip underneath where the ribbon folds. */
  --ribbon: linear-gradient(180deg,
              #fb9b23 0%,
              #f2801c 16%,
              #ea5f07 50%,
              #dc4200 86%,
              #d03400 100%);
  --ribbon-lip:   rgba(124, 14, 0, .85);
  --ribbon-sheen: inset 0 1px 0 rgba(255, 219, 160, .6);
  --ribbon-cast:  0 16px 34px -14px rgba(190, 55, 0, .65);
  --ribbon-radius: clamp(9px, .7vw, 18px);

  --shadow-card: 0 34px 90px -28px rgba(0, 6, 32, .92),
                 0 8px 26px -12px rgba(0, 6, 32, .7);

  /* -- type ------------------------------------------------- *
   * Outfit, self-hosted. A geometric sans in the same lane as Altivo, under the
   * SIL Open Font License, so it can be served from this box with no vendor in
   * the request path. Altivo cannot be: Adobe Fonts covers commercial use but
   * web use only through Adobe's own hosted kit, which would tell Adobe which
   * investor opened the deck and when.
   *
   * ONE variable file covers every weight from 100 to 900. That matters for the
   * banner: the selected tab getting heavier is a real weight change within the
   * same typeface, not the browser faking bold or falling back to another font.
   *
   * If Altivo is ever licensed for self-hosting it drops in here and nothing
   * else changes. */
  --font: 'Outfit', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Reverted 2026-08-29. Both rounded faces were tried on the Our Series header
     and neither was wanted: Fredoka read as a character in its own right, and
     Quicksand was Outfit with the corners off but too light at 700 against
     Outfit's 900. A typeface cannot be rounded by CSS — the terminals are drawn
     into the file — so this stays Outfit. Both files are kept in
     site/design-source/ if it is ever worth another look. */
  --font-display: var(--font);

  /* Banner scale. Everything in the banner derives from this, and the deck page
     needs it too, so it lives here rather than on .nav. */
  /* The banner scales with the viewport, and the FLOOR is what matters. At
     .82vw with an 11px minimum it hit that floor at about 1340px and stayed
     there, so on a laptop the banner was the same size as on a phone while the
     screen was three times wider — tiny, against a page that had grown. A
     steeper slope and a higher floor keep it in proportion on the way down. The
     21px ceiling is unchanged, so nothing about a large monitor moves. */
  --nav-size: clamp(13px, 1.25vw, 21px);
  /* The banner's resulting height: 1.7em padding-top + .8em padding-bottom +
     the logo, which is 5.6em wide at the mark's 1.409 aspect. */
  --nav-h: calc(var(--nav-size) * 6.474);

  /* -- rhythm ----------------------------------------------- */
  --ease:    cubic-bezier(.22, .61, .36, 1);
  /* For the carousel. Eases IN as well as out, so a slide leaves gently instead
     of snapping away, and the long tail means it settles rather than stops. */
  --ease-glide: cubic-bezier(.32, .01, .16, 1);
  /* Elijah's monitor is 3840 x 1440. The design was drawn at 1920, so every
     size below scales with the viewport instead of stopping at a 1920 cap —
     otherwise the layout sits as a small island in the middle of his screen,
     which is exactly what it did the first time. */
  --gutter:  clamp(18px, 3.4vw, 130px);
  --max:     min(74ch, 1600px);
  --max-wide: none;

  /* Reserved space so the fixed download button never covers the last line
     of a page, on top of the device's own safe area. */
  --fab-reserve: 108px;
}


@font-face {
  font-family: 'Outfit';
  src: url('/assets/outfit-var.6c18d579.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}


/* ---- base.css ---- */
/* ============================================================
   Base. Document-level styling only.

   SCAFFOLDING. This is a readable, on-brand baseline so the structure can be
   built and tested before Elijah's After Effects designs land. It is meant to be
   replaced by those designs, not defended.
   ============================================================ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--base);
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  line-height: 1.6;
}

/* The gradient field is CSS, never an exported image: a JPG of this is two
   megabytes and bands visibly on an OLED phone, and cannot respond to viewport
   size. design-handoff.md section 1. */
body::before {
  content: '';
  position: fixed; inset: 0; z-index: -2;
  background:
    radial-gradient(122% 76% at 50% -14%, #1c4fd8 0%, rgba(28, 79, 216, 0) 58%),
    radial-gradient(86% 62% at 88% 20%, #0f3cbb 0%, rgba(15, 60, 187, 0) 62%),
    radial-gradient(98% 70% at 6% 76%, #072a8c 0%, rgba(7, 42, 140, 0) 64%),
    linear-gradient(176deg,
      var(--deep-top) 0%,
      var(--blue) 32%,
      #0b3099 56%,
      #071c5e 80%,
      var(--deep-bottom) 100%);
}
body::after {
  content: '';
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(120% 66% at 50% -8%, rgba(150, 190, 255, .13), transparent 60%),
    radial-gradient(78% 54% at 100% 14%, rgba(255, 138, 30, .05), transparent 66%);
}

/* ---- typography ------------------------------------------ */

h1, h2, h3 {
  font-weight: 900;
  letter-spacing: .045em;
  line-height: 1.08;
  text-transform: uppercase;
  text-wrap: balance;
}

p { max-width: 68ch; }
p + p { margin-top: 1em; }

a { color: var(--ink); }
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: 4px;
}

b, strong { font-weight: 700; color: var(--ink); }

::selection { background: var(--orange); color: var(--ink-on-gold); }

/* ---- never ship a browser-default control ---------------- *
 * A raw scrollbar renders as a white OS-native widget and breaks the design.
 * rules.md / workspace design rules. */
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: rgba(1, 8, 36, .45); }
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--gold), var(--orange));
  border-radius: 99px;
  border: 3px solid rgba(1, 8, 36, .45);
}
html { scrollbar-width: thin; scrollbar-color: var(--orange) rgba(1, 8, 36, .45); }

/* ---- skip link ------------------------------------------- */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 12px 20px;
  background: var(--accent);
  color: var(--ink-on-gold);
  font-weight: 700;
  text-decoration: none;
  border-radius: 0 0 10px 0;
}
.skip:focus { left: 0; }

/* Present to assistive technology and to the document outline, absent visually.
   The homepage's title is the logo artwork, which is an image, so the heading
   that names the page lives here. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- images ---------------------------------------------- */

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


/* ---- components.css ---- */
/* ============================================================
   Components. SCAFFOLDING — replaced by Elijah's designs.
   ============================================================ */

.nav__inner,
.foot__inner,
main {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The banner is fixed on every page, so it occupies no layout space. Clearing
   it is the DEFAULT here rather than something each page remembers to do: a new
   page works without thinking about it, and the two pages that position their
   own first element opt out below. */
main {
  padding-block: clamp(28px, 5vw, 56px);
  margin-top: var(--nav-h);
}

/* Only the pages that actually carry the fixed download button reserve room for
   it. Reserving it everywhere just leaves a dead band at the bottom. */
.page-pitch main {
  padding-bottom: calc(var(--fab-reserve) + env(safe-area-inset-bottom, 0px));
}

/* ---- nav ------------------------------------------------- */

/* Built to Elijah's banner reference, 2026-08-28.
   Logo left, tabs beside it, nothing in the top right. The same bar on every
   page — fixed, over whatever is behind it, with its own band underneath.

   EVERYTHING here is derived from --nav-size, so the logo, the gaps, the bar
   height and the glow keep their proportions to the type at any screen width.
   The first attempt scaled the logo on its own and it ended up 314px wide on a
   3840 monitor, dwarfing the tabs. */

/* ONE definition, every page. There are deliberately no per-page variants of
   the banner: change it here and it changes everywhere. Pages differ only in
   what they put UNDER it, never in the bar itself.

   Fixed, so it follows the page down. Transparent, because the band below is
   ::before rather than a background on the bar — that lets the gradient run
   past the bottom of the bar and fade out, instead of ending on a hard edge. */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  background: none;
  border: 0;
}

/* The band. Colour sampled off Elijah's reference, where it reads rgb(3,18,74)
   and hides the picture completely at the very top. Height is set against the
   bar so the fade keeps its proportions at any screen size. */
.nav::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  /* Ends just over half a bar-height below the bar, so it stops eating into the
     picture. The solid part covers the bar itself and the fade is packed into
     what is left rather than trailing far down the page. */
  height: 170%;
  pointer-events: none;
  z-index: -1;
  background: linear-gradient(180deg,
    rgba(3, 18, 74, .78)  0%,
    rgba(3, 18, 74, .75)  30%,
    rgba(3, 18, 74, .68)  48%,
    rgba(3, 18, 74, .56)  60%,
    rgba(3, 18, 74, .41)  70%,
    rgba(3, 18, 74, .27)  79%,
    rgba(3, 18, 74, .15)  86%,
    rgba(3, 18, 74, .07)  92%,
    rgba(3, 18, 74, .02)  97%,
    rgba(3, 18, 74, 0)    100%);
}

.nav__inner {
  display: flex; align-items: center;
  /* Logo to tabs, and the bar pushed down off the top edge. */
  gap: calc(var(--nav-size) * 4);
  padding-block: calc(var(--nav-size) * 1.7) calc(var(--nav-size) * .8);
  flex-wrap: wrap;
}

.nav__logo { display: block; flex: 0 0 auto; line-height: 0; }
.nav__logo img {
  width: calc(var(--nav-size) * 5.6);
  height: auto;
  filter: drop-shadow(0 4px 14px rgba(0, 8, 40, .7));
}

.nav__links {
  display: flex; align-items: center;
  gap: calc(var(--nav-size) * 2.9);
  flex-wrap: wrap;
}

.nav__links a {
  position: relative;
  /* Two spans in one grid cell: an invisible always-900 copy that sets the
     width, and the visible label. The cell is therefore always as wide as the
     BOLD text, so selecting a tab thickens it in place and never nudges its
     neighbours along the bar. */
  display: inline-grid;
  align-items: center;
  /* Symmetric padding. The beam is absolutely positioned and reserves no space,
     so the labels stay vertically centred on the logo. */
  padding: calc(var(--nav-size) * .3) 0;
  /* line-height 1 strips the leading, so the gap between the label and the beam
     is the number below and not that number plus half a line box. */
  line-height: 1;
  font-size: var(--nav-size);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .9);
  text-decoration: none;
  /* The band is deliberately faint, so the labels carry a little of their own
     contrast rather than relying on it. */
  text-shadow: 0 1px 5px rgba(1, 8, 30, .55), 0 0 18px rgba(1, 8, 30, .35);
  transition: color .25s var(--ease), text-shadow .25s var(--ease);
}
.nav__links a > span { grid-area: 1 / 1; }
.tab__sizer { font-weight: 900; font-variation-settings: 'wght' 900; visibility: hidden; }
.tab__label {
  font-weight: 700;
  font-variation-settings: 'wght' 700;
  /* Outfit is a variable font, so weight is a number that can be animated rather
     than two separate faces to swap between. This covers hover and any change
     inside one page; moving between pages is covered by the view transition
     above. */
  transition: font-variation-settings .32s var(--ease), color .32s var(--ease);
}

.nav__links a:hover { color: #fff; }

/* ---- the selected tab ------------------------------------ *
 * The typeface does NOT change: Outfit is variable, so 700 to 900 moves along a
 * weight axis inside the same family. Nothing is faked bold and nothing falls
 * back to a different font. */

.nav__links a[aria-current='page'] {
  color: #fff;
  text-shadow:
    0 0 calc(var(--nav-size) * .28) rgba(255, 255, 255, .45),
    0 0 calc(var(--nav-size) * .8)  rgba(255, 178, 72, .62),
    0 0 calc(var(--nav-size) * 1.7) rgba(255, 140, 30, .45);
}
.nav__links a[aria-current='page'] .tab__label {
  font-weight: 900;
  font-variation-settings: 'wght' 900;
}

/* ---- the light beam -------------------------------------- *
 * An anamorphic lens streak, not an underline. The shape is what matters: thick
 * and white-hot at the centre, tapering to nothing at both ends, wrapped in a
 * soft warm bloom that tapers with it.
 *
 * It is built from four stacked ELLIPTICAL gradients rather than a rectangle
 * with a box-shadow. A rectangle has the same thickness end to end and its
 * shadow traces that rectangle, which is why the previous version read as a
 * flat light bar. An ellipse is thickest at its centre and thins toward its
 * edges for free, which is exactly the falloff a real streak has.
 *
 *   1  core      needle-thin, pure white
 *   2  inner     tight yellow-white flare hugging the core
 *   3  mid       the orange body of the glow
 *   4  outer     the wide, faint spill
 */
.nav__links a[aria-current='page']::before {
  content: '';
  position: absolute;
  left: 50%;
  top: calc(100% + var(--nav-size) * .1);
  /* 140% wide, with the CORE sized to 100/140 of it — so the bright streak is
     exactly as wide as the word above it while the bloom still has room to
     spill past the ends instead of being clipped square. */
  width: 140%;
  height: calc(var(--nav-size) * 3.4);
  transform: translate(-50%, -50%);
  pointer-events: none;
  background:
    radial-gradient(ellipse 36% 1.4% at 50% 50%,
      #ffffff 0%,
      rgba(255, 246, 224, .98) 28%,
      rgba(255, 205, 120, .6) 62%,
      rgba(255, 170, 60, 0) 100%),
    radial-gradient(ellipse 36% 4.2% at 50% 50%,
      rgba(255, 208, 130, .95) 0%,
      rgba(255, 158, 48, .72) 38%,
      rgba(255, 138, 30, 0) 100%),
    radial-gradient(ellipse 40% 14% at 50% 50%,
      rgba(255, 150, 38, .92) 0%,
      rgba(255, 133, 24, .48) 42%,
      rgba(255, 120, 16, 0) 80%),
    radial-gradient(ellipse 50% 44% at 50% 50%,
      rgba(255, 132, 24, .5) 0%,
      rgba(255, 118, 16, .18) 46%,
      rgba(255, 110, 12, 0) 78%);
}

/* ---- the banner arriving --------------------------------- *
 * Home on a desktop: after the curtain lifts. Everywhere else, and on every
 * phone: shortly after the page is ready. src/client/reveal.js decides which,
 * and sets both attributes below.
 *
 * EVERY RULE HERE IS GATED ON html[data-reveal], WHICH ONLY A SCRIPT CAN SET.
 * With JavaScript off, blocked, or thrown, none of these selectors match and the
 * banner is simply the banner. Nothing in this section can hide anything on its
 * own, which is the property that makes an entrance animation safe to put on a
 * fixed header that every page depends on.
 *
 * The order is band, logo, tabs left to right, then the beam last: the light
 * comes on after the word it belongs to has arrived, rather than travelling in
 * with it. */

html[data-reveal] .nav::before {
  opacity: 0;
  transition: opacity .9s var(--ease);
}

html[data-reveal] .nav__logo {
  opacity: 0;
  transform: translateY(-14px);
  transition: opacity .66s var(--ease-glide), transform .66s var(--ease-glide);
}

html[data-reveal] .nav__links a {
  opacity: 0;
  transform: translateY(-11px);
  /* The first two are the hover transitions from the base rule. A `transition`
     declaration REPLACES rather than extends, so leaving them out here would
     kill the hover fade for the rest of the page's life - the attribute stays on
     <html> permanently so that a strip further down the page still has its
     starting state when it is finally scrolled to. */
  transition:
    color .25s var(--ease),
    text-shadow .25s var(--ease),
    opacity .6s var(--ease-glide),
    transform .6s var(--ease-glide);
}

/* Left to right, a beat apart. Six tabs today; the list is written past that so
   adding one does not silently leave it out of the sequence. */
html[data-reveal] .nav__links a:nth-child(1) { transition-delay: 0s, 0s, .10s, .10s; }
html[data-reveal] .nav__links a:nth-child(2) { transition-delay: 0s, 0s, .16s, .16s; }
html[data-reveal] .nav__links a:nth-child(3) { transition-delay: 0s, 0s, .22s, .22s; }
html[data-reveal] .nav__links a:nth-child(4) { transition-delay: 0s, 0s, .28s, .28s; }
html[data-reveal] .nav__links a:nth-child(5) { transition-delay: 0s, 0s, .34s, .34s; }
html[data-reveal] .nav__links a:nth-child(6) { transition-delay: 0s, 0s, .40s, .40s; }
html[data-reveal] .nav__links a:nth-child(7) { transition-delay: 0s, 0s, .46s, .46s; }
html[data-reveal] .nav__links a:nth-child(8) { transition-delay: 0s, 0s, .52s, .52s; }

/* The beam strikes rather than fades: it opens along its own length from a point
   at the centre out to the full width of the word. That is what an anamorphic
   streak does when a light comes into frame, and it is the reason this is a
   scaleX and not an opacity on its own.
   
   The base rule already carries `translate(-50%, -50%)`, so both states have to
   repeat it - a transform is one value, not a list to add to. */
html[data-reveal] .nav__links a[aria-current='page']::before {
  opacity: 0;
  transform: translate(-50%, -50%) scaleX(.06);
  transition:
    opacity .34s var(--ease),
    transform .82s var(--ease-glide);
  transition-delay: .52s;
}

html[data-reveal][data-nav-in] .nav::before { opacity: 1; }
html[data-reveal][data-nav-in] .nav__logo { opacity: 1; transform: none; }
html[data-reveal][data-nav-in] .nav__links a { opacity: 1; transform: none; }
html[data-reveal][data-nav-in] .nav__links a[aria-current='page']::before {
  opacity: 1;
  transform: translate(-50%, -50%) scaleX(1);
}

/* ---- page head ------------------------------------------- */

.page-head { padding-block: clamp(36px, 6vw, 78px); display: grid; gap: 18px; }
.page-head__eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: .3em; text-transform: uppercase;
  color: var(--ink-faint);
}
.page-head__title {
  font-size: clamp(38px, 7vw, 88px);
  background: linear-gradient(178deg, #ffffff 34%, #bcd2ff 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.page-head__lede { font-size: clamp(15px, 1.5vw, 18px); color: var(--ink-dim); }

/* ---- sections -------------------------------------------- */

.section { padding-block: clamp(30px, 4.5vw, 62px); }
.section + .section { border-top: 1px solid var(--rule-soft); }
.section__title {
  font-size: clamp(19px, 2.3vw, 32px);
  margin-bottom: 18px;
}
.section__body { color: var(--ink-dim); font-size: clamp(14px, 1.2vw, 16.5px); }

/* ---- the ask table --------------------------------------- *
 * A real table, never an exported image. On a narrow screen it scrolls inside its
 * own container so the page itself never scrolls sideways. */

.table-wrap { margin-block: clamp(18px, 3vw, 34px); }
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: rgba(4, 14, 52, .55);
  -webkit-overflow-scrolling: touch;

  /* NO SCROLLBAR CHROME. These tables overflow their frame by a few pixels at
     most, so the bar was not telling anybody anything - it was a fat orange
     rule under every table on the tax credit page, drawn by the browser and
     styled by the site's own scrollbar rule, which is exactly the "never ship a
     browser-default element" case. Elijah, 2026-09-01. The box still scrolls by
     trackpad, shift-wheel, touch and keyboard; only the bar is gone. */
  scrollbar-width: none;
}
.table-scroll::-webkit-scrollbar { width: 0; height: 0; display: none; }
.ask-table { border-collapse: collapse; width: 100%; min-width: 720px; font-size: 14px; }
/* The caption is the section title for its table, not a footnote about it, so
   it is sized and weighted like one. It was 11px and faint, which read as a
   label on the table rather than a heading over it. */
.ask-table__caption {
  caption-side: top;
  text-align: left;
  padding: clamp(16px, 1.3vw, 30px) clamp(16px, 1.2vw, 26px) clamp(12px, 1vw, 20px);
  font-family: var(--font-display);
  font-size: clamp(15px, 1.15vw, 30px);
  font-weight: 900;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #ffffff;
}
.ask-table th,
.ask-table td {
  padding: 12px 18px;
  text-align: right;
  border-bottom: 1px solid var(--rule-soft);
  white-space: nowrap;
}
.ask-table th:first-child,
.ask-table td:first-child { text-align: left; }
.ask-table thead th {
  font-size: 10px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  color: var(--ink-faint);
  background: rgba(3, 12, 48, .7);
  position: sticky; top: 0;
}
.ask-table td.is-primary { color: var(--ink); font-weight: 700; }
.ask-table tbody tr:hover { background: rgba(255, 255, 255, .03); }
.ask-table .is-total td {
  border-top: 1px solid var(--rule);
  border-bottom: 0;
  font-weight: 900;
  color: var(--ink);
}
.table-wrap__hint {
  margin-top: 10px;
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint);
}
@media screen and (min-width: 900px) { .table-wrap__hint { display: none; } }

/* ---- the fixed download button --------------------------- *
 * Bottom right for the entire scroll. Never hidden, never collapsed to an icon,
 * never dependent on JavaScript. design-spec.md section 2. */

/* THE button. The wordmark's ribbon, and the only button style on the site:
   the download and the carousel's View full screen are the same object in two
   places, so they are one rule with two positions rather than two lookalikes
   that drift apart. */
.ribbon-button,
.download-fab {
  display: inline-flex; align-items: center; gap: .7em;
  padding: clamp(10px, .68vw, 16px) clamp(15px, 1.15vw, 27px);

  /* Square with rounded corners and the wordmark's ribbon, not a pill. */
  border: 0;
  border-radius: var(--ribbon-radius);
  border-bottom: max(3px, .22em) solid var(--ribbon-lip);

  background: var(--ribbon);
  color: #ffffff;
  font-family: inherit;
  font-size: clamp(10px, .58vw, 14px);
  font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  text-shadow: 0 2px 0 rgba(132, 26, 0, .4);

  box-shadow: var(--ribbon-sheen), var(--ribbon-cast), 0 4px 14px -4px rgba(0, 6, 32, .8);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), filter .35s var(--ease);
}
.download-fab {
  position: fixed;
  right: max(clamp(24px, 2.4vw, 64px), env(safe-area-inset-right, 0px));
  bottom: max(clamp(24px, 2.4vw, 64px), env(safe-area-inset-bottom, 0px));
  z-index: 80;
}

.ribbon-button:hover,
.download-fab:hover {
  transform: translateY(-2px);
  filter: brightness(1.06);
  box-shadow: var(--ribbon-sheen),
              0 22px 44px -14px rgba(190, 55, 0, .75),
              0 6px 18px -4px rgba(0, 6, 32, .85);
}
.ribbon-button__icon,
.download-fab__icon { font-size: 15px; line-height: 1; }

/* The button carries TWO glyphs and shows one. On a wide screen it downloads
   the file, so it is a down arrow; on a phone it opens the operating system's
   share sheet, so it is a share mark. Both are in the markup and the media
   query picks, rather than a script swapping them after the fact. Everything
   below is the default, which is to say: the desktop button, unchanged. */
.download-fab__icon--share { display: none; }
.download-fab__status { display: none; }

/* Shown only while the 34MB deck is on its way to the share sheet, and only on
   a phone. Nothing spins on a desktop, where the button is a plain link. */
@keyframes fab-spin { to { transform: rotate(360deg); } }

/* On a small screen it keeps its label. An icon-only button leaves an investor
   guessing what it does, which is the opposite of the point. */
@media screen and (max-width: 460px) {
  .download-fab {
    left: max(20px, env(safe-area-inset-left, 0px));
    right: max(20px, env(safe-area-inset-right, 0px));
    justify-content: center;
    padding: 16px 18px;
    font-size: 12px;
  }
}

/* The footer was a placeholder bar and is gone. Elijah's own design replaces
   it; there is deliberately no CSS holding a shape for it. */


/* ---- hiding the page until the curtain can cover it ------- *
 *
 * intro.js adds this class to <html> synchronously, from the HEAD, before the
 * body has been parsed — so the very first paint is this colour and nothing
 * else. It comes off the instant the video has a frame, which is the instant
 * the curtain itself starts covering the page, so there is no moment where
 * either the site or a blank screen is on show.
 *
 * The colour is sampled from frame one of Elijah's export, which is fully
 * opaque: the curtains start closed. So this is not a stand-in for the animation,
 * it IS the animation's first frame, held until the video can take over.
 *
 * visibility, not opacity or display: it inherits, so one rule hides the whole
 * document, and unlike opacity a descendant can opt back OUT of it — which is
 * how the curtain itself stays visible while everything under it does not.
 *
 * With JavaScript off this class is never added and none of it applies. */

html.is-intro-pending { background-color: #061751; }
html.is-intro-pending body { visibility: hidden; }
html.is-intro-pending body .intro { visibility: visible; }

/* ---- the curtain intro ----------------------------------- *
 * pointer-events: none is the important line. The page underneath is live and
 * clickable from the first frame, so the curtain is never something anyone has
 * to wait out. */

.intro {
  position: fixed;
  inset: 0;
  z-index: 200;
  pointer-events: none;
  /* The video is sized to COVER, so it is wider or taller than this box on any
     screen that is not 16:9. Without this the overflow paints outside the
     overlay. */
  overflow: hidden;
  opacity: 1;
  transition: opacity .4s var(--ease);
}
.intro.is-leaving { opacity: 0; }

/* .intro--opaque lived here: a brand-blue ground meant to sit behind the
   curtain on WebKit, where the alpha channel is ignored. It could never have
   worked. WebKit paints those regions as OPAQUE black rather than leaving them
   transparent, so nothing behind the video is ever reached. The curtain is
   skipped on that engine instead; see the note in client/intro.js. */

.intro__video {
  /* COVER, COMPUTED, rather than delegated to object-fit alone.
   *
   * The file is 16:9 and a phone held upright is nothing like it, so the crop
   * is severe and has to be exactly right. object-fit stays, because where it
   * is honoured it is the cheaper path - but the width and height below already
   * describe the covering box, so a browser that ignores object-fit on a video
   * gets the same full-screen crop rather than a letterboxed frame with bars
   * where the curtain should be. Elijah on an iPhone, 2026-09-01: it has to be
   * full screen.
   *
   * max() of "as wide as the window" and "as wide as the window's height needs
   * a 16:9 frame to be" is the definition of cover for a fixed aspect ratio.
   * svh rather than vh so a collapsing address bar cannot shrink it mid-play. */
  width: max(100vw, calc(100svh * 16 / 9));
  height: max(100svh, calc(100vw * 9 / 16));
  max-width: none;
  /* Centred inside the overlay, which clips it. */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  object-fit: cover;
  display: block;

  /* drop-shadow, NOT box-shadow. box-shadow traces the element's rectangle and
     would draw a frame around the whole viewport; drop-shadow follows the
     video's ALPHA, so the shadow is cast by the curtains themselves and falls
     into the gap as they part. That is the whole reason it reads as depth
     rather than as a video sitting on a page.

     One shadow, moderate blur: this is filtering a 2560-wide frame on every
     tick, and stacking two would cost twice as much for very little. */
  filter: drop-shadow(0 8px 38px rgba(1, 6, 26, .62));
}

/* Belt and braces: anyone who has asked for less motion never gets the element
   in the first place, but if that check is ever missed the CSS hides it too. */
@media (prefers-reduced-motion: reduce) {
  .intro { display: none; }
}

/* ---------------------------------------------------------------- breakdown
 *
 * The detailed analysis under GA Tax Credit: two cost breakdowns the spreadsheet puts
 * side by side, and that read as a comparison only when they stay side by side.
 *
 * They separate below 1200px rather than at the usual breakpoint, because two
 * columns of six-column money tables squeezed into a laptop is worse than the
 * same two one after another. Stacked, industry rate comes first, which is the
 * order the argument runs in: this is what it costs, this is what the credit is
 * calculated on, this is what we actually spend.
 */
.breakdown {
  display: grid;
  gap: clamp(30px, 3vw, 72px);
  align-items: start;
}
@media screen and (min-width: 1400px) {
  .breakdown { grid-template-columns: 1fr 1fr; }
}

/* Without this a grid child refuses to shrink below its content and the tables
   push the page sideways instead of scrolling inside their own frames. */
.breakdown__col { min-width: 0; }

.breakdown__head {
  padding-bottom: clamp(10px, 1vw, 18px);
  border-bottom: 1px solid var(--rule);
  margin-bottom: clamp(6px, .8vw, 14px);
}
.breakdown__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(17px, 1.5vw, 32px);
  font-weight: 900;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #ffffff;
}
.breakdown__subtitle {
  margin: 8px 0 0;
  font-size: clamp(12px, 1vw, 15px);
  color: var(--ink-dim);
}

/* Two six-column money tables have to share the width one usually gets, so
   these tighten rather than scroll. Nothing here is a smaller FONT: the numbers
   are the content and shrinking them to fit is the wrong trade.
   
   The line item wraps instead of forcing the table wider. Base table rules set
   white-space: nowrap on every cell, which is right for a figure and wrong for
   "Writing - Research, scripting, worksheet printouts" — that one row was
   pushing the season cost column off the edge of its own frame. */
.breakdown .ask-table { min-width: 0; }
.breakdown .ask-table th,
.breakdown .ask-table td { padding: 11px 12px; }
.breakdown .ask-table th:first-child,
.breakdown .ask-table td:first-child {
  white-space: normal;
  min-width: 150px;
  padding-right: 18px;
}
.breakdown .table-wrap { margin-block: clamp(12px, 1.2vw, 24px); }

.breakdown__summary {
  margin-top: clamp(16px, 1.6vw, 30px);
  padding: clamp(16px, 1.4vw, 28px);
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: rgba(4, 14, 52, .55);
}
.ask-table--summary { min-width: 0; }
.ask-table--summary thead th { position: static; }

.breakdown__note {
  margin: clamp(12px, 1.2vw, 20px) 0;
  text-align: center;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The one number the page exists for. */
.breakdown__credit {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin: clamp(14px, 1.4vw, 24px) 0 0;
  padding-top: clamp(12px, 1.2vw, 20px);
  border-top: 1px solid var(--rule);
}
.breakdown__credit-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.breakdown__credit-value {
  font-family: var(--font-display);
  font-size: clamp(22px, 2.2vw, 44px);
  font-weight: 900;
  letter-spacing: .02em;
  background: var(--accent);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-variant-numeric: tabular-nums;
}

/* ---- band heading ----------------------------------------- *
 * Splits a money page into named parts: OVERVIEW, then DETAILED ANALYSIS.
 * Elijah's partner, 2026-08-31 — the tax credit page had grown a headline, two
 * summary tables and a full line-item breakdown with nothing telling the reader
 * where the claim stopped and the evidence started. */
.band-heading {
  /* EQUAL air above and below, so it sits centred between the two blocks it
     divides rather than hanging off one of them. It had 70 above and 22 below
     and read as a caption stuck to the table underneath. Elijah, 2026-08-31. */
  margin-block: clamp(34px, 4vw, 72px);
  margin-inline: 0;

  /* The rules either side taper OUT, fading toward the edges of the page rather
     than stopping at a hard end. A rule that stops dead draws the eye to where
     it stopped; one that fades leaves the words as the only thing with an
     edge. */
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.8vw, 38px);

  font-family: var(--font-display);
  font-size: clamp(20px, 2vw, 42px);
  font-weight: 900;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: center;
  color: #ffffff;
}
.band-heading::before,
.band-heading::after {
  content: '';
  flex: 1 1 0;
  height: 1px;
  min-width: 0;
}
.band-heading::before {
  background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .38));
}
.band-heading::after {
  background: linear-gradient(90deg, rgba(255, 255, 255, .38), rgba(255, 255, 255, 0));
}

/* ---- a group heading INSIDE a table ------------------------ *
 * Our Ask is one table for the whole slate now, with the three focus areas as
 * rows rather than as three tables with air between them. It has to read as a
 * divider and not as data, so it is heavier, tracked, and carries the rule
 * above it that used to be the gap between the tables. */
.ask-table__group {
  text-align: left;
  padding: clamp(20px, 1.8vw, 34px) 12px 10px;
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-display);
  font-size: clamp(12px, 1vw, 15px);
  font-weight: 900;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #ffffff;
  background: rgba(3, 12, 48, .35);
  white-space: nowrap;
}
.ask-table tbody tr.is-group:first-child .ask-table__group { padding-top: 12px; }
.ask-table tbody tr.is-group:hover { background: none; }

/* ---- the one row a page is built to reach ------------------ *
 * The Georgia credit at the foot of the fiscal-year table. Everything above it
 * is the working; this is the answer.
 *
 * It is bigger, and the NUMBER is gold. It was briefly lit from behind in blue
 * as well, which put a glowing band across the foot of the table and made the
 * row look like a control rather than a total. Elijah, 2026-08-31: the row
 * keeps the same ground as the ones above it and earns its weight from size
 * and colour alone.
 *
 * The figure is picked out as the cell that is neither the label nor empty,
 * rather than by column number, so a column added or dropped cannot silently
 * move the gold onto the wrong number. */
.ask-table .is-headline td {
  padding-block: clamp(16px, 1.6vw, 26px);
  font-size: clamp(15px, 1.35vw, 22px);
}
.ask-table .is-headline td:not(:first-child):not(:empty) {
  color: var(--gold);
}

/* ---- the opt-out confirmation ---------------------------- *
 * Shown only to somebody who has just opened ?hf-no-track or ?hf-track. An
 * ordinary visitor never has this element in their document, so it costs them
 * nothing and cannot be mistaken for a cookie banner - which this site
 * deliberately does not have and does not need.
 *
 * Bottom centre, over everything, because the person who opened that link is
 * looking for an answer and should not have to hunt for it. */
.optout-note {
  position: fixed;
  left: 50%;
  bottom: clamp(16px, 3vw, 40px);
  transform: translateX(-50%);
  z-index: 200;

  max-width: min(560px, calc(100vw - 32px));
  padding: clamp(12px, 1.2vw, 18px) clamp(16px, 1.6vw, 26px);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(4, 14, 52, .96);
  box-shadow: 0 24px 60px -24px rgba(0, 6, 32, .95);

  font-family: var(--font-ui);
  font-size: clamp(13px, 1vw, 16px);
  line-height: 1.5;
  text-align: center;
  color: #ffffff;
}

/* It did not save. The one case where the reader has to do something. */
.optout-note.is-warning {
  border-color: rgba(255, 170, 60, .6);
  background: rgba(58, 26, 4, .96);
}


/* ---- page-home.css ---- */
/* ============================================================
   HOME, and the entrance animations for every page.

   Split out of pages.css on 2026-09-01, when that file reached 2,869 lines. The
   six files it became are concatenated by site/build.mjs in the order listed in
   buildCss(), and that order is the same order they were in - so the cascade is
   character for character what it was. Adding a file means adding it to that
   array; a stylesheet the array does not name is simply never served.

   ONE THING LIVES HERE THAT IS NOT THE HOME PAGE. The entrance system - the
   `html[data-reveal]` rules for the banner, the home strips, Our Shows, the deck
   and the money pages, plus the money-page hover - is one contiguous block in
   the middle of this file. It stayed here rather than being scattered across the
   other five because it IS one system: the shapes are chosen against each other,
   and moving the rules would have changed their position in the cascade for no
   behavioural reason at all. Look for "the strips arriving".
   ============================================================ */

/* ============================================================
   Homepage — built to Elijah's design, 2026-08-28.

   Banner over a full-bleed hero, a fade at the top so the logo and tabs read
   against any frame of the video, a fade at the bottom carrying the hero into
   the blue, then the OUR SERIES rail as bare placeholder shapes.

   The blue field behind everything below the hero is an After Effects export,
   masked to the bottom fade defined here. Until it lands, --bg-fallback stands
   in so the layout can be judged.
   ============================================================ */

:root {
  /* How far the hero runs past the bottom of the window. */
  --hero-over: 18svh;
  /* How far Our Shows climbs back INTO that overhang. Less than the overhang, so
     the two are not the same number by accident: the hero's height and the space
     under the dissolve are separate decisions. Elijah, 2026-08-31. */
  --rail-lift: 12svh;

  /* The colour the hero fades INTO. It must match the top edge of the exported
     background exactly or a seam appears where the two meet. Sampled from the
     design; Elijah confirms it against his export. */
  --bg-top: #0e2b76;

  /* Height of the hero's bottom fade, as a fraction of the hero. This is the
     shape the background export is masked to. Kept short: a long fade eats the
     image and the design has the picture holding almost the whole frame. */
  --hero-fade-height: 34%;

  /* Rail geometry. Every card is the same height; the leading card is 16:9 and
     the rest are portrait, as in the design. Change these two lines to change
     the whole rail. */
  --rail-h: clamp(150px, 15.5vw, 620px);
  --rail-featured-aspect: 16 / 9;
  --rail-card-aspect: 7 / 10;

  /* The deck carousel below it. One slide large in the middle with its
     neighbours peeking in, so three numbers describe the whole thing. */
  /* Sized by whichever runs out first, WIDTH or HEIGHT. On a 3840-wide monitor a
     purely width-driven slide is taller than the window and the button under it
     is never on screen at the same time; on a short laptop the same number is
     fine. 70vh is the tallest a 16:9 slide can be and still leave room for the
     banner above it and the button below. */
  --slide-w: max(320px, min(62vw, 2300px, calc(70vh * 16 / 9)));
  --slide-h: calc(var(--slide-w) * 9 / 16);

  /* How far a neighbour's CENTRE sits from the middle slide's centre. It has to
     clear half of each: 0.5 for the active slide, plus half of a neighbour at
     --peek-scale (0.35), plus air. 0.85 is edge-to-edge with nothing between
     them, so 0.865 is about as tight as this goes before they touch. */
  --peek-shift: calc(var(--slide-w) * 0.865);
  --peek-scale: .7;
}

/* ---- full-bleed containers ------------------------------- *
 * The homepage is a full-bleed layout, not a centred reading column: the logo
 * sits near the left edge and the rail runs off the right. The other three
 * pages stay narrow, because they are documents to read. */

.nav__inner { max-width: none; }

.page-home main {
  max-width: none;
  /* The hero sits above main and starts at the top of the page, under the
     banner, so main must not be pushed down. */
  margin-top: 0;
  /* No top padding: the rail sits right up under the hero's fade, so OUR SERIES
     reads as part of the same field rather than a separate block below it. */
  padding-top: 0;
}

/* ---- the shell: banner over hero ------------------------- */

.shell { position: relative; }

/* ---- hero ------------------------------------------------ */

/* Sized by viewport HEIGHT, not by aspect ratio. On a 3840-wide monitor an
   aspect-driven hero either runs off the bottom of the screen or gets cropped
   into a letterbox slice; in the design it simply occupies the top ~two thirds
   of the window, which is what this does at any width. */
.hero {
  position: relative;
  width: 100%;

  /* ---- the line's geometry, declared HERE and not on .hero-line ----------- *
   * Two elements sit on the film now — the box with the title in it, and the
   * sentence under the box — and the second has to be placed off the first
   * without measuring it. Custom properties inherit and siblings do not, so the
   * three numbers that decide where the box is and how tall it opens live on
   * their common parent. .hero-line reads them exactly as it read its own
   * declaration before; nothing about the box changed.
   *
   * THE SIZE IS DERIVED, NOT CHOSEN. The sentence, set in Outfit 900 uppercase
   * with .09em tracking, measures a fixed number of ems wide; the type is
   * therefore "as big as the window can hold it on one line" — the window, less
   * the margins and the box's own padding, divided by that width. The divisor is
   * measured in a browser rather than estimated, because an estimate 5% out
   * either clips the line or leaves it small. */
  --line-fs: clamp(19px, calc((100vw - clamp(24px, 1.8vw, 70px)) / 29.5), 190px);
  /* Measured from the TOP, because the hero's bottom edge is 18svh below the
     window and anchoring to it puts the box off the screen. 55svh is the middle
     of the window, nudged down. It is where the TITLE sits; the box grows down
     from there, so adding a line to the box never moves the title. */
  --line-top: 55svh;

  /* WHAT THE BOX OPENS TO, ADDED UP RATHER THAN GUESSED AT.
   *
   * A fixed height, because auto is not animatable. It used to be a single
   * multiple of --line-fs and it now has two things to hold, so it is written as
   * the sum of its parts: the title's band, a gap, and the second sentence's
   * band. Each is a variable, so a breakpoint changes the one it needs to and
   * the total follows — before, the same multiple was written out in three
   * places and one of them was always about to go stale.
   *
   * 1.5 on the title is one line of 1.14 line-height plus the air the design has
   * above and below it. The sub's band is its own size at 1.5 line-height times
   * however many rows it wraps to, which is one on a wide screen and two once the
   * sentences stack. */
  --line-title-h: calc(var(--line-fs) * 1.5);
  --line-sub-fs: clamp(12px, calc(var(--line-fs) * .365), 44px);
  --line-sub-rows: 1;
  --line-sub-gap: clamp(4px, .45vw, 16px);
  --line-sub-h: calc(var(--line-sub-fs) * 1.5 * var(--line-sub-rows));
  /* A little air under the last line. The title's band carries about .18em of
     its own above the letters and the sub's carries only .25 of its much smaller
     size below them, so without this the box reads bottom-tight: generous over
     the title, pinched under the sentence. It is PADDING rather than height, so
     the content stays centred in what is left and the title does not move. */
  --line-sub-pad: calc(var(--line-sub-gap) * .7);
  --line-box-h: calc(
    var(--line-title-h) + var(--line-sub-gap) + var(--line-sub-h) + var(--line-sub-pad)
  );
  /* Taller than the screen, deliberately. Elijah, 2026-08-31: the fade was
     finishing exactly at the fold, which meant the only place it could go was
     UP into the picture. Now the hero runs on past the bottom of the window and
     the whole dissolve happens below it, over the top of Our Shows, which is
     pulled up to meet it. The window shows picture edge to edge; the fade is
     something you scroll into.

     svh rather than vh so a phone's collapsing address bar cannot change it.
     Declared at :root, not here: Our Shows is a sibling and has to read it. */
  height: calc(100svh + var(--hero-over));
  min-height: 340px;
  overflow: hidden;
  /* No background on the hero box itself. Anything opaque here would show
     through the masked fade and put back the seam the mask exists to remove. */
  background: transparent;
}

/* A phone in portrait gets a taller crop; a 2:1 letterbox is a sliver there. */
/* The old mobile hero rule lived here and made the hero half a screen tall while
   the rest of the layout still assumed a full one. The mobile section at the end
   of this file replaces it. */

/* THE FADE, both ends.
 *
 * The hero image is MASKED at the top and the bottom rather than covered by
 * painted gradients, so what fills those bands is Elijah's own background
 * artwork rather than a colour picked to imitate it. That is the whole point:
 * the earlier top band was near-black rgba(3,10,30), which was visibly not the
 * same blue as the field it sat above.
 *
 * The stops are a SMOOTHSTEP curve at both ends, not a straight line. A linear
 * alpha ramp over a photograph reads as a hard edge - it sheds most of its
 * opacity through the middle of the ramp and then lingers, so the eye finds a
 * boundary. Smoothstep leaves 1.0 gently, falls fastest in the middle and lands
 * on 0 flat, which is what actually looks like a dissolve.
 *
 * The top is a wash rather than a cut-out: the mask bottoms out at 0.28 instead
 * of 0, so the band takes the artwork's colour while the picture still reads
 * through it. The bottom fade is the last 14% only.
 */
.hero__frame {
  position: absolute;
  inset: 0;
  -webkit-mask-image: linear-gradient(180deg,
    /* The top band is 12svh exactly as Elijah approved it, restated as a
       percentage of the taller box so it does not grow with it.
       The bottom dissolve runs 84svh to 104svh. Twenty svh between the last
       fully solid pixel and the first fully clear one, which is what stops it
       reading as an edge, and it now finishes four svh higher so it is not
       sitting on Our Shows. Elijah, 2026-08-31. */
    rgba(0, 0, 0, 0) 0.0%,
    rgba(0, 0, 0, .008) 0.71%,
    rgba(0, 0, 0, .035) 1.53%,
    rgba(0, 0, 0, .085) 2.44%,
    rgba(0, 0, 0, .16) 3.36%,
    rgba(0, 0, 0, .26) 4.37%,
    rgba(0, 0, 0, .38) 5.39%,
    rgba(0, 0, 0, .52) 6.41%,
    rgba(0, 0, 0, .66) 7.42%,
    rgba(0, 0, 0, .79) 8.34%,
    rgba(0, 0, 0, .89) 9.15%,
    rgba(0, 0, 0, .96) 9.76%,
    rgba(0, 0, 0, 1) 10.17%,
    /* The picture, uninterrupted. */
    rgba(0, 0, 0, 1) 71.19%,
    rgba(0, 0, 0, .995) 72.88%,
    rgba(0, 0, 0, .98) 74.58%,
    rgba(0, 0, 0, .945) 76.27%,
    rgba(0, 0, 0, .89) 77.97%,
    rgba(0, 0, 0, .81) 79.66%,
    rgba(0, 0, 0, .70) 81.36%,
    rgba(0, 0, 0, .60) 82.71%,
    rgba(0, 0, 0, .47) 84.07%,
    rgba(0, 0, 0, .35) 85.25%,
    rgba(0, 0, 0, .24) 86.27%,
    rgba(0, 0, 0, .14) 87.12%,
    rgba(0, 0, 0, .07) 87.63%,
    rgba(0, 0, 0, .025) 87.97%,
    rgba(0, 0, 0, 0) 88.14%,
    rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(180deg,
    /* The top band is 12svh exactly as Elijah approved it, restated as a
       percentage of the taller box so it does not grow with it.
       The bottom dissolve runs 84svh to 104svh. Twenty svh between the last
       fully solid pixel and the first fully clear one, which is what stops it
       reading as an edge, and it now finishes four svh higher so it is not
       sitting on Our Shows. Elijah, 2026-08-31. */
    rgba(0, 0, 0, 0) 0.0%,
    rgba(0, 0, 0, .008) 0.71%,
    rgba(0, 0, 0, .035) 1.53%,
    rgba(0, 0, 0, .085) 2.44%,
    rgba(0, 0, 0, .16) 3.36%,
    rgba(0, 0, 0, .26) 4.37%,
    rgba(0, 0, 0, .38) 5.39%,
    rgba(0, 0, 0, .52) 6.41%,
    rgba(0, 0, 0, .66) 7.42%,
    rgba(0, 0, 0, .79) 8.34%,
    rgba(0, 0, 0, .89) 9.15%,
    rgba(0, 0, 0, .96) 9.76%,
    rgba(0, 0, 0, 1) 10.17%,
    /* The picture, uninterrupted. */
    rgba(0, 0, 0, 1) 71.19%,
    rgba(0, 0, 0, .995) 72.88%,
    rgba(0, 0, 0, .98) 74.58%,
    rgba(0, 0, 0, .945) 76.27%,
    rgba(0, 0, 0, .89) 77.97%,
    rgba(0, 0, 0, .81) 79.66%,
    rgba(0, 0, 0, .70) 81.36%,
    rgba(0, 0, 0, .60) 82.71%,
    rgba(0, 0, 0, .47) 84.07%,
    rgba(0, 0, 0, .35) 85.25%,
    rgba(0, 0, 0, .24) 86.27%,
    rgba(0, 0, 0, .14) 87.12%,
    rgba(0, 0, 0, .07) 87.63%,
    rgba(0, 0, 0, .025) 87.97%,
    rgba(0, 0, 0, 0) 88.14%,
    rgba(0, 0, 0, 0) 100%);
  z-index: 1;
}

.hero__media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The source is 16:9 and this box is much wider than that on an ultrawide
     monitor, so `cover` crops top and bottom. Biasing upward keeps faces, titles
     and the top of the frame — the parts that carry the shot — rather than
     centring the crop and slicing heads off. */
  object-position: center 32%;
  display: block;

  /* The gap before the first frame. Held at zero rather than filled with a
     poster, so what shows through is the page's own ground and not a still from
     somewhere else in the film. The class is ADDED by script and removed the
     moment the film is playing — that way round, so that with JavaScript off
     nothing ever hides it. */
  transition: opacity .28s var(--ease);
}
.hero__media.is-waiting { opacity: 0; }

/* Marks the box as the pitch video's home until the file exists. Removed the
   moment site.json names a video file. */
.hero__placeholder-label {
  position: absolute;
  left: 50%; bottom: 18%;
  transform: translateX(-50%);
  margin: 0;
  padding: 10px 20px;
  border-radius: 99px;
  border: 1px solid rgba(255, 255, 255, .28);
  background: rgba(2, 9, 32, .55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: 11px; font-weight: 700; letter-spacing: .26em; text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
  white-space: nowrap;
}

/* ---- the fades ------------------------------------------- *
 * Top: darkens the image under the banner so the logo and tabs always read.
 * Bottom: carries the hero into the blue below. The bottom one is the shape
 * Elijah masks the exported background to. */

/* There are deliberately NO painted fade overlays any more, at either end. The
   bottom one stopped dead at the hero's edge while still 40% opaque, and a
   semi-opaque layer that simply stops is a hard line. The top one was a
   near-black wash that never matched the background it sat above. The mask on
   .hero__frame replaces both. */

/* ---- the page field -------------------------------------- *
 * Everything below the hero sits on the exported blue. It repeats vertically,
 * so the export is a seamless vertical loop and the page can be any length.
 * --bg-image is set once the export lands. */

/* Elijah's export, and nothing on top of it.
   Earlier there were two extra radial layers standing in for a vignette. The
   artwork has its own — darker at the edges, brighter through the middle — so
   those are gone rather than doubled up on his. */
.page-home,
.page-pitch,
.page-series,
.page-ask,
.page-tax {
  background-color: var(--bg-deep);
  background-image: var(--bg-image);
  background-repeat: var(--bg-repeat);
  background-position: var(--bg-pos);
  background-size: var(--bg-size);
  /* The blend belongs to whichever background is active, which is why it is a
     variable and not a literal.
     
     It was `overlay, normal, normal, normal, normal` — one entry per layer of
     the CSS-generated version, where the leading `overlay` turned its noise
     layer into texture. The export has ONE layer, so that leading `overlay`
     landed on Elijah's artwork instead and blended it against the navy
     background-colour underneath. Overlay darkens everything below mid-grey, and
     every colour in a deep blue field is below mid-grey, so his gradient came out
     crushed and the wrong hue. It looked like a bad export. It was a leftover. */
  background-blend-mode: var(--bg-blend);
}

/* These pages paint their own field, so the generic one in base.css is off. */
.page-home::before, .page-home::after,
.page-pitch::before, .page-pitch::after,
.page-series::before, .page-series::after,
.page-ask::before, .page-ask::after,
.page-tax::before, .page-tax::after { content: none; }

:root {
  /* ============================================================
     THE FIELD behind Our Shows and the three money pages.
     ============================================================

     Drawn in CSS, not loaded as an image, and that is the point rather than a
     saving. Elijah's export is a fixed rectangle: to cover a page of any length
     it has to be tiled, and tiling a gradient means folding its two ends
     together, which averages them and flattens the thing you wanted. A CSS
     gradient has no size of its own. It is simply the page, at whatever height
     the page turns out to be, with no seam to hide and nothing to loop.

     Two rules it follows, both learned the hard way:

       - NO NOISE. An SVG turbulence layer was in here to fake texture and it
         read as dust on a screen.
       - NO SYMMETRIC VIGNETTE. Darkening both edges equally makes a page look
         lit from the middle, like a spotlight, instead of graded. The light here
         sits OFF centre and falls away unevenly, which is what Elijah's own
         artwork does.

     Colours are sampled from his deck: the blue runs from #143fb8 at the light
     down to #030d33 in the far corner.
     ============================================================ */

  /* Where the light sits. Each page moves it, further down this file, so four
     pages of the same field do not read as one long uniform wall. */
  --bg-x: 34%;
  --bg-y: 28%;

  /* The colour the page continues in below the gradient, and behind it while it
     paints. It is the mid tone rather than the darkest, so a slow paint never
     flashes something darker than the page it is about to become. */
  --bg-deep: #0a2a91;

  --bg-image:
    /* A darker band across the top, so the page starts deep and opens into the
       light rather than beginning at whatever brightness the light happens to
       fall at. It also gives the banner something consistent to sit on, since
       the banner is fixed and the light is not.
       
       The stops are a smoothstep, not a straight line: a linear alpha ramp over
       a gradient sheds most of its opacity through the middle and then lingers,
       which the eye finds as an edge. This leaves 1.0 gently and lands on 0
       flat. Same curve as the hero mask, for the same reason. */
    linear-gradient(180deg,
      rgba(3, 13, 51, .82)  0%,
      rgba(3, 13, 51, .80)  2%,
      rgba(3, 13, 51, .74)  5%,
      rgba(3, 13, 51, .62)  9%,
      rgba(3, 13, 51, .46) 13%,
      rgba(3, 13, 51, .30) 17%,
      rgba(3, 13, 51, .17) 21%,
      rgba(3, 13, 51, .07) 25%,
      rgba(3, 13, 51, .02) 29%,
      rgba(3, 13, 51, 0)   33%),

    /* A second, dimmer bloom from the opposite quarter. It keeps the falloff
       from being a clean radial — which reads as a spotlight — and it breaks up
       the long tonal ramp enough that 8-bit banding never gets a straight run to
       show itself in. Without noise, this is what does that job. */
    radial-gradient(85% 65% at calc(100% - var(--bg-x)) calc(100% - var(--bg-y)),
      rgba(26, 78, 214, .30) 0%,
      rgba(22, 66, 190, .16) 34%,
      rgba(18, 56, 165, .05) 60%,
      rgba(16, 50, 150, 0) 78%),

    /* The field itself. One light, placed off centre, falling to deep navy at
       the far corner. Stops are close together near the light and spread out
       into the shadow, because that is how light actually falls off and an even
       spacing reads as a printed ramp. */
    radial-gradient(125% 95% at var(--bg-x) var(--bg-y),
      #143fb8   0%,
      #1139ae   14%,
      #0e32a2   26%,
      #0b2b93   38%,
      #092480   50%,
      #071d6b   62%,
      #061755   73%,
      #051140   84%,
      #040e37   93%,
      #030d33  100%);

  --bg-size:   100% 100%;
  --bg-repeat: no-repeat;
  --bg-blend:  normal;
  --bg-pos:    0 0;
}

/* No band under the hero any more.
 *
 * There was one, moved five times, to stop the field's light making the section
 * under the film the brightest thing on the site. Elijah, 2026-08-31: the two
 * ends of the hero should behave identically, and the top has no such band. So
 * neither does the bottom. If the brightness under it becomes a problem again
 * the fix belongs in the field itself, where it would apply to both ends, and
 * not in a patch laid over one of them.
 */

/* ---- OUR SERIES rail ------------------------------------- *
 * Placeholder shapes only, by instruction: no thumbnails, just where they go.
 * The track runs off the right edge to imply more, exactly as in the design. */

/* Pulled up under the hero so the heading sits inside the fade rather than
   below a boundary. z-index above .hero__frame, which is 1. Home only: the rail
   is not overlapping anything on the other pages. */
.page-home .rail {
  position: relative;
  z-index: 2;
  margin-top: calc(var(--rail-lift) * -1);
}

.rail {
  padding-block: clamp(2px, .3vw, 10px) clamp(30px, 4vw, 60px);
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
}

.rail__heading {
  font-size: clamp(15px, 1.25vw, 46px);
  font-weight: 900;
  letter-spacing: .1em;
  /* Letter-spacing adds a gap after the LAST letter too, so centred caps sit
     visibly left of centre. Pull that gap back. */
  margin-inline-end: -.09em;
  text-transform: uppercase;
  color: #ffffff;
  margin-bottom: clamp(10px, 1.1vw, 18px);
}

.rail__track {
  display: flex;
  align-items: stretch;
  gap: clamp(8px, .75vw, 30px);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  /* Let the last card run under the right gutter rather than stopping short. */
  margin-right: calc(var(--gutter) * -1);
  padding-right: var(--gutter);
  padding-bottom: 4px;
  scrollbar-width: none;
}
.rail__track::-webkit-scrollbar { display: none; }

.rail__card {
  flex: 0 0 auto;
  height: var(--rail-h);
  width: calc(var(--rail-h) * 0.7);
  aspect-ratio: var(--rail-card-aspect);
  border-radius: clamp(8px, .55vw, 26px);
  scroll-snap-align: start;

  /* Deliberately empty. This is where Elijah's exported artwork goes. */
  background:
    linear-gradient(160deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .05)),
    rgba(8, 28, 84, .5);
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 18px 40px -22px rgba(0, 6, 32, .9);
  transition: transform .45s var(--ease), border-color .45s var(--ease);
}
.rail__card:hover { transform: translateY(-5px); border-color: rgba(255, 180, 90, .4); }

.rail__card--featured {
  width: calc(var(--rail-h) * 16 / 9);
  aspect-ratio: var(--rail-featured-aspect);
}

/* ---- PITCH DECK carousel --------------------------------- *
 * One slide large in the middle, the ones either side of it peeking in, arrows,
 * and a 45 second auto-advance. Full bleed: the peeks are meant to run off both
 * edges of the screen so the deck reads as continuing past the frame.
 *
 * Position is a CLASS per slide (is-prev / is-active / is-next / is-far-*), set
 * by client/carousel.js. Nothing here is written by script, so every measurement
 * lives in this block and the served HTML carries no inline styles. */

.carousel {
  padding-block: clamp(10px, 1.2vw, 30px) clamp(40px, 5vw, 96px);
  margin-inline: calc(var(--gutter) * -1);
}

.carousel__stage {
  position: relative;
  height: var(--slide-h);
  /* Clips the peeks at the screen edges rather than widening the document. */
  overflow: hidden;
}

.carousel__slide,
.show-panel {
  margin: 0;
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--slide-w);
  height: var(--slide-h);
  border-radius: clamp(8px, .55vw, 26px);
  overflow: hidden;
  background: rgba(4, 16, 58, .55);
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 30px 70px -30px rgba(0, 6, 32, .95);
  transform: translateX(-50%);
  /* transform and opacity ONLY. box-shadow was in this list, and it is not a
     composited property: animating it repaints a 2300px card on every frame,
     thirteen times over, which is what made the slide stutter. The shadow now
     simply stays put while the slide moves. */
  transition:
    transform .95s var(--ease-glide),
    opacity .95s var(--ease-glide);
}

.carousel__slide img,
.show-panel img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The four positions. Everything further away parks just outside its neighbour
   and fades out, so a slide always arrives from the side it should. */
.carousel .is-active,
.rail--shows .is-active {
  transform: translateX(-50%) scale(1);
  opacity: 1;
  z-index: 2;
}
.carousel .is-prev,
.rail--shows .is-prev {
  transform: translateX(calc(-50% - var(--peek-shift))) scale(var(--peek-scale));
  opacity: .45;
  z-index: 1;
}
.carousel .is-next,
.rail--shows .is-next {
  transform: translateX(calc(-50% + var(--peek-shift))) scale(var(--peek-scale));
  opacity: .45;
  z-index: 1;
}
.carousel .is-far-left,
.rail--shows .is-far-left {
  transform: translateX(calc(-50% - var(--peek-shift) * 1.85)) scale(.6);
  opacity: 0;
  pointer-events: none;
}
.carousel .is-far-right,
.rail--shows .is-far-right {
  transform: translateX(calc(-50% + var(--peek-shift) * 1.85)) scale(.6);
  opacity: 0;
  pointer-events: none;
}

.carousel .is-prev:hover,
.carousel .is-next:hover,
.rail--shows .is-prev:hover,
.rail--shows .is-next:hover { opacity: .72; }

.carousel__arrow {
  position: absolute;
  top: 50%;
  z-index: 3;
  /* Centred ON the active slide's edge rather than parked at the screen edge, so
     the controls stay with the thing they control however wide the monitor is. */
  transform: translate(-50%, -50%);
  width: clamp(40px, 3.4vw, 112px);
  height: clamp(40px, 3.4vw, 112px);
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .26);
  background: rgba(4, 15, 50, .72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: rgba(255, 255, 255, .92);
  cursor: pointer;
  box-shadow: 0 16px 40px -18px rgba(0, 6, 32, .95);
  transition:
    background .3s var(--ease),
    border-color .3s var(--ease),
    color .3s var(--ease),
    box-shadow .3s var(--ease),
    filter .3s var(--ease),
    transform .3s var(--ease);
}
.carousel__arrow--prev { left: calc(50% - var(--slide-w) / 2); }
.carousel__arrow--next { left: calc(50% + var(--slide-w) / 2); }

/* Touched, it becomes the same object as the View full screen button below it:
   the wordmark's ribbon gradient, its sheen along the top edge, its cast, and
   the dark lip where the ribbon folds — here as the border, since a circle has
   no bottom edge to fold over. One accent, one treatment. */
.carousel__arrow:hover,
.carousel__arrow:focus-visible {
  background: var(--ribbon);
  border-color: var(--ribbon-lip);
  color: #ffffff;
  box-shadow: var(--ribbon-sheen), var(--ribbon-cast), 0 4px 14px -4px rgba(0, 6, 32, .8);
  /* The translate has to be repeated: a bare scale() here would drop it and the
     button would jump to the corner it is positioned from. */
  transform: translate(-50%, -50%) scale(1.07);
}
.carousel__arrow:active {
  background: var(--ribbon);
  border-color: var(--ribbon-lip);
  color: #ffffff;
  filter: brightness(.94);
  transform: translate(-50%, -50%) scale(.97);
}

.carousel__chevron {
  width: 42%;
  height: 42%;
  display: block;
}

/* The arrows do nothing without the script, so they ship with the `hidden`
   attribute and carousel.js takes it off. Note the :not([hidden]) guard: putting
   a display value on the base rule would DEFEAT the attribute, since any display
   declaration overrides the one the browser gives [hidden]. That is also what
   keeps the previous arrow gone on slide 1, where carousel.js puts it back. */
.carousel__arrow:not([hidden]) {
  display: grid;
  place-items: center;
}

/* ---- the way through to the deck ------------------------- *
 * The only way out of the carousel, by instruction: the slides themselves do
 * not navigate. Same ribbon button as the deck page download, centred under the
 * strip where it reads as the end of the section. */

/* ---- no JavaScript --------------------------------------- *
 * Absolutely-positioned slides would pile up on top of each other, so without
 * the script the stage becomes an ordinary scrolling strip. Thirteen real links
 * into the deck page, in order, with no behaviour attached to any of them. */
.carousel:not(.is-live) .carousel__stage {
  display: flex;
  gap: clamp(10px, 1vw, 34px);
  height: auto;
  overflow-x: auto;
  padding-inline: var(--gutter);
  scrollbar-width: none;
}
.carousel:not(.is-live) .carousel__stage::-webkit-scrollbar { display: none; }
.carousel:not(.is-live) .carousel__slide {
  position: static;
  flex: 0 0 auto;
  transform: none;
  opacity: 1;
}

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

/* ---- the money pages, as cards ----------------------------- *
 * Our Ask, GA Tax Credit and Initial Seed have no artwork and are not getting
 * any, so the card IS the design: the page's own title with the same beam the
 * page itself wears, and one line about what is behind it.
 *
 * It sits in the SAME box as a deck slide - .carousel__slide already sets the
 * size, the radius and the five positions - so this only fills it. That is the
 * point: three strips on one page have to read as one object repeated, not as
 * three components that happen to be near each other. */
/* .carousel__slide.page-card, not .page-card: the shared anchor rule further
   down this file sets `display: block` on every slide, and at equal specificity
   the later rule wins. Two class names rather than a reorder, so moving either
   block cannot quietly flatten this card into one line again. */
.carousel__slide.page-card {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: clamp(6px, .7vw, 16px);
  padding: clamp(18px, 2.4vw, 54px);
  text-align: center;
  text-decoration: none;
  color: inherit;
  /* OPAQUE. The gradient is lit from the top so the card reads as lit rather
     than as a flat panel next to thirteen photographs, but the layer under it
     is solid: a deck slide is a photograph and hides whatever is behind it,
     while this card was 82% and the slide sliding past showed straight through
     it. Elijah, 2026-09-01. Every card in a strip has to be as opaque as the
     artwork it sits beside. */
  background:
    radial-gradient(110% 85% at 50% -12%, rgba(120, 176, 255, .38), rgba(120, 176, 255, 0) 62%),
    linear-gradient(178deg, #14409c 0%, #0d2d7e 46%, #0a2263 100%);
}

.page-card__title {
  font-family: var(--font-display);
  /* Sized against the card it lives in, not against the page. The first pass
     used the page-head scale and left a title floating in the middle of a
     1792px card like a caption. */
  font-size: clamp(26px, 3.7vw, 112px);
  font-weight: 900;
  letter-spacing: .04em;
  text-transform: uppercase;
  line-height: 1.05;
  color: #ffffff;
}

/* The beam is built for a page head and carries a lot of transparent padding
   with it. Inside a card that padding is the whole gap, so it is pulled in.
   display:block because it is a <span> here rather than the <div> it is on a
   page head, and an inline element ignores a width. */
.page-card .beam {
  display: block;
  width: min(78%, 520px);
  margin-block: clamp(-14px, -1.1vw, -4px);
}

.page-card__text {
  max-width: 32ch;
  font-size: clamp(13px, 1.5vw, 36px);
  line-height: 1.45;
  color: var(--ink-dim);
}

/* The open card is the one being read; the two beside it are context. Dimming
   the type as well as the card stops a neighbour's sentence competing with the
   one in the middle. */
.carousel .is-prev .page-card__text,
.carousel .is-next .page-card__text { opacity: .5; }

.page-card:hover .page-card__title { color: var(--gold); }

/* THE CARDS EITHER SIDE STAY SOLID, AND THEIR CONTENTS DIM INSTEAD.
 *
 * A slide beside the open one sits at 45% opacity, and for a photograph that is
 * exactly right: it reads as the same picture, further away. For a card it is
 * not, because what shows through 45% of a card is the page field behind it, so
 * the card reads as a hole rather than as something in the distance. Elijah,
 * twice: "the background of the slides are still transparent so you see the
 * background."
 *
 * So the slide itself is opaque in every on-stage position and the recession is
 * carried by the type going quiet. The far positions keep opacity 0, because
 * those are genuinely leaving.
 */
.carousel .page-card.is-prev,
.carousel .page-card.is-next { opacity: 1; }

.page-card > * { transition: opacity .95s var(--ease-glide); }
.carousel .page-card.is-prev > *,
.carousel .page-card.is-next > * { opacity: .42; }
.carousel .page-card.is-prev:hover > *,
.carousel .page-card.is-next:hover > * { opacity: .68; }

/* The hover rule further up this file lifts a neighbour to .72 opacity, which
   would put the transparency straight back on a card. */
.carousel .page-card.is-prev:hover,
.carousel .page-card.is-next:hover { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .page-card > * { transition: none; }
}


/* ---- the two blocks on the home page ---------------------- *
 * Our Shows and Pitch Deck are the same object twice: a title, a carousel, a
 * button. Sharing the wrapper is what keeps them that way — style one and the
 * other follows, rather than two sets of rules drifting apart the first time
 * either is touched. */

.home-block {
  /* Nothing at the top. The hero ends in a fade rather than an edge, so any gap
     under it reads as the page having stopped rather than as breathing room —
     Our Shows should start while the fade is still finishing. Elijah,
     2026-08-31. */
  padding-block: 0 clamp(44px, 4.8vw, 104px);
}

/* A hairline between one block and the next, so Pitch Deck reads as its own
   section rather than as more of the shows above it. On the second block, not
   the first, so there is never a line under the hero.
   
   The space either side of that line is deliberately large. Two strips of
   moving artwork a short gap apart read as one long strip that keeps changing;
   the room is what makes them two things. */
.home-block + .home-block {
  border-top: 1px solid rgba(255, 255, 255, .11);
  padding-top: clamp(44px, 4.8vw, 104px);
}

.home-block__title {
  font-family: var(--font-display);
  font-size: clamp(26px, 3.2vw, 88px);
  font-weight: 900;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-align: center;
  color: #ffffff;
  margin: 0 0 clamp(20px, 2.4vw, 52px);
  line-height: 1;
}

/* The line under each strip, in place of the button that used to sit there.
   Elijah, 2026-08-31.
   
   Capped at the width of the open slide, so it sits under the centre frame
   rather than running the full width of the page and pulling the eye off the
   artwork it belongs to. --slide-w is the same variable both strips size
   themselves from, so the three stay in step by construction. */
.home-block__caption {
  max-width: var(--slide-w);
  margin: clamp(20px, 2.2vw, 46px) auto 0;
  text-align: center;

  /* The heading's face and weight, at about a third of its size. It is a
     statement under the artwork, not a caption on it, so it is set to be read
     from across the room rather than leaned into. Elijah, 2026-08-31.
     
     Uppercase, matching the heading above it. Elijah, 2026-08-31. Tracked a
     little wider than sentence case would be, because caps set tight close up
     and become a block rather than words. */
  font-family: var(--font-display);
  font-size: clamp(18px, 1.7vw, 34px);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: .06em;
  line-height: 1.3;

  /* Lit rather than coloured. The text stays white and the blue sits behind it
     as a soft halo, so it reads as glowing on the deep blue ground instead of
     turning into a blue line on a blue page. */
  color: #ffffff;
  text-shadow:
    0 0 2px rgba(120, 176, 255, .55),
    0 0 18px rgba(78, 142, 255, .55),
    0 0 46px rgba(52, 116, 245, .35);
}

/* ---- the strips arriving --------------------------------- *
 * Our Shows, Pitch Deck and The Numbers each lift into place the first time you
 * reach them, and stay put after that. src/client/reveal.js watches for the
 * block entering the window and adds `is-in`.
 *
 * SAME SAFETY AS THE BANNER: every rule is gated on html[data-reveal], which
 * only a script can set. With JavaScript off, or if anything in that script
 * throws, none of these match and all three strips are simply on the page. A
 * scroll animation must never be the reason a section of the site is blank.
 *
 * Title, then the artwork, then the line underneath, each a beat behind the one
 * before, so the block assembles rather than sliding up as one slab.
 *
 * Transform and opacity only. Both are composited, so this is the GPU moving a
 * layer rather than the page re-laying-out three times while a strip of thirteen
 * slides is already animating inside it. */

html[data-reveal] .home-block__title,
html[data-reveal] .home-block .rail__stage,
html[data-reveal] .home-block .carousel__stage,
html[data-reveal] .home-block__caption {
  opacity: 0;
  /* Deliberately short. A long lift on a phone means the strip is still arriving
     when the next one is already at the top of the window. */
  transform: translateY(26px);
  transition:
    opacity .78s var(--ease-glide),
    transform .78s var(--ease-glide);
}

html[data-reveal] .home-block .rail__stage,
html[data-reveal] .home-block .carousel__stage { transition-delay: .13s; }
html[data-reveal] .home-block__caption { transition-delay: .28s; }

html[data-reveal] .home-block.is-in .home-block__title,
html[data-reveal] .home-block.is-in .rail__stage,
html[data-reveal] .home-block.is-in .carousel__stage,
html[data-reveal] .home-block.is-in .home-block__caption {
  opacity: 1;
  transform: none;
}

/* ---- the page head arriving ------------------------------ *
 * The page head comes in WITH THE BANNER rather than on scroll, because it is
 * already on screen when the page opens - waiting for a scroll that has not
 * happened would leave the top of the page empty. So it hangs off
 * [data-nav-in], the same signal the banner uses, and follows a beat behind it.
 *
 * UNSCOPED, and that is a decision rather than an oversight. `.page-head` exists
 * on exactly four pages - Our Shows, Our Ask, GA Tax Credit and Initial Seed -
 * and all four now open the same way. It was scoped to .page-series while only
 * Our Shows was animated; a selector list that grows by one every time another
 * page is named is a rule waiting to be forgotten. */

html[data-reveal] .page-head__eyebrow,
html[data-reveal] .page-head__title,
html[data-reveal] .page-head__lede {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity .72s var(--ease-glide),
    transform .72s var(--ease-glide);
}
html[data-reveal] .page-head__title { transition-delay: .06s; }
html[data-reveal] .page-head__lede { transition-delay: .30s; }

/* The beam strikes, exactly as the one under the selected tab does: it opens
   along its own length from a point at the centre. `.beam` carries no transform
   of its own, so both states are a plain scaleX. */
html[data-reveal] .page-head .beam {
  opacity: 0;
  transform: scaleX(.05);
  transition:
    opacity .3s var(--ease),
    transform .8s var(--ease-glide);
  transition-delay: .2s;
}

html[data-reveal][data-nav-in] .page-head__eyebrow,
html[data-reveal][data-nav-in] .page-head__title,
html[data-reveal][data-nav-in] .page-head__lede {
  opacity: 1;
  transform: none;
}
html[data-reveal][data-nav-in] .page-head .beam {
  opacity: 1;
  transform: scaleX(1);
}

/* ---- Our Shows arriving ---------------------------------- *
 * Below the fold it is the observer: each group's heading, then each row as you
 * reach it. A row at a time rather than a group at a time, because a group of
 * eight shows is several screens tall and revealing it as one block means seven
 * of the eight have already finished before you see them. */

html[data-reveal] .page-series .slate__heading {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity .6s var(--ease-glide),
    transform .6s var(--ease-glide);
}
html[data-reveal] .page-series .slate__heading.is-in { opacity: 1; transform: none; }

/* Artwork first, then the writing beside it. Not the row as one block: the
   picture is what the eye lands on, and the title reading in a moment later is
   the difference between a row appearing and a row being introduced. */
html[data-reveal] .page-series .show .show__art,
html[data-reveal] .page-series .show .show__text {
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity .74s var(--ease-glide),
    transform .74s var(--ease-glide);
}
html[data-reveal] .page-series .show .show__text { transition-delay: .11s; }

html[data-reveal] .page-series .show.is-in .show__art,
html[data-reveal] .page-series .show.is-in .show__text {
  opacity: 1;
  transform: none;
}

/* ---- the money pages arriving ---------------------------- *
 * Our Ask, GA Tax Credit and Initial Seed. Below the page head it is the
 * observer: the headline figures, the band heading, each table, the notes, and
 * on the tax credit page each column of the detailed breakdown.
 *
 * `.figures`, `.band-heading`, `.section` and `.breakdown__col` exist on these
 * three pages and nowhere else, but the selectors are scoped anyway. `.section`
 * in particular is a name general enough that the day it appears somewhere else
 * it must not silently start hiding itself.
 *
 * THE FIGURES ARE WATCHED AS A ROW, NOT AS THREE CARDS. They sit side by side,
 * so three observers firing a few pixels apart is three cards arriving at three
 * different moments for no reason. One trigger, and the stagger below is what
 * separates them - which is also what the counting hangs off: reveal.js starts
 * the numbers running when this container arrives. */

html[data-reveal] .page-ask .figure,
html[data-reveal] .page-tax .figure {
  opacity: 0;
  transform: translateY(24px);
  /* border-color and box-shadow are in this list for the hover rules further
     down, NOT for the entrance. A transition declaration replaces rather than
     extends, so a hover rule that declared its own would have to repeat all
     four; listing them once here is the version that cannot drift. */
  transition:
    opacity .7s var(--ease-glide),
    transform .7s var(--ease-glide),
    border-color .3s var(--ease),
    box-shadow .35s var(--ease);
}
html[data-reveal] .page-ask .figure:nth-child(2),
html[data-reveal] .page-tax .figure:nth-child(2) { transition-delay: .11s; }
html[data-reveal] .page-ask .figure:nth-child(3),
html[data-reveal] .page-tax .figure:nth-child(3) { transition-delay: .22s; }
html[data-reveal] .page-ask .figure:nth-child(4),
html[data-reveal] .page-tax .figure:nth-child(4) { transition-delay: .33s; }

html[data-reveal] .page-ask .figures.is-in .figure,
html[data-reveal] .page-tax .figures.is-in .figure { opacity: 1; transform: none; }

html[data-reveal] .page-ask .band-heading,
html[data-reveal] .page-tax .band-heading,
html[data-reveal] .page-ask .section,
html[data-reveal] .page-tax .section,
html[data-reveal] .page-tax .breakdown__col {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity .72s var(--ease-glide),
    transform .72s var(--ease-glide);
}
html[data-reveal] .page-ask .band-heading.is-in,
html[data-reveal] .page-tax .band-heading.is-in,
html[data-reveal] .page-ask .section.is-in,
html[data-reveal] .page-tax .section.is-in,
html[data-reveal] .page-tax .breakdown__col.is-in { opacity: 1; transform: none; }

/* ---- hover on the money pages ---------------------------- *
 * EVERY RULE HERE IS BEHIND (hover: hover) AND (pointer: fine), which is the
 * whole reason it is safe to put on pages that are read on a phone. A touch
 * screen has no hover: it synthesises one on tap and then LEAVES IT STUCK on the
 * last thing touched, so an unguarded :hover rule is a row that stays lit and
 * scaled until you tap something else. These rules simply do not exist there.
 *
 * They are outside the [data-reveal] gate on purpose. Hover is not an entrance -
 * it costs nothing when idle, hides nothing, and should work with the entrance
 * animations turned off, on a reduced-motion machine, or if reveal.js never runs
 * at all. */
@media (hover: hover) and (pointer: fine) {
  /* A row lifts toward you. The scale is DELIBERATELY SMALL, and the number is
     set by the cell padding rather than by taste: the table is exactly as wide
     as its scroll container on a desktop, so whatever it grows by is clipped at
     both ends. At 1.008 a 1000px table grows 4px each side, well inside the 18px
     of padding in the first and last cells, so nothing readable ever reaches the
     edge.

     The container hides its own scrollbars, so the fraction of a pixel the top
     and bottom rows grow past the frame cannot produce one. */
  .ask-table tbody tr,
  .ask-table tfoot tr {
    transition:
      transform .26s var(--ease),
      background-color .26s var(--ease),
      box-shadow .26s var(--ease);
  }
  .ask-table tbody tr:not(.is-group):hover,
  .ask-table tfoot tr:hover {
    transform: scale(1.008);
    background: rgba(255, 255, 255, .06);
    box-shadow: 0 14px 30px -18px rgba(0, 6, 32, .95);
  }
  /* A group heading is a label across the table, not a row of figures. It is
     already excluded from the background highlight, and it is excluded from this
     for the same reason. */
  .ask-table tbody tr.is-group:hover { transform: none; }

  /* The headline figures. Not links, so this is emphasis rather than an
     affordance: the card under the pointer brightens at its edge and lifts off
     the page. */
  .figure:hover {
    border-color: rgba(255, 255, 255, .3);
    box-shadow: 0 26px 54px -26px rgba(0, 6, 32, .98);
  }
}

/* ---- the deck arriving ----------------------------------- *
 * A slide settles rather than slides: the figure fades up while the picture
 * inside it comes off a very slight scale. That reads as the slide coming to
 * rest, and it is the right gesture for a deck, where thirteen identical
 * rectangles sliding past each other would be motion for its own sake.
 *
 * THE TRANSFORM IS ON THE PICTURE, NEVER ON .slide. A slide is a scroll-snap
 * target on this page (`scroll-snap-align: center; scroll-snap-stop: always`),
 * and a transform moves the snap area with it - the deck would snap to a
 * position that no longer exists by the time the animation finished. The figure
 * gets opacity only, which snapping does not care about.
 *
 * Scaling UP to rest, inside the figure's `overflow: hidden`, also means the
 * picture always more than covers its box. Sliding it would show a strip of the
 * figure's own background along one edge for the length of the animation. */

html[data-reveal] .page-pitch .slide {
  opacity: 0;
  transition: opacity .6s var(--ease-glide);
}
html[data-reveal] .page-pitch .slide img {
  transform: scale(1.035);
  transition: transform .82s var(--ease-glide);
}
html[data-reveal] .page-pitch .slide.is-in { opacity: 1; }
html[data-reveal] .page-pitch .slide.is-in img { transform: none; }

/* The back-to-top block. OPACITY ONLY, and both halves of that are deliberate.
   `.page-pitch .deck__end` is a snap target like a slide, so it cannot be
   transformed - and the obvious alternative, transforming the button inside it,
   is worse: .deck__top already owns `transform` for its hover lift and its own
   three-property transition, and a reveal rule on the same property would
   outrank the hover and kill it for good. */
html[data-reveal] .page-pitch .deck__end { opacity: 0; transition: opacity .6s var(--ease-glide); }
html[data-reveal] .page-pitch .deck__end.is-in { opacity: 1; }

/* The counter and ticks arrive with the banner, not on a scroll: they are the
   same kind of thing, furniture that frames the page rather than content in it.
   deck.js reveals them by adding is-deck-enhanced to <html>, so with the script
   gone this selector has nothing to match and nothing is hidden that was not
   already hidden. */
html[data-reveal] .deck__hud {
  opacity: 0;
  transition: opacity .8s var(--ease);
  transition-delay: .24s;
}
html[data-reveal][data-nav-in] .deck__hud { opacity: 1; }

/* ---- the line over the hero ------------------------------ *
 * A box that draws itself in, with the sentence inside it in the site's own
 * display face. It was a rendered video with an alpha channel for most of a day;
 * as real type it selects, it is sharp at any size, it costs nothing to
 * download, and changing the words is editing a string in site.json.
 */
.hero-line {
  position: absolute;
  left: 50%;
  /* ANCHORED BY ITS TOP EDGE, not its middle.
   *
   * Centring it on --line-top was right when the box held one line. With two, it
   * would open equally in both directions and push the title upward off the
   * position it was designed at every time the second line changed. Elijah asked
   * for it to expand DOWN. So the top edge is pinned where the one-line box's top
   * edge used to be — half a title band above --line-top — and everything the box
   * gains, it gains below. */
  top: calc(var(--line-top) - var(--line-title-h) / 2);
  z-index: 2;

  width: fit-content;
  /* Nearly the whole window: the point of this block is that it runs the width
     of the screen, so the page gutter would be 260px the line cannot use. */
  max-width: calc(100vw - clamp(4px, .35vw, 14px) * 2);
  padding-inline: clamp(6px, .5vw, 20px);
  padding-bottom: var(--line-sub-pad);
  box-sizing: border-box;

  border: 2px solid rgba(150, 212, 255, 1);
  border-radius: clamp(4px, .4vw, 10px);
  background: rgba(0, 0, 0, .26);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  backdrop-filter: blur(16px) saturate(1.15);

  box-shadow:
    0 0 0 1px rgba(214, 238, 255, .70),
    0 0 10px rgba(120, 196, 255, .95),
    0 0 26px rgba(70, 166, 255, .78),
    0 0 60px rgba(46, 136, 250, .52),
    0 0 120px rgba(30, 104, 226, .30),
    inset 0 0 34px rgba(96, 184, 255, .30);

  overflow: hidden;
  pointer-events: none;

  /* A column now: the title, then the sentence under it, both centred. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--line-sub-gap);

  /* CLOSED. A 2px box with a 2px border is a rule, which is the first move. It
     is scaled to nothing horizontally, so there is not even a dot on screen
     until it is asked for. Only the X is translated, because the top edge is the
     anchor. */
  transform: translateX(-50%) scaleX(0);
  height: 2px;

  transition:
    transform .75s var(--ease-glide),
    height .5s var(--ease-glide);
}

/* 1. the rule opens, centre outward. */
.hero-line.is-line { transform: translateX(-50%) scaleX(1); }

/* 2. the box opens around it. The height is --line-box-h, declared on .hero, so
      the sentence underneath knows how far down to sit. */
.hero-line.is-open { height: var(--line-box-h); }

/* The two halves are one line. They stay separate elements only so the reveal
   knows where to take its beat; they read as a single sentence. */
.hero-line__row { display: inline; }

.hero-line__text { white-space: nowrap; }

/* On a phone the sentence cannot be one line at any readable size, so it wraps
   and the box grows to hold it. Better a wrapped line than a clipped one. */
@media screen and (max-width: 899px) {
  .hero-line__text { white-space: normal; }
  /* The title wraps to two rows here, and the sentence under it stacks into two
     as well. --line-box-h adds them up on its own. */
  .hero { --line-title-h: calc(var(--line-fs) * 4.3); --line-sub-rows: 2; }
}

.hero-line__text {
  margin: 0;
  text-align: center;

  font-family: var(--font-display);
  font-size: var(--line-fs);
  font-weight: 900;
  letter-spacing: .09em;
  /* Letter-spacing adds a gap after the LAST letter too, so centred caps sit
     visibly left of centre. Pull that gap back. */
  margin-inline-end: -.09em;
  text-transform: uppercase;
  line-height: 1.14;
  color: #ffffff;

  /* 3. the words arrive last, after the box has somewhere to put them. */
  opacity: 0;
  transition: opacity .55s var(--ease-glide);
}
.hero-line.is-text .hero-line__text { opacity: 1; }

/* One word. It is in the markup from the first frame and holds its own width,
   so the box is measured once and nothing reflows as the line arrives. Only the
   opacity and a few pixels of lift change. */
.hero-line__w {
  display: inline-block;
  opacity: 0;
  transform: translateY(.16em);
  transition:
    opacity .5s var(--ease-glide),
    transform .5s var(--ease-glide);
}
.hero-line__w.is-in {
  opacity: 1;
  transform: none;
}

/* ---- the second sentence, inside the box ----------------- *
 * "15 original shows. 150+ episodes. Framing the world for the next generation."
 *
 * IN THE BOX, under the title. Elijah, 2026-09-09. It was a sibling sitting
 * below it first, because the box's open height has to be a fixed number — auto
 * cannot be animated — and that number used to be a single multiple of
 * --line-fs, which only described the title.
 *
 * What made it safe to move in is that --line-box-h is now a SUM: the title's
 * band, plus the gap, plus this sentence's own band. Both bands are variables on
 * .hero, so a breakpoint changes the part it needs to and the total follows. The
 * box also grows downward from a pinned top edge rather than opening in both
 * directions, so putting a line in here never shifts the title off the position
 * it was designed at.
 *
 * No second border, no second glow: one lit box with two voices in it. This is
 * the quieter one, and everything about it says so — a third of the size, less
 * tracking, and it arrives after the title has finished.
 */
.hero-sub {
  margin: 0;
  max-width: 100%;
  text-align: center;
  pointer-events: none;

  font-family: var(--font-display);
  /* A little over a third of the title, and the same variable --line-box-h adds
     up, so the box can never be shorter than the sentence inside it. */
  font-size: var(--line-sub-fs);
  line-height: 1.5;
  font-weight: 700;
  letter-spacing: .17em;
  margin-inline-end: -.17em;   /* tracking adds a gap after the last letter too */
  text-transform: uppercase;
  line-height: 1.5;
  color: #ffffff;

  /* A quieter version of the cool light the Our Shows tagline carries. Inside the
     box it is over film as well as over the backdrop blur, so it still needs a
     halo to hold against a bright frame — just less of one than it needed when it
     was sitting on the picture with nothing behind it. */
  text-shadow:
    0 1px 10px rgba(2, 10, 40, .7),
    0 0 .5em rgba(150, 200, 255, .35);

  /* Arrives as ONE PIECE, after the last word of the line above has landed
     (home.js). A second word-by-word reveal directly under the first is the same
     trick done twice. */
  opacity: 0;
  transform: translateY(.5em);
  transition:
    opacity .85s var(--ease-glide),
    transform .85s var(--ease-glide);
}

.hero-sub.is-in {
  opacity: 1;
  transform: none;
}

/* On a wide screen the three sentences run together as one line. The spans exist
   so a phone can put them on their own rows instead of breaking after a full
   stop wherever the width happens to fall. */
.hero-sub__row { display: inline; }

@media screen and (max-width: 899px) {
  .hero {
    /* Its own size here rather than a fraction of the title's: the title is
       being sized to fit two wrapped rows on a narrow screen, and a third of
       that is smaller than this sentence can be read at. --line-box-h reads
       this, so the box grows to match. */
    --line-sub-fs: clamp(10px, 2.9vw, 15px);
  }
  .hero-sub {
    /* The first two sentences are short and belong together; only the long one
       gets a row of its own. Flex with wrap does that without a rule per row,
       and --line-sub-rows: 2 is what tells the box to expect it. */
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0 .6em;
    width: 100%;
    letter-spacing: .12em;
    margin-inline-end: 0;
  }
  .hero-sub__row:last-child { flex-basis: 100%; }
}

/* Reduced motion gets the line without the performance: no draw-on, just the
   finished box. */
@media (prefers-reduced-motion: reduce) {
  .hero-line {
    transition: none;
    transform: translateX(-50%) scaleX(1);
    height: var(--line-box-h);
  }
  .hero-line__text { transition: none; opacity: 1; }
  .hero-line__w { transition: none; opacity: 1; transform: none; }
  /* The second sentence is simply THERE, exactly as the title beside it is.
     These rules make the box visible without waiting for a class, and the line
     used to wait for one anyway - so under this setting the title appeared at
     once and the sentence under it arrived two seconds later off a word timer,
     which is the two halves of one headline disagreeing. Codex, 2026-09-09. */
  .hero-sub {
    transition: none;
    opacity: 1;
    transform: none;
  }
}

/* The heading is a link to the tab the strip belongs to. It must not look like
   one until you are on it: the whole block is clickable, and an underlined
   heading in the middle of a picture wall reads as a mistake. */
.home-block__title a {
  color: inherit;
  text-decoration: none;
  transition: color .3s var(--ease);
}
.home-block__title a:hover { color: var(--gold); }

/* A button with nothing behind it yet. Held back rather than dressed up, and
   labelled underneath, because a placeholder that looks finished is one nobody
   notices before it ships. */
.ribbon-button.is-placeholder {
  filter: grayscale(.55) brightness(.72);
  cursor: not-allowed;
}
.ribbon-button.is-placeholder:hover {
  transform: none;
  filter: grayscale(.55) brightness(.72);
  box-shadow: var(--ribbon-sheen), var(--ribbon-cast), 0 4px 14px -4px rgba(0, 6, 32, .8);
}
.actions__note {
  margin: 0;
  font-size: clamp(10px, .66vw, 14px);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---- the shows rail --------------------------------------- *
 *
 * It IS the pitch deck carousel, pointed at the show artwork. Rather than a
 * second set of rules that look the same today and drift apart the first time
 * either is touched, the panels JOIN the carousel's selectors further down this
 * file — same size, same peek scale, same offsets, same arrows. There is one
 * carousel on this page, used twice. */

.rail--shows .rail__stage {
  position: relative;
  height: var(--slide-h);
  /* Pulled out to the full width of the window before clipping. `main` insets
     this page by a gutter, so without the negative margin the stage clipped the
     peeks a hundred-odd pixels in from each edge and they looked cut off rather
     than running off the screen. The deck carousel has done this from the start
     — the shows rail lost it when it was rebuilt. */
  margin-inline: calc(var(--gutter) * -1);
  overflow: hidden;
}

/* Without the script the stage is a plain scrolling row: absolutely positioned
   panels would otherwise pile on top of each other in the middle. */
.rail--shows:not(.is-live) .rail__stage {
  display: flex;
  gap: clamp(10px, 1vw, 34px);
  height: auto;
  overflow-x: auto;
  padding-inline: var(--gutter);
  scrollbar-width: none;
}
.rail--shows:not(.is-live) .rail__stage::-webkit-scrollbar { display: none; }
.rail--shows:not(.is-live) .show-panel {
  position: static;
  flex: 0 0 auto;
  transform: none;
  opacity: 1;
}

/* Both are anchors now, to /pitch and /series. A link inherits the page's link
   colour and an underline, neither of which belongs on a picture. */
.carousel__slide,
.show-panel {
  display: block;
  appearance: none;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}
.show-panel.is-active { cursor: default; }
.show-panel:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.show-panel:hover { border-color: rgba(255, 190, 110, .45); }

/* ---- homepage document below the rail -------------------- *
 * The pitch sections stay a readable width even though the rail is full bleed. */

.page-home .page-head,
.page-home .section {
  max-width: var(--max);
}


/* ---- page-pitch.css ---- */
/* ============================================================
   Pitch deck page — the deck laid out vertically.
   ============================================================ */

.page-pitch main {
  max-width: none;
  /* The deck computes its own top spacing, banner height included. */
  margin-top: 0;
  /* The deck computes its own top spacing to centre slide one. main's default
     top padding sat on top of that and pushed everything down by exactly that
     much, which is why slide one was low and every slide after it was fine. */
  padding-top: 0;
}

.deck {
  width: 100%;
  max-width: var(--deck-w);
  margin-inline: auto;
  padding-block: clamp(18px, 2.6vw, 48px);
  display: grid;
  gap: clamp(18px, 2.2vw, 46px);
}

/* One slide, one tracked section. The source art is 1920 wide, so the container
   is capped near that: stretching a 1920 slide across a 3840 monitor doubles
   every pixel and the type on the slides goes soft. */
.slide {
  margin: 0;
  border-radius: clamp(8px, .55vw, 18px);
  overflow: hidden;
  background: rgba(4, 16, 54, .5);
  box-shadow: 0 26px 60px -30px rgba(0, 6, 32, .9);
  border: 1px solid rgba(255, 255, 255, .1);
}
.slide img {
  display: block;
  width: 100%;
  height: auto;
  /* Reserves the 16:9 box before the image arrives, so lazily loaded slides do
     not shunt the page around under the reader as they land. */
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.deck__end {
  display: grid;
  justify-items: center;
  gap: 12px;
  padding-block: clamp(30px, 5vw, 80px) clamp(10px, 2vw, 30px);
}

.deck__top {
  display: inline-flex; align-items: center; gap: .7em;
  padding: clamp(15px, 1.15vw, 26px) clamp(24px, 2vw, 46px);

  border: 0;
  border-radius: var(--ribbon-radius);
  border-bottom: max(3px, .22em) solid var(--ribbon-lip);

  background: var(--ribbon);
  color: #ffffff;
  font-size: clamp(12px, .78vw, 19px);
  font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  text-shadow: 0 2px 0 rgba(132, 26, 0, .4);

  box-shadow: var(--ribbon-sheen), var(--ribbon-cast);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), filter .35s var(--ease);
}
.deck__top:hover {
  transform: translateY(-3px);
  filter: brightness(1.06);
  box-shadow: var(--ribbon-sheen), 0 24px 48px -14px rgba(190, 55, 0, .78);
}
.deck__top-arrow { font-size: 1.2em; line-height: 1; }

.deck__end-note {
  font-size: clamp(10px, .62vw, 14px);
  font-weight: 700; letter-spacing: .24em; text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---- deck scrolling -------------------------------------- *
 * Mandatory snap: a scroll moves to the next slide and centres it rather than
 * landing between two. Pure CSS, so this works with JavaScript disabled; the
 * only thing deck.js adds on top is the 45-second idle advance.
 *
 * The banner is sticky on this page. With mandatory snapping, a banner that
 * scrolls away at the top can become unreachable — scrolling up just snaps back
 * to slide one — so it stays put, and scroll-padding keeps it from covering the
 * slide it is centring. */

/* No scrollbar on the deck. The column of ticks down the left says where you are
   and takes you anywhere, which is everything the scrollbar was doing here and
   more, so the bar on the right is one more thing beside a full-screen picture
   for no gain. The page still scrolls by every other means: wheel, trackpad,
   arrow keys, Home and End, and the ticks themselves.
   
   Only on this page. Everywhere else the scrollbar is the sole indication that
   there is more below, and removing it there would hide the page from the
   reader. */
html.is-deck { scrollbar-width: none; }
html.is-deck::-webkit-scrollbar { width: 0; height: 0; display: none; }

html.is-deck {
  scroll-snap-type: y mandatory;
  scroll-padding-top: var(--nav-h);
}

/* deck.js adds this to <html> once it has taken the scroll over. Native snapping
   fights an animated scroll and the result stutters, so it goes off — but only
   when something is actually driving the scroll instead. With JavaScript off,
   blocked, or thrown, the class never appears and the rules above stand, which
   is the whole fallback.

   A class rather than a root.style write, so both halves of the decision are in
   this file and the build output contains no inline styles at all. */
html.is-deck.is-deck-enhanced {
  scroll-snap-type: none;
  scroll-behavior: auto;
}

html.is-deck.is-deck-enhanced .slide,
html.is-deck.is-deck-enhanced .deck__end {
  scroll-snap-align: none;
  scroll-snap-stop: normal;
}

/* The banner is fixed, so it takes no layout space and slide one would otherwise
   sit at the very top of the page on load — uncentred, unlike every slide after
   it, which the scroll controller centres properly.
   
   Rather than scripting a jump on load (which flashes, and does nothing with
   JavaScript off), the deck simply opens with enough padding that slide one is
   already centred in the space below the banner at scroll position zero:
   
       banner height + half of whatever is left over after the slide
   
   max() keeps it sane on a viewport too short to centre anything. */
/* A slide is sized by whichever runs out first, WIDTH or HEIGHT.
 *
 * It used to be width alone, and on a monitor that is wide but not tall — a
 * 1920x1080 panel with browser chrome eating a hundred pixels — a slide came out
 * taller than the space under the banner and simply could not be seen whole. The
 * reader has to scroll a slide to read a slide, which defeats the deck.
 *
 * The height term is the tallest 16:9 box that fits below the banner with a
 * little air, converted back to a width. Whichever of the three is smallest wins,
 * so the deck fits any monitor without a media query per size.
 *
 * --deck-slide-h, not --slide-h: that name belongs to the home carousel, and two
 * different slides sharing one variable is a trap for whoever edits next. */
.page-pitch .deck {
  --deck-air: clamp(24px, 7vh, 96px);
  --deck-w: min(
    2100px,
    calc(100vw - 2 * var(--gutter)),
    calc((100vh - var(--nav-h) - var(--deck-air)) * 16 / 9)
  );
  --deck-slide-h: calc(var(--deck-w) * 9 / 16);
  padding-top: max(
    clamp(18px, 2.6vw, 48px),
    calc(var(--nav-h) + (100vh - var(--nav-h) - var(--deck-slide-h)) / 2)
  );
}

.page-pitch .slide,
.page-pitch .deck__end {
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

/* ---- where you are in the deck ---------------------------- *
 *
 * A vertical column down the LEFT edge, centred against the slide. Vertical
 * because the deck moves vertically: the ticks then run the same way the reader
 * does, and the mark that lights up is at the height of the slide it belongs to
 * rather than at a right angle to the motion. Left because the download sits
 * bottom right and the two must never compete — one is the thing to do, the
 * other is only where you are.
 *
 * Hidden until deck.js adds is-deck-enhanced, exactly like the carousel arrows.
 * Without the script the page still scrolls, and a counter that could not follow
 * the scroll would be worse than no counter. */

.deck__hud { display: none; }

html.is-deck-enhanced .deck__hud {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, .9vw, 20px);
  position: fixed;
  left: max(clamp(20px, 1.9vw, 52px), env(safe-area-inset-left, 0px));
  /* Centred on the viewport rather than pinned to a corner: the slide it
     describes is centred too, so the eye finds them together. */
  top: 50%;
  transform: translateY(-50%);
  z-index: 80;
}

.deck__count {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(12px, .78vw, 19px);
  font-weight: 900;
  letter-spacing: .1em;
  color: var(--ink-dim);
  /* The number changes every slide, so it lines up on its digits rather than
     shifting the slash sideways between 9 and 10. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.deck__count-now { color: #ffffff; }
.deck__count-sep { opacity: .45; margin-inline: .3em; }

.deck__ticks {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(5px, .42vw, 11px);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A real button, so it is focusable and works from the keyboard with nothing
   added. The padding is the click target and the ::before is the visible mark:
   a 3px target is a design that only works for people with a steady hand. */
.deck__tick {
  appearance: none;
  border: 0;
  padding: clamp(3px, .22vw, 5px) clamp(6px, .5vw, 12px);
  background: none;
  cursor: pointer;
  display: block;
}
.deck__tick::before {
  content: '';
  display: block;
  /* A horizontal dash in a vertical stack. The current one grows sideways, away
     from the column, so the stack itself never shifts as you move through it. */
  width: clamp(10px, .85vw, 22px);
  height: clamp(2px, .16vw, 4px);
  border-radius: 99px;
  background: rgba(255, 255, 255, .26);
  transition: background .3s var(--ease), width .3s var(--ease);
}
.deck__tick:hover::before { background: rgba(255, 255, 255, .55); }
.deck__tick.is-on::before {
  background: var(--accent);
  width: clamp(18px, 1.5vw, 38px);
  box-shadow: 0 0 clamp(6px, .5vw, 14px) rgba(255, 150, 40, .7);
}
.deck__tick:focus-visible::before {
  background: #ffffff;
  box-shadow: 0 0 0 2px rgba(255, 160, 50, .9);
}

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

/* Someone who has asked for less motion gets the snapping without the glide. */
@media (prefers-reduced-motion: reduce) {
  html.is-deck { scroll-behavior: auto; }
}


/* ---- page-series.css ---- */
/* ============================================================
   Our Series — the slate. One row per show: artwork on the left,
   title and logline on the right, running down the page.
   ============================================================ */

/* Capped and centred, so the whole slate sits in the middle of the screen with
   even margins either side. The writing inside it stays left aligned — the block
   is centred, not the text. Without the cap the rows started hard against the
   left gutter with a field of empty blue to the right on a wide monitor. */
.page-series main { max-width: min(1700px, 100%); }

/* Capped to what the content actually needs — the artwork column, the gap, and
   the logline's measure — then centred. The page container was already centred,
   but the rows stretched across all of it while the writing only filled the
   left half, so the whole block read as shoved against the left gutter with a
   field of empty blue beside it. Narrowing the block is what centres it; the
   text inside stays left aligned. */
.slate {
  display: grid;
  gap: clamp(40px, 5vw, 96px);
  padding-bottom: clamp(40px, 6vw, 110px);
  /* An explicit percentage of the VIEWPORT, then centred. Nothing intrinsic.
     `fit-content` looked right on first paint and then drifted back to the left
     on a resize or a reload: it derives the width from the content, and the
     content's own tracks were sized in vw and ch, so the answer changed
     depending on whether the webfont had loaded and in what order things
     resolved. A percentage has no such argument with itself — it is the same
     number every layout pass. */
  width: min(1400px, 78vw);
  max-width: 100%;
  margin-inline: auto;
}

.slate__group { display: grid; gap: clamp(16px, 1.8vw, 34px); }

/* Set like the banner: same face, same weight, same tracking. */
.slate__heading {
  font-size: clamp(13px, 1vw, 24px);
  font-weight: 900;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-bottom: clamp(10px, 1vw, 18px);
  border-bottom: 1px solid var(--rule-soft);
}

.slate__rows { display: grid; gap: clamp(20px, 2.2vw, 44px); }

.show {
  display: grid;
  /* Both columns are percentages of the block above, so the artwork's left edge
     and the text's right edge are always the same distance from their side of
     the screen, at any width. */
  grid-template-columns: 40% minmax(0, 1fr);
  gap: 4%;
  align-items: center;
}

/* One rule for both states: the empty box and the real artwork are the same
   shape, so a row does not move when its picture arrives. */
.show__art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: clamp(10px, .8vw, 22px);
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 20px 44px -24px rgba(0, 6, 32, .9);
}

/* The placeholder fill only applies to the empty div, never behind a picture. */
div.show__art {
  background:
    linear-gradient(160deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .05)),
    rgba(8, 28, 84, .5);
}

.show__text { display: grid; gap: clamp(6px, .55vw, 12px); }

.show__title {
  font-size: clamp(24px, 2.7vw, 72px);
  font-weight: 900;
  letter-spacing: .04em;
  text-transform: uppercase;
  line-height: 1.1;
  color: #ffffff;
}

.show__subject {
  font-size: clamp(11px, .8vw, 21px);
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.show__logline {
  font-size: clamp(13px, .95vw, 22px);
  line-height: 1.65;
  color: var(--ink-dim);
  /* No measure cap: the column is a fixed share of the block, so it already is
     the measure. Capping it again would leave dead space inside the column and
     put the imbalance back. */
}

/* On a phone the artwork goes above the writing rather than beside it: a 24vw
   thumbnail next to text is unreadable at 390px. */
@media screen and (max-width: 720px) {
  .show { grid-template-columns: 1fr; gap: 14px; }
  .show__art { max-width: 460px; }
}

/* ---- centred page head with a beam divider --------------- *
 * Used on Our Series: title, the light beam, then the line. All centred as a
 * block and centre-aligned as text. Everything BELOW it stays left aligned. */

/* Trying the rounded display face here first, on Our Series only. If it works
   it moves up to a plain .page-head__title rule and covers every page. */
.page-head--centred {
  justify-items: center;
  text-align: center;
  /* Tight. The beam's box is mostly transparent padding, so the gap here is on
     top of that and the three elements drifted apart. The beam pulls itself back
     in with negative margins below. */
  gap: clamp(2px, .3vw, 10px);
}

.page-head--centred .page-head__title {
  font-weight: 900;
}

.page-head--centred .page-head__lede {
  font-size: clamp(15px, 1.5vw, 34px);
  font-weight: 800;
  color: #ffffff;
  /* One line. It is a single short sentence and breaking it in half reads as an
     accident rather than a decision. */
  max-width: none;
  white-space: nowrap;
  /* A cool glow rather than the beam's warm one, so it reads as its own light
     instead of spill from the streak above it. */
  text-shadow:
    0 0 .2em rgba(255, 255, 255, .35),
    0 0 .6em rgba(120, 180, 255, .62),
    0 0 1.4em rgba(60, 130, 255, .5);
}

/* The same anamorphic streak as the selected nav tab, at page scale. Four
   stacked ellipses rather than a bar with a shadow: an ellipse is thickest at
   its centre and thins to nothing at both ends on its own, which is the taper a
   real light streak has and a rectangle cannot fake. */
/* The box has to keep the nav beam's PROPORTIONS, not just its gradients. The
   ellipse sizes are percentages of this element, so stretching a short box out
   to 760px wide spread the same falloff across ten times the distance and the
   glow went thin and washed out. aspect-ratio locks the shape instead. */
.beam {
  position: relative;
  /* Sized against the VIEWPORT, not its container. It used to be 72% of whatever
     it sat inside, which meant a different beam on every page: inside the money
     pages' 74ch prose column it came out two thirds the height it has on Our
     Shows, and since the negative margins below cancel a fixed amount of dead
     space, the shorter box got over-pulled and the streak landed across the
     bottom of the title instead of under it. One beam, one size, everywhere. */
  width: min(820px, 72vw);
  aspect-ratio: 5 / 1;
  /* The 5:1 box is mostly transparent above and below the streak, so it claims
     far more vertical space than it shows. These pull that dead space back out
     rather than letting it push the title and the line apart. */
  margin-block: clamp(-72px, -5vw, -22px);
  pointer-events: none;

  /* Four stacked ellipses: white core, hot inner, orange body, outer bloom.
   *
   * This is the version that works, and it has now survived two attempts to
   * improve it. A linear colour ramp masked into a beam shape gave a genuine
   * left-to-right fade and looked thin and washed; offset ellipses of different
   * hues gave coloured blobs with a gap. The reason both failed is the same: the
   * thing that makes this read as light is that every layer shares one centre
   * and one colour, so the falloff is the only thing the eye has to follow.
   *
   * Solid orange, four layers, nothing else. A red tint off the left end was
   * tried and dropped the same minute. If anything is ever added here it goes at
   * the END of the stack so it paints behind these four, or it flattens the
   * core. */
  background:
    radial-gradient(ellipse 34% 1.3% at 50% 50%,
      #ffffff 0%,
      rgba(255, 248, 230, 1) 30%,
      rgba(255, 210, 130, .68) 64%,
      rgba(255, 175, 65, 0) 100%),
    radial-gradient(ellipse 35% 4% at 50% 50%,
      rgba(255, 214, 140, 1) 0%,
      rgba(255, 162, 52, .8) 38%,
      rgba(255, 140, 32, 0) 100%),
    radial-gradient(ellipse 39% 13% at 50% 50%,
      rgba(255, 152, 40, .95) 0%,
      rgba(255, 135, 26, .55) 42%,
      rgba(255, 122, 18, 0) 80%),
    radial-gradient(ellipse 50% 44% at 50% 50%,
      rgba(255, 134, 26, .58) 0%,
      rgba(255, 120, 18, .22) 46%,
      rgba(255, 112, 14, 0) 78%);
}

/* ---- sparks ---------------------------------------------- *
 * Faint embers coming off the line. Eighteen of them, placed once with a fixed
 * seed rather than at random on every build, so the beam looks the same on every
 * page and in every screenshot.
 *
 * Two rules make them read as sparks instead of dust, which is the failure mode:
 * they cluster tightly on the streak's line and spread further out only toward
 * the ends, the way embers actually leave a flame; and the further one sits from
 * the line the fainter it is. Dust is evenly scattered and evenly bright.
 *
 * Drawn on ::after so they sit above the glow, and the box is a little wider and
 * much taller than the beam so they are not clipped at the edges. */
.beam::after {
  content: '';
  position: absolute;
  inset: -34% -5%;
  pointer-events: none;
  background-repeat: no-repeat;
  background-image:
    radial-gradient(circle, rgba(255, 208, 140, 0.56) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.54) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.37) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.37) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 176, 84, 0.32) 0%, rgba(255, 176, 84, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.32) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.48) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 176, 84, 0.48) 0%, rgba(255, 176, 84, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.56) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 176, 84, 0.37) 0%, rgba(255, 176, 84, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.48) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.55) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.36) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.59) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.51) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 176, 84, 0.46) 0%, rgba(255, 176, 84, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.36) 0%, rgba(255, 208, 140, 0) 68%),
    radial-gradient(circle, rgba(255, 208, 140, 0.24) 0%, rgba(255, 208, 140, 0) 68%);
  background-size: 3.3px 3.3px, 1.9px 1.9px, 1.8px 1.8px, 2.8px 2.8px, 2.8px 2.8px, 2.6px 2.6px, 1.7px 1.7px, 2.5px 2.5px, 2.4px 2.4px, 3.1px 3.1px, 2.1px 2.1px, 3.1px 3.1px, 1.6px 1.6px, 3.4px 3.4px, 3.0px 3.0px, 3.3px 3.3px, 1.8px 1.8px, 2.6px 2.6px;
  background-position: 5.8% 51.8%, 11.2% 52.3%, 17.0% 57.0%, 19.6% 57.0%, 29.0% 58.4%, 30.2% 41.5%, 35.6% 46.1%, 41.9% 54.1%, 47.4% 51.7%, 55.0% 57.1%, 56.9% 45.9%, 64.2% 48.1%, 70.3% 57.5%, 75.3% 49.3%, 76.4% 53.2%, 81.7% 45.5%, 87.0% 42.5%, 95.4% 39.1%;
}

/* The tagline holds one line everywhere it fits. On a phone it cannot, and a
   line running off the side is worse than a break. */
@media screen and (max-width: 640px) {
  .page-head--centred .page-head__lede { white-space: normal; max-width: 26ch; }
}

/* ---- a different crop per page ---------------------------- *
 * One asset, four framings. His field is a diagonal sweep with the bright band
 * running from the lower right up through the middle, so every position below
 * keeps that band in frame — the variation is which part of it you get, not
 * whether you get any. Landing a page in a dark corner is what made this look
 * wrong the first time. */

/* HOME PUTS ITS LIGHT LOW, and that is what makes the hero's two ends match.
 *
 * The default light sits 28% down the page. With a full-screen hero, the top of
 * the film dissolves onto the field's dark upper band and the bottom dissolves
 * onto the brightest part of it, so the same mask produced two different
 * colours at the two ends. Elijah, 2026-08-31: the bottom should be the colour
 * the top is.
 *
 * Moving the light below the fold fixes it where it belongs — in the field,
 * which both ends read from — rather than with a darkening patched over one of
 * them. That patch existed, was moved five times, and is gone. */
.page-home    { --bg-x: 38%; --bg-y: 74%; }

/* THE FIELD BEHIND A FULL-SCREEN HERO, evened out.
 *
 * The film dissolves at both ends onto whatever the field is doing there, and
 * on this page those two places are very different. The field's own dark band
 * runs out at 33% of the page, and the hero's bottom edge lands at 36% — right
 * where the band has just finished. So the top of the film faded onto deep navy
 * and the bottom faded onto the brightest tone on the page, from an identical
 * mask. Elijah, 2026-08-31: the bottom should be the colour the top is.
 *
 * This is an extra layer over the field, transparent where the field is already
 * dark and deepening toward where it is not, so the whole hero sits on one
 * tone. It is in the FIELD, which both ends of the film read from, rather than
 * a patch laid under one end — that patch existed, moved five times, and is
 * gone.
 *
 * The percentages are of the page, so they follow the hero as the page grows.
 * If the home page ever gets much longer they will need looking at again, which
 * is why the hero's own share of the page is written down here: 36%. */
.page-home {
  background-image:
    linear-gradient(180deg,
      rgba(3, 13, 51, 0)    0%,
      rgba(3, 13, 51, .06)  8%,
      rgba(3, 13, 51, .18) 15%,
      rgba(3, 13, 51, .32) 22%,
      rgba(3, 13, 51, .43) 28%,
      rgba(3, 13, 51, .48) 33%,
      rgba(3, 13, 51, .48) 38%,
      rgba(3, 13, 51, .41) 43%,
      rgba(3, 13, 51, .30) 48%,
      rgba(3, 13, 51, .18) 54%,
      rgba(3, 13, 51, .08) 60%,
      rgba(3, 13, 51, .02) 66%,
      rgba(3, 13, 51, 0)   72%),
    var(--bg-image);
}

.page-series  { --bg-x: 30%; --bg-y: 24%; }
.page-ask     { --bg-x: 66%; --bg-y: 32%; }
.page-seed    { --bg-x: 40%; --bg-y: 58%; }
.page-tax     { --bg-x: 72%; --bg-y: 66%; }


/* ---- driftwall.css ---- */
/* ============================================================
   The episode wall — Our Shows, between the title and the slate.

   A drifting 3D wall of key art. Adapted from React Bits' DriftWall
   (reactbits.dev/components/drift-wall, MIT); the geometry is theirs, the
   colours and the sizes are the Homeframe palette in tokens.css. Behaviour is
   in src/client/driftwall.js.

   THIS IS NAVIGATION, NOT DECORATION. Every tile is a link to a row further
   down the page, so it has to stay legible enough to recognise a show in and
   obvious enough to look clickable. That is why the tiles carry their title on
   hover and why the active one lifts as far as it does — the original's version
   of "active" is a brightness change, which on a wall of sixteen similar
   thumbnails is not enough to tell you what you are about to click.
   ============================================================ */

.drift-wall {
  position: relative;

  /* FULL BLEED, out of main's reading column.
   *
   * main is capped at 1700px with a gutter either side, which is right for a
   * page of writing and wrong for this: a masked wall of artwork sitting in the
   * middle of a 3840-wide screen with blue either side reads as an island, which
   * is the exact note Elijah has given before about 1920-drawn layouts on his
   * monitor. The wall is the width of the window and the slate below keeps the
   * column.
   *
   * 50% is half of main's content box and main is centred, so this lands the
   * wall's centre on the window's centre at any width. 100vw counts the
   * scrollbar, which puts the wall a few pixels wide — body has overflow-x:
   * hidden, so those pixels are clipped rather than scrollable. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);

  /* Tall enough for three rows of artwork to read, short enough that the slate
     underneath is still on the same screen on a laptop. The wall is a way into
     the list; a wall that fills the window hides the thing it leads to. */
  height: clamp(340px, 52vh, 720px);
  margin-block: clamp(4px, 1vw, 18px) clamp(26px, 3.4vw, 70px);
  overflow: hidden;
  perspective: var(--dw-perspective, 1200px);
  perspective-origin: 50% 50%;

  /* HOW MANY COLUMNS ARE SHOWING, and therefore how wide a column is.
   *
   * The plane has to be about as wide as the window or the mask has nothing to
   * fade at the edges and the wall reads as a strip. Column width is therefore
   * derived from the window and the count, not chosen — 1.04 is the overfill the
   * mask eats, on top of the 1.18 the plane is already scaled by.
   *
   * The build renders nine columns; the rules further down hide the ones off the
   * end on narrower screens, and the slices they hold overlap so nothing is lost
   * when they go. */
  --dw-cols: 5;
  --dw-col-w: calc(100vw / var(--dw-cols) * 1.04);

  /* One tile, and the air around it. The tile box INCLUDES the gap, which is
     what lets the script treat one child as one unit of scroll. */
  --dw-gap: clamp(10px, .8vw, 22px);
  --dw-tile-w: calc(var(--dw-col-w) - var(--dw-gap));
  --dw-tile-h: calc(var(--dw-tile-w) * 9 / 16);
  --dw-radius: clamp(8px, .7vw, 18px);
  --dw-perspective: 1200px;
  /* How far a lit tile comes toward you. */
  --dw-lift: 62px;

  /* ---- the four edges ------------------------------------- *
   * The wall has no edges of its own: it dissolves into the field on all four
   * sides, so it reads as something the page is looking through rather than a
   * rectangle sitting on it.
   *
   * TWO LINEAR RAMPS INTERSECTED, not the original's ellipse. An ellipse is the
   * right answer inside the bounded card the component was written for; here the
   * wall runs the full width of the window, and an ellipse sized to fade the
   * sides was still 57% opaque where it met the bottom — a row of artwork sliced
   * flat across the middle of the page. Two ramps put the fade exactly where it
   * is asked for and nowhere else.
   *
   * The bottom fades faster than the top: below it is the slate, and the wall
   * has to be gone by the time the first show row starts. */
  --dw-fade-x: 15%;
  --dw-fade-top: 24%;
  --dw-fade-bottom: 20%;

  -webkit-mask-image:
    linear-gradient(to right, transparent 0%, #000 var(--dw-fade-x), #000 calc(100% - var(--dw-fade-x)), transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 var(--dw-fade-top), #000 calc(100% - var(--dw-fade-bottom)), transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0%, #000 var(--dw-fade-x), #000 calc(100% - var(--dw-fade-x)), transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 var(--dw-fade-top), #000 calc(100% - var(--dw-fade-bottom)), transparent 100%);
  mask-composite: intersect;
}

/* The plane is centred on the wall and then rotated away from the viewer, so it
   is WIDER than the box it sits in and its far edges are eaten by the mask
   above. That overfill is the effect: a wall with visible ends is a strip. */
.drift-wall__plane {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  flex-direction: row;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
  will-change: transform;
  /* The starting transform, so the wall is already in perspective on the first
     paint and with the script blocked. driftwall.js overwrites it every frame
     with the same shape plus the pointer's tilt. */
  transform:
    translate(-50%, -50%) scale(1.18)
    rotateX(14deg) rotateY(-12deg) rotateZ(0deg)
    translateZ(-120px);
}

.drift-wall__col {
  position: relative;
  width: var(--dw-col-w);
  transform-style: preserve-3d;
}

/* ---- how many columns each screen gets ------------------- *
 * Four on a phone, five by default, seven on a wide monitor, all nine on
 * Elijah's. Hiding from the END is what makes this safe: the first FOUR columns
 * between them already hold every show on the slate (see episodeWall in
 * src/pages/index.mjs), so the extra five make the wall wider rather than more
 * complete. Hiding from anywhere else, or dealing the shows round-robin, would
 * quietly drop shows off a phone. */
.drift-wall__col:nth-child(n + 6) { display: none; }

@media screen and (min-width: 1700px) {
  .drift-wall { --dw-cols: 7; }
  .drift-wall__col:nth-child(n + 6) { display: block; }
  .drift-wall__col:nth-child(n + 8) { display: none; }
}

@media screen and (min-width: 2400px) {
  .drift-wall { --dw-cols: 9; }
  .drift-wall__col:nth-child(n + 8) { display: block; }
}

.drift-wall__track {
  display: flex;
  flex-direction: column;
  will-change: transform;
  transform-style: preserve-3d;
}

.drift-wall__tile {
  position: relative;
  display: block;
  width: 100%;
  height: calc(var(--dw-tile-h) + var(--dw-gap));
  flex: 0 0 auto;
  outline: none;
  text-decoration: none;
  transform-style: preserve-3d;
  /* A pointer, because it goes somewhere. */
  cursor: pointer;
}

/* The picture and its furniture. Inset by half the gap on every side, so the
   gap between two tiles is one whole --dw-gap and the scroll unit stays exactly
   one tile plus one gap. */
.drift-wall__inner {
  position: absolute;
  inset: calc(var(--dw-gap) / 2);
  display: block;
  border-radius: var(--dw-radius);
  overflow: hidden;
  background: rgba(4, 14, 52, .9);
  border: 1px solid rgba(255, 255, 255, .12);
  /* FULL BRIGHTNESS, ALWAYS. Elijah, 2026-09-09. The ported component dims every
     tile to about half and lays a navy wash over it, lifting both only on the one
     you are pointing at — which reads as the artwork being switched off until you
     touch it. The wall is the shows; they are not a texture. What separates the
     tile under the cursor now is that it comes toward you, takes a gold edge and
     says its name, which is plenty and does not cost the other forty their
     colour. */
  transform: translateZ(0);
  /* The tile is what the pointer hits, not this. elementFromPoint has to land
     on the element carrying data-drift-tile. */
  pointer-events: none;
  transition:
    transform .42s cubic-bezier(.22, 1, .36, 1),
    opacity .42s cubic-bezier(.22, 1, .36, 1),
    border-color .42s cubic-bezier(.22, 1, .36, 1),
    box-shadow .42s cubic-bezier(.22, 1, .36, 1);
}

.drift-wall__tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

/* ---- the title on the tile you are pointing at ----------- *
 * Not in the original, and it is here because this wall is a menu. Sixteen
 * pieces of key art at 200px are not sixteen recognisable shows, and a link
 * that will move the page ought to say where to. */
.drift-wall__label {
  position: absolute;
  inset: auto 0 0 0;
  padding: 1.6em .7em .55em;
  font-family: var(--font-display);
  font-size: clamp(9px, .72vw, 15px);
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.15;
  color: #ffffff;
  text-align: center;
  background: linear-gradient(to top,
    rgba(2, 9, 34, .95) 0%,
    rgba(2, 9, 34, .78) 42%,
    rgba(2, 9, 34, 0) 100%);
  opacity: 0;
  transform: translateY(.5em);
  transition:
    opacity .3s cubic-bezier(.22, 1, .36, 1),
    transform .3s cubic-bezier(.22, 1, .36, 1);
  pointer-events: none;
}

/* ---- the lit tile ---------------------------------------- */

.drift-wall__tile.is-active .drift-wall__inner {
  transform: translateZ(var(--dw-lift));
  border-color: rgba(255, 194, 30, .75);
  box-shadow:
    0 24px 60px -18px rgba(0, 6, 32, .9),
    0 0 26px -6px rgba(255, 160, 40, .5);
}

.drift-wall__tile.is-active .drift-wall__label { opacity: 1; transform: none; }

/* ---- the row it takes you to ----------------------------- *
 * A gold edge that draws attention to the row for two seconds and then leaves.
 * The page has just moved on its own and every row looks like every other row;
 * without this you arrive and have to work out which one you asked for.
 *
 * On the artwork rather than the whole row: a border around a full-width row is
 * a box drawn on the design, and the artwork is the thing that was just clicked
 * in the wall above. */
.show.is-target .show__art {
  animation: show-target 2.4s var(--ease) 1;
}

@keyframes show-target {
  0%   { border-color: rgba(255, 194, 30, .95); box-shadow: 0 0 0 3px rgba(255, 194, 30, .5), 0 20px 44px -24px rgba(0, 6, 32, .9); }
  70%  { border-color: rgba(255, 194, 30, .95); box-shadow: 0 0 0 3px rgba(255, 194, 30, .5), 0 20px 44px -24px rgba(0, 6, 32, .9); }
  100% { border-color: rgba(255, 255, 255, .18); box-shadow: 0 0 0 0 rgba(255, 194, 30, 0), 0 20px 44px -24px rgba(0, 6, 32, .9); }
}

/* The banner is fixed, so a row jumped to from the wall would otherwise arrive
   underneath it. A little more than the banner's own height, so the title is not
   pressed against its bottom edge. */
.page-series .show {
  scroll-margin-top: calc(var(--nav-h) + clamp(16px, 2vw, 44px));
}

/* ---- smaller screens ------------------------------------- *
 * The columns stay at five. Distributing sixteen shows round-robin means
 * dropping a column drops shows out of the wall entirely, and the plane already
 * overfills its box at every width — the tiles just get smaller. */
@media screen and (max-width: 760px) {
  .drift-wall {
    height: clamp(250px, 40vh, 380px);
    /* Four columns, not five. A 390px screen divided five ways is a 76px tile,
       which is a coloured rectangle rather than a piece of key art you could
       recognise a show by. Four still covers the whole slate: the slices are
       four shows each overlapping by one, so columns 1-4 hold shows 1-13 and the
       last three arrive as the columns wrap. */
    --dw-cols: 4;
    --dw-gap: 7px;
    /* Less of the width given to the side fade than a desktop gets: 15% of a
       390px screen is 58px eaten off each end, which is most of a column. 7% was
       the other way wrong — 27px is not enough gradient to soften a tile cut in
       half by the edge, and the right-hand column ended in a hard vertical line.
       11% is about 43px, which reads as the wall carrying on past the screen. */
    --dw-fade-x: 11%;
    --dw-lift: 34px;
    --dw-perspective: 700px;
    margin-block: 2px clamp(20px, 5vw, 40px);
  }
  .drift-wall__col:nth-child(n + 5) { display: none; }
  /* No hover on a touch screen, so the label would only ever appear on the tile
     that happens to be under a finger mid-tap. The slate below carries every
     title in full. */
  .drift-wall__label { display: none; }
}

/* ---- reduced motion -------------------------------------- *
 * The wall stops dead: no drift, no pointer tilt. It is still a wall of artwork
 * and every tile is still a link, which is the part that matters. */
@media (prefers-reduced-motion: reduce) {
  .drift-wall__plane,
  .drift-wall__track { will-change: auto; }
  .drift-wall__inner,
  .drift-wall__label { transition-duration: .01ms; }
  .show.is-target .show__art { animation: none; border-color: rgba(255, 194, 30, .95); }
}


/* ---- page-money.css ---- */
/* ============================================================
   The money pages — Our Ask, Initial 500K Seed, GA Tax Credit.

   These three carry Elijah's budget sheets. They are documents to READ, so the
   prose keeps the narrow measure the rest of the site uses, but a five-column
   table squeezed into a reading column is unreadable and scrolls sideways with
   half the screen empty. So the column widens for the tables and the prose is
   pulled back in on its own.
   ============================================================ */

.page-ask main,
.page-tax main {
  max-width: min(1700px, 100%);
}

/* The head is CENTRED on all three money pages, but the lede is NOT the glowing
   one-line tagline that treatment carries on Our Shows. That styling exists for
   "Framing the world for the next generation" — five words, one line, a wordmark
   in prose. Here the lede is a full sentence explaining a budget, and at 34px
   with a glow behind it, it competes with the title instead of introducing it.
   Centred, ordinary weight, and a measure it can actually break on. */
/* More air around the beam here than on Our Shows. There the beam separates a
   title from a five-word tagline and wants to sit close; here it separates a
   title from a sentence, and the same tight spacing reads as crowding. The
   number is the beam's negative margin, which cancels the transparent padding
   in its 5:1 box — less negative is more gap. */
.page-ask .page-head .beam,
.page-tax .page-head .beam {
  margin-block: clamp(-56px, -3.5vw, -10px);
}

/* Two ledes in a row are one thought split across two lines, so they sit closer
   to each other than either sits to the title. .page-head is a grid and its gap
   is uniform, so the second one pulls itself back up rather than the gap being
   changed for everything. Elijah, 2026-08-31. */
.page-head--centred .page-head__lede + .page-head__lede {
  /* Half the grid gap, not most of it. At 13px the two boxes overlapped by
     seven pixels, which the leading was hiding rather than the spacing being
     right. */
  margin-top: calc(-1 * clamp(5px, .55vw, 9px));
}

.page-ask .page-head--centred .page-head__lede,
.page-tax .page-head--centred .page-head__lede {
  font-size: clamp(15px, 1.3vw, 30px);
  font-weight: 600;
  color: var(--ink-dim);
  white-space: normal;
  max-width: 62ch;
  margin-inline: auto;
  text-shadow: none;
}

/* Prose keeps its measure. A line of text 1700px wide is a line nobody finishes.
   margin-inline: auto matters as much as the max-width: without it the capped
   block sits against the left gutter while the full-width tables below are
   centred, and the page reads as two columns that do not line up. */
.page-ask .page-head,
.page-tax .page-head,
.page-ask .section__body,
.page-tax .section__body {
  max-width: 74ch;
  margin-inline: auto;
}

/* ---- headline figures ------------------------------------ *
 * Three numbers across the top. The claim; the tables underneath are the
 * evidence. Text, never an exported graphic, so they can be selected, copied,
 * read aloud and searched. */

.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(12px, 1.2vw, 28px);
  margin-block: clamp(22px, 3vw, 52px);
}

.figure {
  padding: clamp(18px, 1.6vw, 38px) clamp(18px, 1.6vw, 34px);
  border-radius: clamp(10px, .7vw, 20px);
  border: 1px solid rgba(255, 255, 255, .14);
  background: linear-gradient(165deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .02));
  box-shadow: 0 20px 44px -28px rgba(0, 6, 32, .95);
}

.figure__value {
  font-family: var(--font-display);
  font-size: clamp(28px, 2.6vw, 68px);
  font-weight: 900;
  line-height: 1.02;
  letter-spacing: -.01em;
  color: #ffffff;
  margin: 0 0 .35em;
  /* Figures are read down a column as much as across a row, so they line up on
     their digits rather than drifting with each number's own widths. */
  font-variant-numeric: tabular-nums;
}

/* The first figure on a page is the one the page exists to say. */
.figure:first-child .figure__value { color: var(--accent); }

.figure__label {
  margin: 0;
  font-size: clamp(11px, .72vw, 16px);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* ---- the tables ------------------------------------------ */

/* Money columns line up on their digits. Without this the thousands separators
   wander and a column of dollar figures cannot be compared down the page. */
.ask-table td,
.ask-table th { font-variant-numeric: tabular-nums; }

/* Each table is its own block with air around it, rather than three tables
   running together into one wall of numbers. */
.page-ask .section,
.page-tax .section {
  padding-block: clamp(10px, 1.2vw, 26px);
  border-top: 0;
}


/* ---- the footer ------------------------------------------ *
 * One link. Deliberately quiet, deliberately not invisible: "conspicuous" is a
 * legal requirement in California, not a design preference, so this is real type
 * at a size a reasonable person notices rather than a grey whisper.
 */
.site-foot {
  padding: clamp(28px, 3vw, 60px) var(--gutter) clamp(34px, 3.4vw, 70px);
  text-align: center;
  border-top: 1px solid rgba(255, 255, 255, .10);
  margin-top: clamp(30px, 4vw, 90px);
}
.site-foot__link {
  font-family: var(--font-ui);
  font-size: clamp(13px, 1vw, 17px);
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .58);
  text-decoration: none;
  transition: color .3s var(--ease);
}
.site-foot__link:hover { color: #ffffff; }

/* ---- the privacy notice ---------------------------------- *
 * A document, set to be read: one column at a comfortable measure, generous
 * leading, and headings that separate without shouting. It uses the site's own
 * faces so it does not read as a page from somewhere else.
 */
.legal {
  max-width: 760px;
  margin-inline: auto;
  padding-block: clamp(24px, 3vw, 64px) clamp(40px, 5vw, 100px);
  color: rgba(255, 255, 255, .84);
  font-family: var(--font-ui);
  font-size: clamp(15px, 1.05vw, 19px);
  line-height: 1.75;
}
.legal__head { margin-bottom: clamp(28px, 3vw, 56px); }
.legal__title {
  font-family: var(--font-display);
  font-size: clamp(34px, 4vw, 84px);
  font-weight: 900;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: #ffffff;
  margin: 0 0 clamp(10px, 1vw, 18px);
  line-height: 1;
}
.legal__lede {
  margin: 0 0 clamp(10px, 1vw, 16px);
  font-size: clamp(16px, 1.25vw, 23px);
  color: rgba(255, 255, 255, .92);
}
.legal__meta {
  margin: 0;
  font-size: clamp(12px, .9vw, 15px);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
}
.legal__section { margin-bottom: clamp(26px, 2.6vw, 52px); }
.legal__heading {
  font-family: var(--font-display);
  font-size: clamp(17px, 1.5vw, 27px);
  font-weight: 900;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #ffffff;
  margin: 0 0 clamp(8px, .8vw, 14px);
}
.legal p { margin: 0 0 .9em; }
.legal p:last-child { margin-bottom: 0; }
.legal strong { color: #ffffff; font-weight: 700; }
.legal a { color: var(--gold, #ffb45a); }
.legal code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .92em;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .08);
}
.legal__list {
  margin: 0 0 .9em;
  padding-left: 1.15em;
}
.legal__list li { margin-bottom: .45em; }
.legal__list li::marker { color: rgba(255, 255, 255, .38); }



/* ---- mobile.css ---- */
/* ==========================================================================
   MOBILE
   ==========================================================================
   Everything below is inside one media query and nothing above 760px is touched
   by any of it. That is deliberate: the desktop design is finished and signed
   off, and the safest way to leave it alone is to make every mobile rule an
   override that literally cannot apply to a wide screen.

   It is a different LAYOUT, not a squeezed copy. A phone is a tall narrow window
   held in one hand, so the ordering is: one thing at a time, full width, big
   enough to read at arm's length, and nothing that needs a sideways scroll
   except the tables that genuinely cannot be anything else.

   What was actually wrong before this existed, measured at 390x844:
     - the fixed nav was 215px tall, a quarter of the screen, permanently.
     - the hero was 439px of which the nav covered half.
     - the hero line sat at 462px, BELOW the hero, on top of Our Shows.
     - Our Shows was pulled up 12svh into a hero that was no longer tall enough
       to have anywhere to pull from, so the two collided.
     - the tax credit page scrolled sideways: 435px of content in a 390px window.
   ========================================================================== */

@media screen and (max-width: 760px) {

  /* ---- the edges of the world ------------------------------------------ *
   * What a phone paints around the page. Pull past the top or the bottom and
   * iOS rubber-bands, showing whatever colour is on the canvas, and the canvas
   * was taking the body's brand blue - a bright band above the header and below
   * the footer that announced where the page stopped. Elijah, 2026-09-01: "the
   * solid blue background is too light so its very obvious".
   *
   * Setting a background on <html> is what moves the canvas off the body's, and
   * the deepest navy in the field is the colour that reads as depth rather than
   * as the page running out. overscroll-behavior stops the bounce outright
   * where the browser honours it; the colour is what covers the case where it
   * does not. The matching theme-color meta in layout.mjs does the same for the
   * browser's own bars. */
  html {
    background-color: #040e34;
    overscroll-behavior-y: none;
  }

  /* ---- the frame ------------------------------------------------------- */
  :root {
    /* The hero still runs past the fold so its bottom fade has somewhere to
       live, but by a tenth of a screen rather than a fifth: on a phone the fade
       is a much larger share of the picture. */
    --hero-over: 10svh;
    --rail-lift: 10svh;
    /* Tighter margins. 130px of gutter on a 390px screen is most of the screen. */
    --gutter: 18px;
    /* How much room the fixed bar takes. main already clears it with a
       margin-top of exactly this, and the deck page centres its first slide
       against it, so this one number moves everything that has to sit under the
       bar. 10px of padding, a 44px logo, 10px of padding. */
    /* Two rows: the logo on its own, the tab strip under it. Measured against
       the built page rather than added up on paper, because the strip's bottom
       padding is holding the beam and the logo's height comes from its aspect
       ratio. Everything that clears the bar reads this one number. */
    --nav-h: 112px;
  }

  /* ---- the bar --------------------------------------------------------- *
   * Elijah's phone design, 2026-09-01: the logo centred on its own line, and a
   * strip of tabs under it that slides sideways.
   *
   * What it replaces was one row, logo left and tabs beside it, and the tabs
   * simply did not fit: "you have to scroll to see everything ... I cant even go
   * to the other tabs to see them." A phone is 390 points wide and six uppercase
   * tabs beside a logo are not, so the choice is between wrapping them onto
   * three lines (which is the 215px bar this replaced), shrinking them past
   * reading size, or giving them a line of their own. This is the third.
   *
   * The logo centred also fixes the thing a left-aligned mark does on a narrow
   * screen: with nothing in the top right it read as a page that had lost half
   * its header. */
  .nav__inner {
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding-block: 7px 0;
    /* The strip runs edge to edge, so a tab cut in half at the right-hand side
       is the thing that says there is more. The page gutter goes back on the
       strip's own padding, below, so the FIRST tab still lines up with
       everything else on the page. */
    padding-inline: 0;
  }
  /* Big enough that HOMEFRAME and the ribbon under it are both legible. Smaller
     and the wordmark is a smudge, which is not a logo, it is a stain. */
  .nav__logo img { width: 72px; }

  .nav__links {
    /* An EXPLICIT 100%, not align-self: stretch. A flex item's automatic minimum
       size is its min-content width, and for a nowrap row of six tabs that is
       425px on a 390px screen - so the strip stretched itself 35px past the edge
       of the phone and took the right-hand end of its own fade mask with it.
       Overflow makes that minimum zero in principle; in practice a percentage is
       the number, not an argument about one. */
    width: 100%;
    min-width: 0;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 20px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* THE BOTTOM PADDING IS THE BEAM, and it is not spare space.
       overflow-x: auto forces overflow-y to compute to auto as well - there is
       no such thing as a box that scrolls sideways and spills vertically - so
       the orange streak, which hangs BELOW the label it belongs to, was being
       clipped away to nothing the moment this became a scroller. Elijah,
       2026-09-01: "the orange beam under tabs is gone on phone". It was never a
       style that got lost; it was a style with nowhere left to be drawn. This is
       the room it needs. */
    padding: 0 26px 15px var(--gutter);
    /* Fades at both ends. The left one sits inside the gutter, so it softens the
       edge without touching the first label. */
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 13px, #000 calc(100% - 30px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 13px, #000 calc(100% - 30px), transparent 100%);
  }
  .nav__links::-webkit-scrollbar { display: none; }
  .nav__links a {
    flex: 0 0 auto;
    font-size: 12.5px;
    letter-spacing: .1em;
    /* 36px of thumb for a 12px label. */
    padding-block: 12px;
  }
  /* A shorter streak. The desktop one stands 3.4 label-heights tall, which under
     a 12.5px label is forty-odd pixels of glow hanging off a bar barely twice
     that, and it would need the strip's padding to be twice as deep to hold it.
     The shape and every colour are the desktop beam's; only the height is a
     phone's. */
  .nav__links a[aria-current='page']::before {
    top: calc(100% + 1px);
    height: calc(var(--nav-size) * 2.1);
  }
  /* The band behind the bar. Sized off the bar, so a taller bar needs a smaller
     multiple to finish in the same place. */
  .nav::before { height: 148%; }

  /* ---- the hero -------------------------------------------------------- *
   * Full screen, as on desktop. A phone is the one place a full-bleed film
   * actually gets the whole frame, and halving it was throwing that away. */
  .hero { min-height: 420px; }

  /* The three line numbers, on .hero so the sentence under the box reads the
     same ones. See the block of the same name in page-home.css. */
  .hero {
    /* Dead centre of the window. It sat at 64svh, two thirds of the way down,
       which on a tall narrow screen reads as a caption that has slid off the
       picture rather than a title on it. Elijah, 2026-09-01. */
    --line-top: 50svh;
    /* Sized so the LONGER of the two rows fits on one line. "FOR HOMESCHOOL
       FAMILIES" is about 17.5em wide in this face at this tracking, and the box
       has 358px of room on a 390px screen, so anything above 20px wraps it to a
       third line and the box clips. Measured, not guessed. */
    --line-fs: clamp(16px, 5vw, 24px);
    /* The TITLE's band: a 1.3593 row and a 1.0 row at 1.2 line-height is 2.83 of
       the base size, and 3.25 is that plus the air the design has above and below
       the type. --line-box-h adds the second sentence's two rows on top of this,
       so this number stays what it always was. */
    --line-title-h: calc(var(--line-fs) * 3.25);
    --line-sub-rows: 2;
  }

  .hero-line {
    /* An EXPLICIT width, not fit-content. The desktop box hugs one nowrap line,
       but the moment the text is allowed to wrap, fit-content on a flex parent
       collapses towards min-content and the box shrinks to the width of the
       longest single word - which is exactly what happened: a box two thirds of
       a phone wide with the first and last words clipped off inside it. */
    width: calc(100vw - 12px);
    max-width: none;
    padding-inline: 10px;
    border-width: 1.5px;
    /* Half the glow. The desktop shadow is six layers wide and on a small box it
       reads as a smudge rather than a light. */
    box-shadow:
      0 0 0 1px rgba(214, 238, 255, .55),
      0 0 8px rgba(120, 196, 255, .8),
      0 0 22px rgba(70, 166, 255, .55),
      0 0 50px rgba(46, 136, 250, .3);
  }
  /* Two lines, split where the reveal already pauses, so it reads as a title
     rather than as a sentence that ran out of room. */
  .hero-line__row { display: block; }
  .hero-line__text { white-space: normal; line-height: 1.2; }

  /* THE TWO ROWS ARE SET TO THE SAME LENGTH, not the same size.
   *
   * "A cinematic studio" is five characters shorter than "for homeschool
   * families", so at one size it came out visibly narrower and the block read
   * as a sentence that had wrapped rather than as a title in two lines. Elijah,
   * 2026-09-01: make the first line long enough that it runs as far as the
   * second.
   *
   * 1.3593 is the ratio of their widths, measured in the browser at 100px in
   * Outfit 900 uppercase at this tracking (1481.67 against 1090.00) rather than
   * estimated from the character count, which is 28% out because the letters
   * are not the same width. If either line is ever reworded, measure it again:
   * this number belongs to these two strings and nothing else.
   */
  .hero-line__row:first-child { font-size: calc(var(--line-fs) * 1.3593); }

  /* Height comes from --line-box-h, set on .hero above. */

  /* ---- the strips ------------------------------------------------------ *
   * One card, nearly full width, with a sliver of the next one so it is
   * obviously a strip rather than a single picture. The numbers are chosen
   * together: at 80vw wide and a 0.875 shift the neighbour clears the open card
   * by 8px and shows about 30px of itself. */
  .home-block--split,
  .rail--shows,
  .carousel {
    --slide-w: min(80vw, calc(58vh * 16 / 9));
    --slide-h: calc(var(--slide-w) * 9 / 16);
    --peek-shift: calc(var(--slide-w) * 0.875);
    --peek-scale: .7;
  }
  .home-block { padding-block: 0 clamp(28px, 7vw, 44px); }
  .home-block + .home-block { padding-top: clamp(28px, 7vw, 44px); }
  .home-block__title { font-size: clamp(30px, 9vw, 44px); margin-bottom: 16px; }
  .home-block__caption { font-size: clamp(15px, 4.2vw, 20px); }

  /* The ends of the strip dissolve rather than being cut off, the same as the
     split layout does on desktop. */
  .rail--shows .rail__stage,
  .carousel__stage {
    -webkit-mask-image: linear-gradient(90deg,
      rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .5) 4%, rgba(0, 0, 0, 1) 9%,
      rgba(0, 0, 0, 1) 91%, rgba(0, 0, 0, .5) 96%, rgba(0, 0, 0, 0) 100%);
    mask-image: linear-gradient(90deg,
      rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .5) 4%, rgba(0, 0, 0, 1) 9%,
      rgba(0, 0, 0, 1) 91%, rgba(0, 0, 0, .5) 96%, rgba(0, 0, 0, 0) 100%);
  }

  /* ---- the money-page cards -------------------------------------------- *
   * The card is about 175px tall on a phone, so the desktop floor would fill it
   * to the edges. Its own scale here, measured against that height. */
  .carousel__slide.page-card { padding: 14px 16px; gap: 2px; }
  .page-card__title { font-size: clamp(19px, 5.4vw, 26px); }
  .page-card__text { font-size: clamp(11px, 3.1vw, 14px); line-height: 1.35; max-width: 30ch; }
  .page-card .beam { width: min(82%, 300px); margin-block: -8px; }

  /* ---- page heads ------------------------------------------------------ */
  .page-head { padding-block: clamp(22px, 6vw, 40px) clamp(18px, 5vw, 32px); gap: 12px; }
  .page-head__title { font-size: clamp(34px, 10.5vw, 52px); line-height: 1; }
  .page-head--centred .page-head__lede { font-size: clamp(15px, 4.3vw, 20px); }
  .page-ask .page-head--centred .page-head__lede,
  .page-tax .page-head--centred .page-head__lede { font-size: clamp(14px, 4vw, 18px); }

  /* ---- figures --------------------------------------------------------- *
   * Two up rather than one: three big numbers stacked is a screen and a half of
   * scrolling before any of them can be compared, and comparing them is the
   * entire reason they are next to each other. */
  .figures { grid-template-columns: 1fr 1fr; gap: 10px; }
  .figure { padding: 14px 12px; }
  .figure__value { font-size: clamp(22px, 7vw, 34px); }
  .figure__label { font-size: 10.5px; letter-spacing: .12em; }
  /* A figure on its own on the last row spans both columns rather than sitting
     lopsided in the left one. */
  .figures > .figure:last-child:nth-child(odd) { grid-column: 1 / -1; }

  /* ---- the money tables, rebuilt for a phone ---------------------------- *
   *
   * THEY ARE NOT TABLES HERE. Each row becomes a card: the row's name as a
   * heading, then one figure per line with its column name beside it.
   *
   * The previous answer was to let each table scroll sideways inside its own
   * frame with the first column pinned. It worked, in the sense that nothing
   * overflowed, and Elijah's verdict on using it was "horrible" - which is the
   * right verdict. Reading a six-column sheet through a 390-point window means
   * dragging it back and forth for every comparison, and a table you have to
   * drag is not a table you read, it is a table you give up on. This is a deck
   * an investor opens on a phone, so the sheet has to fit the phone.
   *
   * Nothing about the markup stops being a table: it is still <table>, still one
   * <tr> per row, still carrying its roles, and above 760px none of this
   * applies. The column names come from data-label, which the two renderers in
   * pages/index.mjs put on every body cell.
   */
  .breakdown,
  .breakdown > *,
  .section > *,
  .table-wrap { min-width: 0; }
  .table-wrap { margin-block: clamp(14px, 4vw, 26px); }

  /* The frame and the scroller both go. There is nothing left to scroll, and a
     border drawn around a stack of bordered cards is a box inside a box. */
  .table-scroll {
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: none;
  }
  /* It told the reader to scroll sideways. There is no sideways any more. */
  .table-wrap__hint { display: none; }

  .ask-table {
    display: block;
    width: 100%;
    min-width: 0;
    font-size: 15px;
  }
  /* The header row's job is done by data-label, one copy per figure. */
  .ask-table thead { display: none; }
  .ask-table tbody,
  .ask-table tfoot { display: block; }
  .ask-table__caption {
    display: block;
    padding: 0 2px 14px;
    font-size: 17px;
  }

  /* ONE BLOCK PER SECTION, NOT ONE PER ROW.
   *
   * The rows under a heading are the same kind of thing as each other - four
   * shows under Curricular Focus, eleven line items under Post-Production - and
   * a gap between every one of them broke a list into a column of loose cards
   * with nothing saying where the section started or stopped. Elijah,
   * 2026-09-01: merge them, and keep the gaps for the major sections.
   *
   * So the rows in a run share one outline: no gap between them, a hairline
   * where they meet, and rounded corners only at the two ends of the run. The
   * air is spent on the group headings instead, which is where it does work. */
  .ask-table tbody tr,
  .ask-table tfoot tr {
    display: block;
    margin: 0;
    padding: 0 14px 4px;
    border: 1px solid var(--rule);
    /* The row above already drew this edge. */
    border-top-width: 0;
    border-radius: 0;
    background: rgba(4, 14, 52, .55);
  }
  /* The first row of a run closes the top: after a group heading, or at the very
     start of the table. */
  .ask-table tbody tr:first-child,
  .ask-table tbody tr.is-group + tr {
    border-top-width: 1px;
    border-radius: 14px 14px 0 0;
  }
  /* The last of a run closes the bottom: before the next group heading, or at
     the end. :has() is how a row asks about the one after it; without it the
     corners stay square, which is a blunt edge and not a broken layout. */
  .ask-table tbody tr:last-child,
  .ask-table tbody tr:has(+ tr.is-group) { border-radius: 0 0 14px 14px; }
  .ask-table tbody tr:first-child:last-child { border-radius: 14px; }

  /* The total is not part of the run. It gets its air back. */
  .ask-table tfoot tr {
    margin-top: 14px;
    border-top-width: 1px;
    border-radius: 14px;
  }
  /* A hover tint on a touch screen is a stuck highlight after a tap. */
  .ask-table tbody tr:hover { background: rgba(4, 14, 52, .55); }

  .ask-table td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 18px;
    padding: 10px 0;
    text-align: right;
    white-space: normal;
    border-bottom: 1px solid var(--rule-soft);
  }
  .ask-table td::before {
    content: attr(data-label);
    flex: 0 1 auto;
    text-align: left;
    font-family: var(--font);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .18em;
    line-height: 1.35;
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  .ask-table tr > td:last-child { border-bottom: 0; }

  /* The first column is the row's NAME, so it is the card's heading rather than
     a label with a value beside it. */
  .ask-table tbody td:first-child,
  .ask-table tfoot td:first-child {
    display: block;
    padding: 13px 0 11px;
    text-align: left;
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 900;
    letter-spacing: .04em;
    line-height: 1.15;
    text-transform: uppercase;
    color: #ffffff;
    border-bottom: 1px solid var(--rule);
  }
  .ask-table tbody td:first-child::before,
  .ask-table tfoot td:first-child::before { content: none; }

  /* An empty cell holds a column open in a grid. On a card it would be a label
     with a blank beside it, which reads as missing rather than as not
     applicable. */
  .ask-table td:empty { display: none; }

  /* A group heading divides the cards. It is not one of them. */
  .ask-table tr.is-group {
    display: block;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
  }
  .ask-table .ask-table__group {
    display: block;
    padding: clamp(16px, 4.5vw, 26px) 2px 10px;
    font-size: 13px;
    white-space: normal;
    background: none;
    border-bottom: 0;
  }
  .ask-table tbody tr.is-group:first-child .ask-table__group { padding-top: 2px; }

  /* The total, and the one row a page is built to reach. Brighter ground and a
     stronger edge, because on a stack of cards "last" is not the same signal
     that a ruled-off final row is in a table. */
  .ask-table tfoot tr.is-total {
    border-color: rgba(255, 255, 255, .3);
    background: rgba(8, 26, 78, .85);
  }
  .ask-table .is-total td {
    border-top: 0;
    border-bottom: 1px solid var(--rule);
    font-weight: 700;
  }
  .ask-table .is-total td:last-child { border-bottom: 0; }
  .ask-table .is-headline td { padding-block: 12px; font-size: 16px; }
  .ask-table .is-headline td:first-child { font-size: 18px; }

  /* The breakdown's line-item column had a 150px floor and a right pad to keep
     it from crushing the columns beside it. As a heading it has the card. */
  .breakdown .ask-table th:first-child,
  .breakdown .ask-table td:first-child { min-width: 0; padding-right: 0; }
  .breakdown .ask-table th,
  .breakdown .ask-table td { padding-left: 0; padding-right: 0; }
  .breakdown { gap: 26px; }

  /* The summary block is the one table whose first column is a FIGURE and not a
     name, so it gets pairs all the way down rather than a heading it cannot
     honestly carry. */
  .breakdown__summary { padding: 4px 14px; }
  .ask-table--summary tbody tr {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
  }
  .ask-table--summary tbody td:first-child {
    display: flex;
    padding: 10px 0;
    font-family: var(--font);
    font-size: 15px;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
    text-align: right;
    border-bottom: 1px solid var(--rule-soft);
  }
  .ask-table--summary tbody td:first-child::before { content: attr(data-label); }
  .ask-table--summary tbody td.is-primary { font-weight: 900; }

  /* ---- the shows list -------------------------------------------------- */
  /* The slate is 78vw wide, which is right on a monitor and throws away 86px of
     a 390px screen. Full width here, which is also what puts "WEEKLY SHOWS,
     TUTORIALS & SHORTS" back on one line - it was wrapping because the block
     holding it was narrower than the page it sat on, not because the heading was
     too big. Elijah, 2026-09-01. */
  .slate { width: 100%; }
  .slate__heading { white-space: nowrap; }
  .show { gap: 12px; }
  .show__art { max-width: none; }

  /* A page's own PDF button does not appear on a phone at all. A phone is where
     the link gets forwarded, not where a five-page money document gets saved and
     opened, so on a small screen it is a permanent orange square over the
     numbers it is offering. Elijah, 2026-09-01. */
  .download-fab--page { display: none; }

  /* ---- the deck button ------------------------------------------------- *
   * A rounded square in the bottom right corner with a share mark on it, to
   * Elijah's design, 2026-09-01. On a computer it stays the labelled bar it has
   * always been.
   *
   * The icon is a SHARE mark and not a download arrow because on a phone the
   * button no longer downloads: deck.js hands the PDF to the iOS share sheet,
   * where Messages, Mail, AirDrop and Save to Files are all one tap away. That
   * is what the button is actually for - this is a deck you send to someone -
   * and the old behaviour dropped the reader into a full-screen PDF viewer with
   * the site gone behind it.
   *
   * A square rather than the full-width bar it was: the bar sat across the
   * bottom of every slide for the whole scroll, and on a phone that is a third
   * of the width of the deck it is covering. */
  .download-fab {
    left: auto;
    right: max(14px, env(safe-area-inset-right, 0px));
    bottom: max(14px, env(safe-area-inset-bottom, 0px));
    width: 58px;
    height: 58px;
    padding: 0;
    justify-content: center;
    border-radius: 18px;
  }
  .download-fab__icon--down { display: none; }
  .download-fab__icon--share { display: block; width: 25px; height: 25px; }
  /* The words are still the button's NAME, they are just not drawn. display:none
     would take the name away with the pixels and leave a screen reader
     announcing an unlabelled link. */
  .download-fab__label {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* Waiting on the file. A 34MB deck does not arrive instantly on a phone, and a
     button that looks untouched while it works reads as a button that did not
     register the tap. */
  .download-fab.is-busy .download-fab__icon--share { opacity: .3; }
  .download-fab.is-busy .download-fab__status {
    display: block;
    position: absolute;
    inset: 15px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, .3);
    border-top-color: #ffffff;
    animation: fab-spin .8s linear infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .download-fab.is-busy .download-fab__status { animation-duration: 2.4s; }
  }

  /* ---- the notice ------------------------------------------------------ */
  .legal { padding-block: 8px clamp(30px, 8vw, 60px); font-size: 15.5px; line-height: 1.7; }
  .legal__title { font-size: clamp(34px, 10.5vw, 52px); }
  .legal__lede { font-size: 16.5px; }
  .legal__section { margin-bottom: 24px; }
  .legal__heading { font-size: 16px; }

  .site-foot { padding-block: 26px 30px; margin-top: 28px; }

  /* The sticky first column lived here. It was the right fix for a table you
     drag sideways, and there is no longer a table you drag sideways: the block
     above turns every row into a card. Nothing is pinned because nothing moves. */
  /* ---- the deck --------------------------------------------------------- *
   * A stack you scroll, not a projector you step through. A slide is 16:9, so
   * on a 390px screen it is about 200px tall, and stepping one of those at a
   * time through an 844px window is slower than reading and less useful.
   * deck.js wires none of its input handling at this width to match.
   *
   * But it OPENS centred on slide one, exactly as the desktop deck does. It was
   * opening with slide one at the top of the screen, which put slide two in the
   * middle: "the 1st slide starts at the top, so you are centered on 2".
   * Elijah, 2026-09-01. The padding below is the desktop formula - the bar,
   * plus half of whatever is left over - restated in svh, because vh on a phone
   * means the window with the address bar hidden, and the address bar is not
   * hidden when the page opens. That difference is about 60px, which is a third
   * of a slide. */
  .page-pitch .deck {
    --deck-air: clamp(14px, 3svh, 34px);
    --deck-w: min(
      calc(100vw - 2 * var(--gutter)),
      calc((100svh - var(--nav-h) - var(--deck-air)) * 16 / 9)
    );
    /* Centred on the SCREEN, not on the space under the bar. Those are the same
       thing on a monitor, where the bar is a twentieth of the window; on a phone
       the two-row bar is an eighth of it, and centring below the bar left slide
       one sitting 56px low. The bar is translucent and 112px tall, the slide is
       200px, so a screen-centred slide starts at 322 and never goes near it -
       and the max() is there for a window too short for that to be true. */
    padding-top: max(
      calc(var(--nav-h) + 14px),
      calc((100svh - var(--deck-slide-h)) / 2)
    );
    padding-bottom: clamp(20px, 6vw, 44px);
    gap: clamp(14px, 4vw, 28px);
  }

  /* Snapping to a 200px slide in an 844px window fights the reader rather than
     helping them. */
  html.is-deck { scroll-snap-type: none; }
  .page-pitch .slide,
  .page-pitch .deck__end { scroll-snap-align: none; scroll-snap-stop: normal; }

  /* Back to top comes off. On a computer it is the last thing after thirteen
     slides and a sensible offer; on a phone the deck free-scrolls, so a flick is
     already faster than finding a button, and it was one more orange slab in a
     corner already holding the share button. Elijah, 2026-09-01. */
  .deck__top { display: none; }
  .deck__end { padding-block: clamp(20px, 5vw, 40px) clamp(8px, 2vw, 20px); }

  /* Thirteen tick marks down the left edge is a control for a deck that moves
     one slide at a time. This one does not any more, and the column would sit
     on top of the slides. The counter stays, small, out of the way. */
  html.is-deck-enhanced .deck__ticks { display: none; }
  html.is-deck-enhanced .deck__hud {
    top: calc(var(--nav-h) + 8px);
    left: auto;
    right: 14px;
    bottom: auto;
    transform: none;
    padding: 4px 10px;
    border-radius: 99px;
    background: rgba(4, 14, 52, .72);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }
  .deck__count { font-size: 12px; margin: 0; }
}

/* A phone held sideways is a 390px-tall window, and a full-screen hero plus a
   two-row bar leaves nothing. Sideways the bar goes back to one row - there is
   width to spare and height to save - and the hero gives up its overhang.

   760px, NOT 900. At 900 this block reached up to 899px wide, which is above the
   phone breakpoint, so a desktop browser window dragged short and wide - 850 by
   480, say - took --nav-h: 48px while still rendering the full desktop banner,
   and every page's content slid up underneath a bar nearly seventy pixels
   taller than the space reserved for it. An independent review caught it. The
   whole point of this round is that nothing above 760 moves, and a media query
   that can fire above 760 cannot make that promise however narrow the case
   looks. */
@media screen and (max-width: 760px) and (orientation: landscape) and (max-height: 500px) {
  /* 60, not the 48 this started at. Capping the query at 760 put it INSIDE the
     portrait block's reach rather than beside it, so the tab row keeps the
     portrait padding that makes it a 36px thumb target and the bar comes out
     ten pixels taller than it did when this number was measured. Measured
     again against the built page: 58.5, and 60 is the honest reservation. */
  :root { --nav-h: 60px; }
  .nav__inner {
    flex-direction: row;
    align-items: center;
    gap: 14px;
    padding-block: 7px 0;
    padding-inline: var(--gutter) 0;
  }
  .nav__logo img { width: 52px; }
  /* The beam still needs its room, so the bottom padding is the portrait number.
     Only the left one goes, because the logo is beside the strip again. */
  .nav__links { width: auto; padding-left: 0; padding-bottom: 15px; }
  .hero { height: 92svh; --line-top: 50svh; }
}


/* ---- print.css ---- */
/* ==========================================================================
   PRINT, AND THE PAGE PDFs, WHICH ARE NOT THE SAME THING
   ==========================================================================
   Our Ask, GA Tax Credit and Initial Seed each ship a PDF, and the PDF is this
   page printed by headless Chrome rather than a second document maintained by
   hand. That is the whole reason this block is worth having: a separately built
   PDF drifts from the page the first time a number changes, and nobody notices
   until an investor is holding both.

   Regenerate with `node tools/build-page-pdfs.mjs`.

   TWO OUTPUTS, ONE STYLESHEET, AND THE SPLIT IS THE POINT.

   Elijah, 2026-09-01: the downloaded PDF should look like the site - the blue
   field and white type - rather than the white sheet it was. He is right about
   the PDF and it would be wrong for paper: a deep blue page sent to a real
   printer is either forty pages of ink or, with background graphics off,
   white text on white, which is a blank document.

   So the two are separated. `html.is-pdf` is set ONLY by the local server inside
   tools/build-page-pdfs.mjs, which rewrites the <html> tag as it serves the page
   to headless Chrome. Nothing in the shipped site can ever match it: Ctrl+P in a
   browser still produces the readable white sheet, and the file behind the
   download button looks like the page it came from.

     - rules with no prefix apply to both. Geometry, furniture, page breaks.
     - `html:not(.is-pdf)` is paper. It is the whole of the old block.
     - `html.is-pdf` is the download. It mostly UNDOES rather than adds, because
       the screen styling is already what it wants.
   ========================================================================== */
@media print {
  /* MARGIN ZERO, AND THE MARGIN PUT BACK ON `main` INSTEAD.
     A page margin is outside the page area, so nothing paints there - which on
     the white sheet is invisible and on the blue one is a white frame around
     every page. The measurements are the same either way; they have just moved
     somewhere that can be coloured. */
  @page { size: A4; margin: 0; }

  /* ---- both ------------------------------------------------------------ *
   * Furniture. None of it means anything in either output, and the banner is
   * fixed, so left in it would print over the first table on every page. */
  .nav, .site-foot, .hero, .download-fab, .deck__hud, .deck__top,
  .home-block, .table-wrap__hint, .skip { display: none !important; }

  main { margin: 0 !important; padding: 14mm 12mm !important; max-width: none !important; }
  .page-head { padding: 0 0 10mm !important; }

  /* The table is 720px at its narrowest on screen and an A4 text column is about
     660. Without this every table runs off the right edge of every page, in both
     outputs. */
  .table-scroll { overflow: visible !important; }
  .ask-table { width: 100% !important; min-width: 0 !important; font-size: 8.5pt !important; }
  .ask-table thead { display: table-header-group; }
  .ask-table thead th { position: static !important; }
  .ask-table th, .ask-table td { padding: 1.6mm 2.5mm !important; }

  /* Nothing that reads as one OBJECT may be cut in half. A long table is not one
     object: `thead` above repeats on every page it runs onto, so letting it flow
     fills the page instead of pushing a twenty-row slate onto a sheet of its own
     and leaving most of the one before it blank. Individual rows still never
     split. */
  .figure, .breakdown__summary, .ask-table tbody tr { break-inside: avoid; }
  .band-heading { break-after: avoid; }

  .figures { grid-template-columns: repeat(3, 1fr) !important; gap: 4mm !important; }
  .breakdown { grid-template-columns: 1fr !important; gap: 8mm !important; }

  /* ---- paper: white sheet, dark type ----------------------------------- *
   * Everything below is the original print block, now scoped so it cannot
   * reach the generated PDFs. */
  html:not(.is-pdf), html:not(.is-pdf) body {
    background: #ffffff !important; color: #101828 !important;
  }
  html:not(.is-pdf) body::before,
  html:not(.is-pdf) body::after { display: none !important; }
  html:not(.is-pdf) .beam { display: none !important; }
  html:not(.is-pdf) .page-home, html:not(.is-pdf) .page-pitch,
  html:not(.is-pdf) .page-series, html:not(.is-pdf) .page-ask,
  html:not(.is-pdf) .page-tax { background: #ffffff !important; }

  html:not(.is-pdf) .page-head__title {
    -webkit-text-fill-color: #0b2a7a !important;
    color: #0b2a7a !important;
    background: none !important;
    font-size: 26pt !important;
  }
  html:not(.is-pdf) .page-head__eyebrow,
  html:not(.is-pdf) .page-head__lede { color: #344054 !important; }
  html:not(.is-pdf) .page-head__lede { font-size: 11pt !important; }

  html:not(.is-pdf) .figure {
    border: 1px solid #d0d5dd !important; background: #f8fafc !important;
    padding: 4mm !important;
  }
  html:not(.is-pdf) .figure__value { font-size: 15pt !important; color: #0b2a7a !important; }
  html:not(.is-pdf) .figure__label { color: #475467 !important; }

  html:not(.is-pdf) .band-heading { color: #0b2a7a !important; font-size: 13pt !important; }
  html:not(.is-pdf) .band-heading::before,
  html:not(.is-pdf) .band-heading::after { background: #d0d5dd !important; }

  html:not(.is-pdf) .table-scroll { border: 0 !important; background: none !important; }
  html:not(.is-pdf) .table-wrap { margin: 0 0 6mm !important; }
  html:not(.is-pdf) .ask-table thead th {
    background: #eef2f6 !important; color: #344054 !important;
    border-bottom: 1px solid #98a2b3 !important;
  }
  html:not(.is-pdf) .ask-table__caption {
    color: #0b2a7a !important; font-size: 11pt !important; padding: 0 0 2mm !important;
  }
  html:not(.is-pdf) .ask-table th,
  html:not(.is-pdf) .ask-table td {
    border-bottom: 1px solid #e4e7ec !important;
    color: #101828 !important;
  }
  html:not(.is-pdf) .ask-table td.is-primary { color: #101828 !important; }
  html:not(.is-pdf) .ask-table .ask-table__group {
    background: #f2f4f7 !important; color: #0b2a7a !important;
    border-bottom: 1px solid #98a2b3 !important;
  }
  html:not(.is-pdf) .ask-table .is-total td { border-top: 1px solid #98a2b3 !important; }
  html:not(.is-pdf) .ask-table .is-headline td:not(:first-child):not(:empty) {
    color: #8a5a00 !important;
  }

  html:not(.is-pdf) .breakdown__title,
  html:not(.is-pdf) .breakdown__credit-value {
    color: #0b2a7a !important;
    -webkit-text-fill-color: #0b2a7a !important;
    background: none !important;
  }
  html:not(.is-pdf) .breakdown__subtitle,
  html:not(.is-pdf) .breakdown__note,
  html:not(.is-pdf) .breakdown__credit-label { color: #475467 !important; }
  html:not(.is-pdf) .breakdown__summary {
    border: 1px solid #d0d5dd !important; background: #f8fafc !important;
  }

  html:not(.is-pdf) .section { border-top: 1px solid #e4e7ec !important; }
  html:not(.is-pdf) .section__title { color: #0b2a7a !important; }
  html:not(.is-pdf) .section__body { color: #344054 !important; }

  /* ---- the download: the site, on paper-sized pages -------------------- *
   * Almost nothing to add. The screen styling already is the blue field and the
   * white type, so this mostly has to stop the browser throwing them away.
   *
   * THE GRADIENT GOES ON <html>, NOT ON body::before. The field on screen is a
   * `position: fixed` layer, and a fixed element in paged media paints on the
   * FIRST PAGE ONLY - so a three-page PDF would have come out one blue page
   * followed by two white ones with white text on them, which is a blank
   * document with a cover. The root element's background is propagated to the
   * page box instead, which is painted on every page by definition.
   *
   * print-color-adjust is what stops the browser helpfully removing all of it to
   * save ink. It inherits, so declaring it here covers every descendant. */
  html.is-pdf {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    color-adjust: exact;
    color: #ffffff;
    /* ATTACHMENT FIXED, AND IT IS DOING REAL WORK, NOT DECORATION.
       
       The root's background box is the whole DOCUMENT, so by default the
       gradient is sized to the full multi-page height and then TILED - and the
       second tile begins partway down the last page, which came out as a hard
       horizontal seam with the gradient's dark end above it and its bright start
       below. In paged media `fixed` makes the page box the positioning area
       instead, so each page gets one complete gradient, top to bottom, and there
       is nothing to tile. no-repeat is belt and braces.
       
       The colour underneath is the gradient's own last stop, so any region a
       browser declines to paint is still on-brand rather than white. */
    background-color: var(--deep-bottom) !important;
    background-image:
      linear-gradient(176deg,
        var(--deep-top) 0%,
        var(--blue) 32%,
        #0b3099 56%,
        #071c5e 80%,
        var(--deep-bottom) 100%) !important;
    background-attachment: fixed !important;
    background-repeat: no-repeat !important;
    background-size: cover !important;
  }
  /* EVERY CONTAINER TRANSPARENT, AS LONGHANDS, so exactly one gradient paints.
     
     The body carries its own field on these pages - `.page-ask` sets
     background-image, -position, -size and -blend as separate longhands - and
     that field is sized to the BODY BOX, which ends where the content ends. On a
     two-page PDF that produced a visible seam partway down the last page: the
     body's gradient finishing on its dark end, and the page box's gradient
     starting again bright underneath it. Resetting the longhands explicitly
     leaves the page box as the only thing painting, on every page, edge to
     edge. */
  html.is-pdf body,
  html.is-pdf main,
  html.is-pdf .page-ask,
  html.is-pdf .page-tax {
    background-color: transparent !important;
    background-image: none !important;
    background-blend-mode: normal !important;
  }
  /* The two fixed field layers are dropped for the reason above: on paper they
     are a first-page-only artefact sitting on top of the real background. */
  html.is-pdf body::before, html.is-pdf body::after { display: none !important; }

  /* A little tighter than the screen, because a 3840px monitor and an A4 column
     are not the same measure. Everything else keeps its own size. */
  /* SOLID WHITE, NOT THE SCREEN'S GRADIENT FILL. The title is painted with
     `background-clip: text` on screen; through Chrome's print path the clip is
     not always applied and the gradient comes out as a pale rectangle sitting
     behind the letters. */
  html.is-pdf .page-head__title {
    font-size: 30pt !important;
    background: none !important;
    color: #ffffff !important;
    -webkit-text-fill-color: #ffffff !important;
  }
  html.is-pdf .page-head__lede { font-size: 11pt !important; }
  html.is-pdf .figure__value { font-size: 16pt !important; }
  html.is-pdf .figure { padding: 4mm !important; }
  html.is-pdf .band-heading { font-size: 14pt !important; }
  html.is-pdf .ask-table__caption { font-size: 11pt !important; padding: 0 0 2mm !important; }
  html.is-pdf .table-wrap { margin: 0 0 6mm !important; }
  html.is-pdf .beam { margin-block: -30px !important; }
}
