/* Jabberwocky Studios — site styles.
   Palette anchored to the dragon mark (navy + gold).
   Mist Blue page gradient borrowed from Jabberblocky's daylight env. */

:root {
  /* Surface */
  --bg-primary: #e6eef7;
  --page-gradient: radial-gradient(circle at 50% 0%, #f0f5fc 0%, #e6eef7 58%, #cfdcec 100%);
  --rule: rgba(0, 50, 98, 0.10);          /* navy-tinted hairline */

  /* Brand */
  --navy: #003262;                         /* logo outline */
  --navy-soft: rgba(0, 50, 98, 0.32);
  --gold: #fdb515;                         /* logo body */
  --gold-soft: rgba(253, 181, 21, 0.35);

  /* Text */
  --fg: #14213b;                           /* navy-tinted ink */
  --muted: #4a5468;

  --maxw: 42rem;
}

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  min-height: 100%;
  background: var(--page-gradient) var(--bg-primary) fixed;
}

body {
  margin: 0;
  background: transparent;
  color: var(--fg);
  font: 500 0.9375rem/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif;
  /* Containing block for #lattice. min-height keeps the decorative layer
     covering at least the viewport on short pages; on long pages body grows
     to its content, so the lattice tracks content height exactly. */
  position: relative;
  min-height: 100dvh;
}

/* Visible keyboard focus for every interactive element. Previously only the
   audio-thanks footnote had a custom ring; everything else fell back to the UA
   default, which is faint on the light gradient. :focus-visible (not :focus)
   means mouse clicks stay ring-free — only keyboard/AT focus shows it. The
   navy 2px outline matches #processing-note:focus-visible below. */
:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

/* Skip link — the first focusable element on every page. Hidden above the
   viewport until focused, then slides into the top-left so keyboard/screen-
   reader users can jump past the header straight to <main id="main">. */
.skip-link {
  position: fixed;
  left: 0.5rem;
  top: -3.5rem;
  z-index: 10;
  padding: 0.5rem 0.9rem;
  background: var(--navy);
  color: #fff;
  text-decoration: none;
  font-weight: 600;
  border-radius: 0 0 6px 6px;
  transition: top 150ms ease;
}
.skip-link:focus {
  top: 0;
}
/* The skip link's target <main id="main"> carries tabindex="-1" so activating the
   link moves keyboard focus into the content (not just scrolls) — the next Tab then
   continues from inside <main>, not back at the header. Suppress the focus ring on
   the region itself: a 2px outline around the whole page body would be noise, and
   the content coming into view is the affordance. (Same rationale as
   #processing-note:focus below.) */
main[tabindex="-1"]:focus {
  outline: none;
}

/* Self-playing lattice — a preview of Jabberblocky's mechanic.
   Stacks N 9x9 boards vertically to cover the full page length. Each
   board lights cells in jb1's "bauhaus" multi-palette (navy+yellow being
   the dragon-mark colors). Animated by assets/lattice.js. */
#lattice {
  position: absolute;
  /* Pinned to all four edges of body (its containing block) so the layer is
     exactly body-height — never taller. overflow: hidden clips any boards the
     JS over-generates, so this decorative, out-of-flow layer can't extend the
     page's scroll height and leave an empty band below the footer.
     #lattice must stay a DIRECT child of body — a wrapper element between
     them would become the containing block and break this clamp. */
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-content: flex-start;
  gap: clamp(1.5rem, 4dvh, 3rem) clamp(1rem, 3vw, 2rem);
  padding: clamp(2rem, 5dvh, 4rem) clamp(0.5rem, 2vw, 1.5rem);
  /* Layer opacity matches jb1's home-ambient (.40). */
  opacity: 0.4;
}

/* Board geometry matches jb1's home-ambient-lattice verbatim:
   9 * 28px cells + 8 * 6px gaps = 300px square (256px / 24px / 5px at <360px).
   KEEP IN SYNC WITH ~/Coding/jb1/css/home.css .home-ambient-lattice. */
.lattice-board {
  width: 300px;
  height: 300px;
  display: grid;
  grid-template-columns: repeat(9, 28px);
  grid-template-rows: repeat(9, 28px);
  gap: 6px;
  /* Soft 3-stop radial mask matches jb1's home lattice fade per board. */
  -webkit-mask-image: radial-gradient(ellipse at 50% 40%, #000 30%, rgba(0,0,0,0.55) 62%, transparent 96%);
          mask-image: radial-gradient(ellipse at 50% 40%, #000 30%, rgba(0,0,0,0.55) 62%, transparent 96%);
}

@media (max-width: 359px) {
  .lattice-board {
    width: 256px;
    height: 256px;
    grid-template-columns: repeat(9, 24px);
    grid-template-rows: repeat(9, 24px);
    gap: 5px;
  }
}

/* Empty-cell base — matches jb1's light-family rule (charcoal-tinted at low
   alpha). KEEP IN SYNC WITH ~/Coding/jb1/css/home.css
   [data-family="light"] .home-ambient-lattice .lattice-cell. */
.lattice-cell {
  border-radius: 3px;
  box-sizing: border-box;
  background-color: rgba(29, 36, 51, 0.05);
  border: 1px solid rgba(29, 36, 51, 0.10);
}

/* Alive + clearing cells share their painted state — per-cell --ambient-color
   set by JS at build time; fallback is transparent so a missing var fails to
   invisible. Only the animation (and the resting opacity) differs between the
   two states; the clear keyframe keeps the cell's color and brightens it
   momentarily, matching jb1's home-lattice-clear. No color swap. */
.lattice-cell.alive,
.lattice-cell.clearing {
  background-color: var(--ambient-color, transparent);
  border-color: transparent;
  box-shadow: 0 0 5px var(--ambient-glow, rgba(0, 0, 0, 0));
}

.lattice-cell.alive {
  /* Resting opacity — also the reduced-motion fallback (when animation: none
     applies, this value paints the cell). KEEP IN SYNC with the keyframe
     `to` value below. */
  opacity: 0.35;
  animation: lattice-appear 1200ms ease-out forwards;
}

.lattice-cell.clearing {
  animation: lattice-clear 1600ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

@keyframes lattice-appear {
  from { opacity: 0; }
  to   { opacity: 0.35; }
}

@keyframes lattice-clear {
  0%   { opacity: 0.35; filter: brightness(1); }
  35%  { opacity: 0.65; filter: brightness(1.25); }
  100% { opacity: 0;    filter: brightness(1); }
}

@media (prefers-reduced-motion: reduce) {
  .lattice-cell.alive,
  .lattice-cell.clearing { animation: none; }
}

/* Reading-heavy pages (privacy, terms, audio-thanks) carry no `lattice-live`
   class, so the lattice renders STILL and dimmer — quiet brand texture that
   never competes with body copy. The `animation: none` also kills the
   one-shot appear fade, so the field is truly static on load (not just free
   of ongoing motion); the lower opacity reduces contrast behind text.
   Landing pages opt back into full liveliness via <body class="lattice-live">.
   Intentionally NOT synced to jb1 — jb1 has no reading-page lattice variant. */
body:not(.lattice-live) #lattice { opacity: 0.25; }
body:not(.lattice-live) .lattice-cell.alive { animation: none; }

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(2rem, 5dvh, 3.5rem) clamp(1.25rem, 4vw, 2rem) clamp(3rem, 6dvh, 5rem);
  position: relative;
}

/* Header */
header.site {
  border-bottom: 1px solid var(--rule);
  margin-bottom: 2.25rem;
  padding-bottom: 1.25rem;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--navy);
}
.brand-mark {
  width: 56px;
  height: 56px;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 2px 4px rgba(0, 50, 98, 0.18));
  transition: filter 200ms ease;
}
.brand-word {
  /* margin:0 because on jblock/ the wordmark is an <h1> (page heading) and must
     not inherit the global h1 margins, which would offset it in the flex row.
     No-op where .brand-word is a <span> (studio/reading pages). */
  margin: 0;
  font-size: 1.625rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.1;
}
.brand:hover .brand-mark {
  filter: drop-shadow(0 3px 6px rgba(253, 181, 21, 0.45));
}

footer.site {
  border-top: 1px solid var(--rule);
  margin-top: 3rem;
  padding-top: 1rem;
  font-size: 0.8125rem;
  color: var(--muted);
}
footer.site a {
  color: var(--muted);
  text-decoration: none;
}
footer.site a:hover {
  color: var(--navy);
}

/* Headings */
h1 {
  font-size: 1.625rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 0.25rem 0 1.25rem;
  color: var(--navy);
}
h2 {
  font-size: 1.125rem;
  font-weight: 600;
  margin: 2.5rem 0 0.625rem;
  color: var(--navy);
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
/* Gold "piece" tile preceding each section heading — evokes a Jabberblocky tray piece. */
h2::before {
  content: '';
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 2px;
  background: var(--gold);
  box-shadow: 0 1px 2px rgba(0, 50, 98, 0.25);
  flex-shrink: 0;
}
h3 {
  font-size: 1rem;
  font-weight: 600;
  margin: 1.5rem 0 0.5rem;
  color: var(--navy);
}

p, ul, ol { margin: 0.875rem 0; }
ul, ol { padding-left: 1.25rem; }
li { margin: 0.3rem 0; }

.product-entry {
  margin: 0.875rem 0 1.25rem;
}

.store-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.625rem 0.75rem;
  margin: 0.75rem 0 1rem;
}
.store-badge {
  display: inline-flex;
  line-height: 0;
  text-decoration: none;
}
.store-badge img {
  display: block;
  width: auto;
  height: 40px;
}

/* Long unbreakable strings (privacy page's Google policies URL, audio-thanks'
   DOI) can overflow the content column at ~320px viewports without this. */
main {
  overflow-wrap: break-word;
}

/* Links in main flow — navy with soft underline; gold warmth on hover. */
main a {
  color: var(--navy);
  text-decoration: underline;
  text-decoration-color: var(--navy-soft);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: text-decoration-color 150ms ease, text-shadow 200ms ease;
}
main a:hover {
  text-decoration-color: var(--gold);
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: rgba(0, 50, 98, 0.07);
  padding: 0.1em 0.35em;
  border-radius: 3px;
  font-size: 0.92em;
}

hr {
  border: none;
  border-top: 1px solid var(--rule);
  margin: 2rem 0;
}

/* Footnote reference — the `*` after "sequenced" jumps to the processing note at
   the foot of the page. A bare asterisk is a ~5px hit target, so a little trailing
   padding widens it; no left padding, so the marker still hugs the word it marks.
   Don't add a negative margin to close the gap before the comma — it pulls the
   comma back over the padding, which then hit-tests to the paragraph and hands
   back the target the padding just bought (measured: 71% of the box).
   No underline (an underlined `*` reads as a typo); it's navy against body ink,
   and the asterisk convention carries the rest. */
.fn-ref {
  text-decoration: none;
  padding-right: 0.2em;
  font-size: 0.9em;
  vertical-align: super;
  line-height: 0;
}
.fn-ref:hover,
.fn-ref:focus-visible {
  text-decoration: underline;
  text-decoration-color: var(--gold);
}

/* Landing target for the footnote jump: keep it clear of the viewport edge, and
   tint briefly so the eye finds the line it was sent to. tabindex="-1" on the
   <p> means keyboard focus follows the jump, so the next Tab continues from the
   footnote rather than the top of the page. */
#processing-note {
  scroll-margin-block: 2rem;
}
#processing-note:target {
  background: var(--gold-soft);
  border-radius: 3px;
  /* box-shadow, NOT outline, for the halo. `#processing-note:focus` below has
     equal specificity and is declared later, so an `outline` here would lose the
     cascade to its reset — and lose it precisely when the reader clicks the
     asterisk, since that makes the note both :target and :focus. box-shadow is
     on a different property, so the two can't collide. */
  box-shadow: 0 0 0 0.4rem var(--gold-soft);
}
#processing-note:focus {
  outline: none;
}
#processing-note:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 4px;
}

/* Fine print — license caveats and similar small-type asides.
   Used directly by the hand-authored /jblock/audio-thanks (the footnote under the
   credits), and also emitted by jb1's acknowledgements generator
   (scripts/acknowledgements/lib.mjs renders <p class="fine">…). Since audio-thanks
   is now canon here rather than generated, this rule stands on its own — do NOT
   drop it if jb1 stops emitting the class. See "Generated pages" in CLAUDE.md. */
.fine {
  font-size: 0.8125rem;
  color: var(--muted);
}

/* Figures — UI screenshots (portrait phone captures + the square board crop).
   Centered and capped so a phone capture stays legible without dominating the
   reading column; the square crop shares the same cap. The selector is general
   `main figure`, so any future page that uses a figure inherits this. */
main figure {
  margin: 1.5rem auto;
  max-width: 380px;
}
/* The shared card treatment for page imagery — accessibility screenshots and the
   pet gallery. One rule so the two can't drift; the shadow is navy (--navy at 12%)
   and this is the only place it's spelled out. */
main figure img,
.pet-photo {
  border-radius: 10px;
  border: 1px solid var(--rule);
  box-shadow: 0 4px 14px rgba(0, 50, 98, 0.12);
}

main figure img {
  display: block;
  width: 100%;
  height: auto;
}
main figcaption {
  font-size: 0.8125rem;
  color: var(--muted);
  text-align: center;
  margin-top: 0.5rem;
}

/* Pet gallery — the "Other contributors" row on /jblock/audio-thanks. A list, not
   figures: each entry is a named contributor, so the caption carries the meaning and
   the img is alt="" to avoid reading each pet's name twice. auto-fit reflows the
   row by width: three across on desktop, two at typical phone widths (375px), one
   below ~320px. */
.pets {
  list-style: none;
  margin: 1.25rem 0 0.5rem;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1.5rem 1.25rem;
}

.pet {
  text-align: center;
}

/* Square box + object-fit: cover does the cropping, so the sources ship uncropped.
   The pets are all exported 208px wide, so `cover` fits the width exactly and only
   the vertical overflow is croppable — `object-position` can only move these images
   up/down, never left/right. Horizontal framing is therefore fixed at export time
   (see assets/pets/); re-crop with `magick -gravity` if a photo ever needs it.
   Border, radius and shadow come from the shared card rule above. */
.pet-photo {
  width: 104px;
  height: 104px;
  margin: 0 auto 0.625rem;
  overflow: hidden;
}

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

.pet-cap {
  font-size: 0.875rem;
  line-height: 1.45;
  margin: 0;
}

/* ---------------------------------------------------------------------------
   Jabberblocky landing (/jblock/) — the only page with a hero layout.

   Every selector here is scoped (.wrap.wide, .landing, .hero-media, .headline,
   .features, .beta, body.jblock-landing) so none of it reaches the
   five reading pages, which share this stylesheet and stay at the 42rem measure.
   Values come from the design handoff; they intentionally reuse the literals
   already spelled out above (media frame :376, tile shadow :239) rather than
   introducing a token layer the rest of the site doesn't have.
   --------------------------------------------------------------------------- */

/* The wide column is a modifier — .wrap itself stays 42rem everywhere else.
   .wrap's existing fluid padding already matches the spec's --pad-* values.

   overflow-x:clip contains the hero spotlight, which is deliberately wider than
   the video it sits behind (612px of glow on a 360px float) and would otherwise
   bleed ~54px past the right edge and add horizontal scroll. The prototype did
   this on its root container. Two constraints on the fix:
     - `clip`, NOT `hidden` — hidden makes this a scroll container, which breaks
       position:sticky inside it. clip clips without creating a scrollport.
     - on THIS element, not body — body's overflow propagates to the viewport,
       where the clip is dropped (measured: the page still scrolled to x=54).
   The clip lands at the padding edge, past the glow's last visible alpha, so
   nothing renders a seam. */
.wrap.wide {
  max-width: 1200px;
  overflow-x: clip;
}

/* Header/footer become flex rows only on this page; the brand lockup and the
   store badges (the primary download CTA) sit at opposite ends. */
header.site.landing {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}
.landing .store-badges {
  margin: 0;
}

footer.site.landing {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 30px;
  padding-top: 18px;
}
.footer-links {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
}

/* The demo video wears the same frame as page imagery (`main figure img` above
   is the source of truth for these values); a <video> needs its own rule since
   it isn't an <img>. background:#000 so the letterbox matches the footage
   before the poster paints. */
main figure video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--rule);
  box-shadow: 0 4px 14px rgba(0, 50, 98, 0.12);
  background: #000;
}

/* Two layers, deliberately not collapsed into one:
   - .hero-media floats (the pitch and feature chips wrap around it), and is the
     positioning context for the glow.
   - the figure inside it is a normal-flow block, so `position: sticky` lands in
     its well-defined case rather than the murky sticky-on-a-float corner. */
.hero-media {
  float: right;
  position: relative;
  width: clamp(300px, 32%, 360px);
  margin: 0 0 20px 56px;
}
/* Decorative spotlight behind the video. No z-index by design: the figure comes
   later in tree order and is positioned, so it paints on top without one.
   pointer-events:none because the circle overhangs the float and would
   otherwise eat clicks and text selection on the feature list beneath it. */
.hero-media .glow {
  position: absolute;
  left: 50%;
  translate: -50% 0;
  top: -40px;
  width: 720px;
  max-width: 170%;
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle at 50% 45%,
    rgba(255, 255, 255, 0.95) 0%,
    rgba(240, 245, 252, 0.6) 42%,
    rgba(240, 245, 252, 0) 70%);
}
/* NB: this sticky is inert by construction, and the design prototype's is too —
   .hero-media is a float whose only in-flow child is this figure (the glow is
   absolutely positioned), so the containing block is exactly the figure's own
   height and there is zero room to travel. Measured: travel room 0 at every
   width. It's kept because the handoff specifies it and it costs nothing; the
   design's "video stays in view while the reader scans features" intent would
   need the float dropped — i.e. the magazine wrap dropped — to actually work.
   Don't "fix" it by moving sticky onto the float itself: float + sticky is the
   under-defined corner this two-layer split exists to avoid. */
.hero-media figure {
  position: sticky;
  top: 32px;
  margin: 0;
  max-width: 360px;
}

/* Gold "piece" tile — same values as h2::before (:232), reproduced here rather
   than refactoring that shared rule, which every other page depends on. */
.tile {
  display: inline-block;
  border-radius: 2px;
  background: var(--gold);
  box-shadow: 0 1px 2px rgba(0, 50, 98, 0.25);
}

/* The headline is an <h2> for document outline (the brand wordmark is the <h1>),
   but it must not look like a section heading: global h2 is 1.125rem, display:flex,
   and injects a gold tile via h2::before. display:block is load-bearing — the
   three hard <br> line breaks would be shredded by a flex container. The ::before
   reset wins on specificity: (0,1,1) beats h2::before's (0,0,2). */
.headline {
  display: block;
  margin: 0;
  /* Individual properties, not the `font:` shorthand — the shorthand would force
     us to re-declare the whole family stack here, making it a second source of
     truth that can drift from body (:36). The family inherits. */
  font-size: clamp(2.6rem, 3.8vw, 3.4rem);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.025em;
  color: var(--navy);
}
.headline::before {
  content: none;
}

.pitch {
  margin: 1.5rem 0 0;
  font-size: 1.125rem;
  line-height: 1.6;
  max-width: 30rem;
}

/* Feature chips pack into as many columns as fit and reflow with width — no
   heading, no grouping, by design. font-size:0 on the <ul> collapses the source
   whitespace between the inline-flex <li>s, which would otherwise render as real
   spaces and cost a column; each <li> restores its own text size.

   DO NOT convert this <ul> to display:flex or display:grid, however much the
   font-size:0 trick looks like a legacy hack worth modernizing. Flex/grid makes
   .features a BFC root, and a BFC root cannot overlap a float — so instead of
   wrapping around .hero-media it gets narrowed to the column beside it and can
   never reflow underneath, which is the "around/UNDER" magazine wrap the whole
   layout exists for. Measured at a 900px viewport when this was tried:
     ul 836px -> 480px · 2 columns -> 1 · 4 chips -> 0 reclaiming full width
     below the video · list 196px taller.
   It looks identical at 1280px (the list barely passes the video there), which
   is why it reads as safe and has already been proposed once. The block-level
   <ul> + inline-flex <li> pairing is the mechanism, not an accident.
   `vertical-align: top` on the <li> is load-bearing for the same reason — it
   aligns chip tops across an inline row; it is not redundant. */
.features {
  list-style: none;
  padding-left: 0;
  margin: 28px 0 0;
  font-size: 0;
}
.features li {
  display: inline-flex;
  align-items: flex-start;
  vertical-align: top;
  width: 300px;
  max-width: 100%;
  gap: 11px;
  margin: 0;
  padding: 9px 24px 9px 0;
  /* Explicit: body inherits 0.9375rem, which renders visibly small here. */
  font-size: 1rem;
  line-height: 1.55;
}
.features .tile {
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 7px;
  flex-shrink: 0;
}
.features b {
  font-weight: 700;
  color: var(--navy);
}

/* clear:both ends the magazine wrap — the card always starts below the video. */
.beta {
  clear: both;
  margin-top: 30px;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 50, 98, 0.12);
  padding: 22px 26px;
}
.beta p {
  margin: 0;
  line-height: 1.6;
}
.beta b {
  color: var(--navy);
}

/* The calm lever. This page omits `lattice-live`, so :153 already renders the
   lattice static at 0.25; this dims it further (0.25 x the spec's 0.4) so the
   demo video is the only moving thing on the page. Equal specificity with :153
   — (1,1,1) both — so this rule wins by source order. Keep it after that one. */
body.jblock-landing #lattice {
  opacity: 0.1;
}

/* Below ~720px the float would squeeze the text into a sliver: un-float the
   video, center it, keep the 360px cap (never full-bleed), and drop sticky so it
   doesn't hover over the copy on a phone. The 300px chips collapse to one column
   on their own via width:300px + max-width:100%, so they need no query. */
@media (max-width: 720px) {
  .hero-media {
    /* position stays `relative` — do NOT set static here. It's the glow's
       containing block; without it the glow escapes to .wrap and renders as a
       page-wide white circle washing out the header and badges. */
    float: none;
    width: auto;
    margin: 0 0 1.5rem;
  }
  .hero-media figure {
    /* `relative`, not `static`: sticky is dropped (it's inert anyway, and would
       pin the video over the copy on a phone), but the figure must stay
       POSITIONED or the positioned .glow paints on top of it and washes out the
       video. Paint order here is by tree order, which only holds while both
       layers are positioned. */
    position: relative;
    /* MUST reset `top`. The base rule sets `top: 32px` for the sticky above,
       where it's only a stick threshold. Under `position: relative` that same
       declaration becomes a real 32px downward offset — and relative offsets
       don't reflow, so .hero-media keeps reserving the un-offset height and the
       video paints 32px past it, straight onto .headline (which sets margin:0).
       Measured before this reset: video bottom 697.5 vs container bottom 665.5,
       overlapping the headline by 8px. */
    top: auto;
    margin: 0 auto;
  }
}

/* Below ~640px the header/footer rows stack rather than wrap mid-row. Badges
   keep their 40px height (tap target) — only the arrangement changes. */
@media (max-width: 640px) {
  header.site.landing,
  footer.site.landing {
    flex-direction: column;
    align-items: flex-start;
  }
}
