/* ===========================================================================
   SchoolSpirit — Your Academic Companion

   Set entirely in SF. Nothing is fetched: no webfont file, no
   remote asset. The type carries hierarchy through weight, size, tracking
   and colour alone.

   Ground rules that the rest of this file depends on:
   · The film is the page's second beat and it is cut flush against the hero —
     nothing sits between the title and the footage. It runs full-bleed at
     every width and never leaves 16:9, because it composes across its own
     frame and a portrait crop would cut the type out of it.
   · One light source. VERA's ring in the hero is the only bright thing on
     the page; every other surface is indigo and quiet.
   · Media is never dimmed across the whole frame. Legibility comes from a
     gradient at the base of a tile, so the footage above it stays vivid.
   · `.hero` is an isolation root for the orb's `screen` blend. Nothing
     between `.hero` and `.hero__orb` may take a z-index.
   =========================================================================== */

/* ─────────────────────────────────────────────────────────────── 1 · tokens */
:root {
  /* SF, resident on the device. No fallback webfont, by design. */
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* ground: deeper than the brand indigo so the footage reads as light */
  --bg:       #0B0C26;
  --indigo:   #141455;      /* the recovered brand indigo */

  --ink:      #FFFFFF;
  --ink-2:    #C9D4F0;      /* body — 12.9:1 on --bg, 6.6:1 on the hero's brightest blue */
  --ink-3:    #9FAFE0;      /* meta — 8.8:1 on --bg */

  --light:    #DCE4F5;      /* the one light surface: VERA's light as a card */
  --navy:     #2B3A5C;      /* the wordmark's own navy — 8.8:1 on --light */
  --teal:     #5FD4C4;      /* the source design's accent on "Academic" */

  --line:     rgba(159, 175, 224, 0.18);

  /* 4pt scale, the steps this page actually uses */
  --s-4: 16px;  --s-5: 24px;  --s-6: 32px;  --s-7: 48px;

  --gutter: clamp(20px, 5vw, 56px);
  --shell:  1240px;
  --gap:    clamp(8px, 1vw, 14px);

  /* The viewport meta carries `viewport-fit=cover`, so on a notched phone the
     layout runs under the rounded corners and the home indicator unless the
     insets are folded back in. Every edge-pinned thing on the page reads these
     rather than the raw gutter. */
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --pad-l:  max(var(--gutter), var(--safe-l));
  --pad-r:  max(var(--gutter), var(--safe-r));

  --band:   clamp(72px, 11vw, 152px);   /* vertical rhythm between sections */

  --ease:   cubic-bezier(0.16, 1, 0.3, 1);

  /* semantic depth, small and named */
  --z-copy:   2;   /* type over media */
  --z-chrome: 4;   /* the film's own control */
  --z-head:  10;
  --z-skip:  20;
}

/* ─────────────────────────────────────────────────────────────── 2 · reset */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: clamp(84px, 8vw, 112px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: clamp(1.0625rem, 0.98rem + 0.35vw, 1.1875rem);
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: 0.005em;      /* light-on-dark loses perceived weight */
  font-kerning: normal;
  font-feature-settings: "kern" 1, "liga" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, p, figure { margin: 0; }
img, video, svg { display: block; max-width: 100%; }
img, video { height: auto; }

/* The hero ground, VERA's ring, the four app loops and the mirrored closing
   ground are ambient footage: they autoplay muted, loop, and stay in the
   layout. None of them is a player, so none of them takes the pointer — a tap
   cannot reach the element, and therefore cannot open it, and cannot make it
   speak. That is what stops the accidental fullscreen-with-sound.

   The film is the deliberate exception, and it is still not clicked directly:
   `.film__open` is a real button laid over its frame, and the video takes the
   pointer back only once it is actually full screen (see `:fullscreen`), so a
   click there can close it again. */
video { pointer-events: none; }
a { color: inherit; }
button { font: inherit; color: inherit; }

::selection { background: var(--teal); color: #06122A; }

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

.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: fixed; top: 10px; left: 10px;
  z-index: var(--z-skip);
  transform: translateY(calc(-100% - 20px));
  padding: 10px 16px;
  background: var(--light); color: var(--navy);
  font-size: 0.875rem; font-weight: 600; text-decoration: none;
  border-radius: 3px;
  transition: transform 180ms var(--ease);
}
.skip-link:focus-visible { transform: none; }

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-left: var(--pad-l);
  padding-right: var(--pad-r);
}

/* ────────────────────────────────────────────────── 3 · furniture and meta
   The small uppercase labels pinned far-left and far-right. They are page
   furniture, not section eyebrows: they frame a composition, they never
   introduce a heading. */
.furniture {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  margin: 0 0 var(--s-5);
  color: var(--ink-3);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  line-height: 1.2;
  text-transform: uppercase;
}
.furniture--foot { margin: var(--s-5) 0 0; }

/* ─────────────────────────────────────────────────────────── 4 · wordmark */
/* The mark is 42px tall at its smallest; the link around it is not. Height
   comes from the target, not from the artwork. */
.wordmark {
  display: flex;
  align-items: center;
  min-height: 44px;
  width: clamp(126px, 13vw, 168px);
  flex: none;
}
.wordmark img { width: 100%; height: auto; }

/* the real asset is navy on transparent; knock it to white for dark grounds */
.wordmark--knockout img { filter: brightness(0) invert(1); }
.wordmark--sm { width: clamp(112px, 11vw, 138px); }

/* ────────────────────────────────────────────────────────── 5 · masthead */
.masthead {
  position: fixed;
  inset: 0 0 auto;
  z-index: var(--z-head);
  padding-block: clamp(14px, 2vw, 22px);
  transition: background-color 320ms var(--ease), border-color 320ms var(--ease);
  border-bottom: 1px solid transparent;
}
.masthead::before {
  /* A gradient, not a panel — the footage keeps running underneath. It is
     strong enough on its own that the nav stays legible over the tiles even
     with JavaScript off, when the scrolled state never arrives. */
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to bottom,
              rgba(6, 7, 26, 0.82) 0%,
              rgba(6, 7, 26, 0.48) 54%,
              rgba(6, 7, 26, 0) 100%);
  pointer-events: none;
  transition: opacity 320ms var(--ease);
}
.masthead.is-scrolled {
  background: rgba(9, 10, 34, 0.78);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  backdrop-filter: saturate(150%) blur(14px);
  border-bottom-color: var(--line);
}
.masthead.is-scrolled::before { opacity: 0; }

.masthead__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
}

.masthead__nav {
  display: flex;
  gap: clamp(18px, 2.4vw, 34px);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.masthead__nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;         /* the label is 12px; the target is not */
  color: var(--ink-2);
  text-decoration: none;
  transition: color 200ms var(--ease);
}
.masthead__nav a::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 8px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 320ms var(--ease);
}
.masthead__nav a:hover { color: var(--ink); }
.masthead__nav a:hover::after { transform: scaleX(1); }

/* ────────────────────────────────────────────────────────────── 6 · hero
   Full viewport. The footage runs at full strength; only a gradient behind
   the type darkens anything, and it tops out at 34%. */
.hero {
  position: relative;
  isolation: isolate;                 /* blend root — see the file header */
  /* `vh` first as the floor for anything that doesn't know the small/large
     units, then `svh`. Deliberately NOT `dvh`: dvh tracks the browser chrome,
     so on iOS Safari the hero grows and shrinks as the URL bar collapses and
     the whole page below it re-lays-out mid-scroll. `svh` is the smallest
     stable viewport — the hero fills the screen at rest and never resizes. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: clip;
  background: var(--indigo);
}

.hero__ground {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

.hero__orb {
  position: absolute;
  top: 50%;
  left: 62%;
  width: clamp(300px, 46vw, 720px);
  aspect-ratio: 1;
  translate: -50% -54%;
  mix-blend-mode: screen;             /* no z-index here or on any ancestor */
  pointer-events: none;
}
/* The recording's field is near-black rather than black, so `screen` alone
   lifts it into a visible square. Contrast crushes that field to true black,
   and a radial mask feathers whatever survives at the corners. */
.hero__orb video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: contrast(1.42) brightness(1.04) saturate(1.05);
  -webkit-mask-image: radial-gradient(circle at 50% 50%,
                      #000 42%, rgba(0, 0, 0, 0.62) 56%, transparent 72%);
  mask-image: radial-gradient(circle at 50% 50%,
              #000 42%, rgba(0, 0, 0, 0.62) 56%, transparent 72%);
}

@keyframes orb-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.045); }
}

.hero__scrim {
  position: absolute;
  inset: auto 0 0;
  height: 72%;
  background: linear-gradient(to top,
              rgba(5, 6, 22, 0.34) 0%,
              rgba(5, 6, 22, 0.26) 26%,
              rgba(5, 6, 22, 0.10) 58%,
              rgba(5, 6, 22, 0) 100%);
  pointer-events: none;
}

.hero__inner {
  position: relative;
  z-index: var(--z-copy);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-6);
  /* the home indicator sits in the bottom ~34px on a gesture-bar phone, and
     the scroll cue is the lowest thing in the hero */
  padding-bottom: calc(clamp(40px, 7vh, 88px) + var(--safe-b));
  padding-top: clamp(140px, 22vh, 220px);
}

.hero__copy { max-width: min(100%, 660px); }

.hero__title {
  margin: 0;
  font-size: clamp(2.75rem, 8.6vw, 6rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.04em;
  color: var(--ink);
  text-wrap: balance;
}
.hero__line { display: block; }
.hero__line--accent { color: var(--teal); }

.hero__sub {
  max-width: 34ch;
  margin-top: clamp(20px, 3vh, 32px);
  color: var(--ink-2);
  font-size: clamp(1rem, 0.94rem + 0.3vw, 1.125rem);
  line-height: 1.55;
  text-wrap: pretty;
}

.hero__cue {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding-inline: 4px;
  flex: none;
  color: var(--ink-3);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  text-decoration: none;
  transition: color 220ms var(--ease);
}
.hero__cue svg { width: 12px; height: 19px; }
.hero__cue svg path { animation: cue-drift 2.6s var(--ease) infinite; }
.hero__cue:hover { color: var(--ink); }

@keyframes cue-drift {
  0%, 62%, 100% { transform: translateY(0); }
  30%           { transform: translateY(3px); }
}

/* ── hero entrance ─────────────────────────────────────────────────────────
   CSS animations, not class-gated transitions: the resting state is the
   visible state, so a headless render or a background tab still ships the
   composed hero. */
.hero__line,
.hero__sub,
.hero__cue { animation: rise-in 900ms var(--ease) both; }
.hero__line:nth-child(1) { animation-delay: 60ms; }
.hero__line:nth-child(2) { animation-delay: 150ms; }
.hero__line:nth-child(3) { animation-delay: 240ms; }
.hero__sub               { animation-delay: 380ms; }
.hero__cue               { animation-delay: 520ms; }

.hero__ground { animation: ground-settle 1600ms var(--ease) both; }
.hero__orb    { animation: orb-arrive 1800ms var(--ease) both,
                           orb-breathe 11s var(--ease) 1800ms infinite; }

@keyframes rise-in {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: none; }
}
@keyframes ground-settle {
  from { opacity: 0.5; transform: scale(1.06); }
  to   { opacity: 1;   transform: none; }
}
@keyframes orb-arrive {
  from { opacity: 0; transform: scale(0.86); }
  to   { opacity: 1; transform: scale(1); }
}

/* ──────────────────────────────────────────────── 7 · filament line art */
.filament {
  position: absolute;
  width: min(58vw, 620px);
  height: auto;
  aspect-ratio: 12 / 7;
  color: #A9B7E8;
  opacity: 0.1;
  pointer-events: none;
}
.filament--tr { top: 0; right: 0; }

/* ───────────────────────────────────────────────── 8 · the poster grid */
.screens {
  position: relative;
  overflow: clip;
  /* the film runs edge to edge directly above this, so the grid needs a
     clear beat of ground before its furniture starts */
  padding-block: clamp(76px, 9vw, 128px) var(--band);
}

.grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: start;
  gap: var(--gap);
}

.tile {
  position: relative;
  container-type: inline-size;    /* poster type scales with its own card */
  /* A poster tile's media and caption are both absolutely positioned, so the
     box has no intrinsic width to fall back on. Stated explicitly: a tile that
     ever loses its stretch — a `max-width`, a `margin: 0 auto`, a change of
     `justify-items` — would otherwise collapse to zero and vanish silently at
     narrow widths while still looking correct at 1440. */
  width: 100%;
  min-width: 0;
  overflow: hidden;
  border-radius: 2px;
  background: #0E1030;
}

/* Alternating aspect and a dropped second column: the row is a rhythm, not
   four identical cards. Both are desktop-only — a stagger at 375 is noise. */
.tile--poster { aspect-ratio: 0.60; }
.tile--poster.tile--content,
.tile--poster.tile--home { aspect-ratio: 0.68; }

.tile__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Per-tile framing. The app draws its own header at the top of every screen;
   where that header repeats a title set in HTML below, the crop moves down
   past it. */
.tile--voice   .tile__media { object-position: 50% 92%; }  /* past "Speaking with VERA" */
.tile--content .tile__media { object-position: 50% 40%; }  /* below the app's own header */
.tile--courses .tile__media { object-position: 50% 30%; }
.tile--home    .tile__media { object-position: 50% 0%; }

/* Two gradients, both at the edges: a short one seating the corner meta and
   pushing the app's own chrome back, and a base one the title stands on that
   melts the foot of the tile into the page ground. Between them the frame is
   untouched — roughly the middle half of every tile runs at full strength. */
.tile__wash {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(6, 7, 26, 0.52) 0%,
      rgba(6, 7, 26, 0.17) 10%,
      rgba(6, 7, 26, 0) 20%),
    linear-gradient(to top,
      var(--bg) 0%,
      rgba(11, 12, 38, 0.97) 13%,
      rgba(11, 12, 38, 0.80) 32%,
      rgba(11, 12, 38, 0.38) 56%,
      rgba(11, 12, 38, 0) 74%);
  pointer-events: none;
  transition: opacity 420ms var(--ease);
}

.tile__meta {
  position: absolute;
  top: clamp(12px, 3.6cqw, 18px);
  z-index: var(--z-copy);
  color: rgba(255, 255, 255, 0.82);
  font-size: clamp(0.625rem, 2.6cqw, 0.6875rem);
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1;
  text-transform: uppercase;
  text-shadow: 0 1px 10px rgba(4, 5, 20, 0.75);
}
.tile__meta--start { left: clamp(12px, 3.6cqw, 18px); }
.tile__meta--end   { right: clamp(12px, 3.6cqw, 18px); font-variant-numeric: tabular-nums; }

.tile__caption {
  position: absolute;
  inset: auto 0 0;
  z-index: var(--z-copy);
  padding: clamp(14px, 5cqw, 26px);
}
.tile__title {
  margin: 0;
  color: var(--ink);
  font-size: clamp(1.125rem, 8.2cqw, 2.125rem);
  font-weight: 700;
  line-height: 1.0;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

@media (hover: hover) {
  .tile--poster:hover .tile__wash { opacity: 0.86; }
}

/* ── the solid card that breaks the image rhythm ───────────────────────── */
.tile--band {
  grid-column: 1 / -1;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 5vw, 88px);
  padding: clamp(26px, 4.2vw, 54px);
  padding-top: clamp(46px, 6vw, 74px);
  background: var(--light);
  color: var(--navy);
  aspect-ratio: auto;
}
/* 0.80 rather than a lighter grey: 11px meta still has to clear 4.5:1, and
   at 0.62 on this ground it lands at 3.3. */
.tile--band .tile__meta { color: rgba(43, 58, 92, 0.80); text-shadow: none; }

.tile__wordmark {
  width: clamp(150px, 19vw, 270px);
  height: auto;
  flex: none;
}
.tile__statement {
  max-width: 26ch;
  margin: 0;
  font-size: clamp(1.25rem, 0.92rem + 1.45vw, 2.125rem);
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: -0.028em;
  text-wrap: pretty;
}

/* ───────────────────────────────────────────────────────────── 9 · film
   The page's second beat, cut flush against the hero: no padding above, no
   gutter either side, no heading in between. The title lands and the product
   is already running.

   Full-bleed at every width. The frame stays 16:9 at every width too, because
   the film composes across its own frame — a phone on one side, a line of
   type on the other — so a portrait crop on a phone would cut the type out.
   On a narrow screen that means the film is exactly as wide as the device and
   as tall as 16:9 permits; it is the widest thing on the page either way. */
.film {
  position: relative;
  background: #05061A;
  padding: 0;
}
.film__frame {
  position: relative;
  /* Explicit: everything inside this box is absolutely positioned, so it has
     no intrinsic width of its own. The moment it becomes a flex or grid child
     — or picks up `max-width` with `margin-inline: auto` — it collapses to
     zero and the film disappears without a single style erroring. */
  width: 100%;
  overflow: hidden;
  background: #05061A;
  aspect-ratio: 16 / 9;
}
.film__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* corners only, so the frame itself stays at full strength */
.film__vignette {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(4, 5, 20, 0.34) 0%, rgba(4, 5, 20, 0) 22%),
    linear-gradient(to top,    rgba(4, 5, 20, 0.34) 0%, rgba(4, 5, 20, 0) 26%);
  pointer-events: none;
}

.furniture--inset {
  position: absolute;
  top: clamp(14px, 2.2vw, 26px);
  left: max(clamp(16px, 2.4vw, 30px), var(--safe-l));
  right: max(clamp(16px, 2.4vw, 30px), var(--safe-r));
  bottom: auto;
  z-index: var(--z-copy);
  margin: 0;
  color: rgba(255, 255, 255, 0.84);
  text-shadow: 0 1px 10px rgba(4, 5, 20, 0.7);
}

/* ── the film's one control: open it full screen ──────────────────────────
   The button covers the whole frame, so a click anywhere on the film opens
   it — which is what "click the video" means to anyone who isn't reading the
   markup. It is transparent; the corner tag is the visible part, and the only
   thing on the page that says the film can be opened.

   This is the single interactive video on the page. Everything else stays
   inert by `video { pointer-events: none }` above. */
.film__open {
  position: absolute;
  inset: 0;
  z-index: var(--z-chrome);
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: clamp(16px, 2.4vw, 30px);
  padding-right: max(clamp(16px, 2.4vw, 30px), var(--safe-r));
  padding-bottom: clamp(20px, 2.8vw, 34px);
  width: 100%;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.film__open[hidden] { display: none; }

.film__openTag {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;             /* the label is 11px; the target is the frame */
  padding: 10px 16px;
  border: 1px solid rgba(220, 228, 245, 0.34);
  border-radius: 2px;
  background: rgba(8, 10, 32, 0.58);
  color: var(--ink);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  line-height: 1;
  text-transform: uppercase;
  transition: border-color 220ms var(--ease), background-color 220ms var(--ease);
}
.film__openGlyph { width: 15px; height: 15px; }

@media (hover: hover) {
  .film__open:hover .film__openTag {
    border-color: rgba(220, 228, 245, 0.78);
    background: rgba(8, 10, 32, 0.78);
  }
}
/* The button is the size of the film, so the default outline would ring the
   whole frame off-screen. Ring the tag instead, and inset it. */
.film__open:focus-visible { outline: none; }
.film__open:focus-visible .film__openTag {
  outline: 2px solid var(--teal);
  outline-offset: 3px;
  border-color: rgba(220, 228, 245, 0.78);
}

/* ── the site's own player, full screen ───────────────────────────────────
   The fullscreen request is made against `.film__frame`, so the video and
   this chrome are siblings inside the fullscreen element and both draw. The
   film is fitted rather than filled: `cover` would crop the type off the
   sides of its own frames.

   Every `:fullscreen` rule is written twice, once per prefix. They cannot be
   combined into a selector list — an engine that does not know one of the
   pseudo-classes drops the entire rule. */
.film__frame:fullscreen {
  width: 100%;
  height: 100%;
  max-width: none;              /* clears the short-viewport width cap below */
  aspect-ratio: auto;
  background: #05061A;
}
.film__frame:-webkit-full-screen {
  width: 100%;
  height: 100%;
  max-width: none;
  aspect-ratio: auto;
  background: #05061A;
}
.film__frame:fullscreen .film__media { object-fit: contain; }
.film__frame:-webkit-full-screen .film__media { object-fit: contain; }

/* Inline furniture stands down full screen: the player states the title, the
   time and the position itself, and saying any of it twice is noise. */
.film__frame:fullscreen .furniture--inset,
.film__frame:fullscreen .film__progress,
.film__frame:fullscreen .film__open { display: none; }
.film__frame:-webkit-full-screen .furniture--inset,
.film__frame:-webkit-full-screen .film__progress,
.film__frame:-webkit-full-screen .film__open { display: none; }

.film__player {
  position: absolute;
  inset: 0;
  z-index: var(--z-chrome);
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
}
.film__player[hidden] { display: none; }

/* The click surface sits under both bars and over the film. Not a button and
   not tabbable: the play control below carries the same action, and a second
   announcement of it would only be noise. */
.film__surface {
  position: absolute;
  inset: 0;
  grid-row: 1 / -1;
  cursor: pointer;
}

.film__bar {
  position: relative;           /* over the surface */
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 24px);
  padding: clamp(18px, 2.4vw, 32px) max(clamp(18px, 2.4vw, 32px), var(--safe-r))
           calc(clamp(18px, 2.4vw, 32px) + var(--safe-b))
           max(clamp(18px, 2.4vw, 32px), var(--safe-l));
  transition: opacity 260ms var(--ease), transform 260ms var(--ease);
}
.film__bar--top {
  grid-row: 1;
  justify-content: space-between;
  padding-bottom: clamp(28px, 5vw, 64px);
  background: linear-gradient(to bottom, rgba(4, 5, 20, 0.62) 0%, rgba(4, 5, 20, 0) 100%);
}
.film__bar--bottom {
  grid-row: 3;
  padding-top: clamp(28px, 5vw, 64px);
  background: linear-gradient(to top, rgba(4, 5, 20, 0.72) 0%, rgba(4, 5, 20, 0) 100%);
}

/* Idle: the chrome steps out of the way of the film it is describing, and
   stops taking the pointer so the surface underneath keeps the click. */
.film__player.is-idle .film__bar {
  opacity: 0;
  pointer-events: none;
}
.film__player.is-idle .film__bar--top    { transform: translateY(-8px); }
.film__player.is-idle .film__bar--bottom { transform: translateY(8px); }

.furniture--player {
  margin: 0;
  color: rgba(255, 255, 255, 0.84);
  text-shadow: 0 1px 10px rgba(4, 5, 20, 0.7);
}

/* ── the controls themselves ─────────────────────────────────────────── */
.film__ctl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid rgba(220, 228, 245, 0.34);
  border-radius: 2px;
  background: rgba(8, 10, 32, 0.58);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 220ms var(--ease), background-color 220ms var(--ease);
}
@media (hover: hover) {
  .film__ctl:hover {
    border-color: rgba(220, 228, 245, 0.78);
    background: rgba(8, 10, 32, 0.78);
  }
}
.film__ctl svg { width: 16px; height: 16px; }

/* One button, two glyphs, stacked in the same cell so the control never
   changes width as the state flips. */
.film__ctl--play { display: grid; place-items: center; }
.film__glyph { grid-area: 1 / 1; transition: opacity 160ms var(--ease); }
.film__glyph--play  { opacity: 0; }
.film__glyph--pause { opacity: 1; }
.film__player.is-paused .film__glyph--play  { opacity: 1; }
.film__player.is-paused .film__glyph--pause { opacity: 0; }

/* ── the seek bar ────────────────────────────────────────────────────── */
.film__seek {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 44px;                 /* the target; the line inside is 3px */
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;           /* drag to scrub, don't scroll the page */
}
/* The three layers are pinned to the middle of the 44px target explicitly —
   an absolutely-positioned child does not inherit the flex centring. */
.film__seek::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 3px;
  margin-top: -1.5px;
  background: rgba(220, 228, 245, 0.26);
}
.film__seekFill {
  position: absolute;
  left: 0;
  top: 50%;
  width: 0;
  height: 3px;
  margin-top: -1.5px;
  background: var(--light);
}
.film__seekHead {
  position: absolute;
  left: 0;
  top: 50%;
  width: 11px;
  height: 11px;
  margin: -5.5px 0 0 -5.5px;
  border-radius: 50%;
  background: var(--light);
  transform: scale(0);
  transition: transform 180ms var(--ease);
}
.film__seek:hover .film__seekHead,
.film__seek:focus-visible .film__seekHead,
.film__seek.is-scrubbing .film__seekHead { transform: scale(1); }
.film__seek:focus-visible { outline: 2px solid var(--teal); outline-offset: 4px; }

.film__time {
  flex: none;
  margin: 0;
  color: rgba(255, 255, 255, 0.84);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 10px rgba(4, 5, 20, 0.7);
}
.film__timeSep { opacity: 0.5; margin: 0 0.45em; }

/* Reduced motion: the chrome still gets out of the way, it just does not
   slide or fade to do it. */
@media (prefers-reduced-motion: reduce) {
  .film__player.is-idle .film__bar--top,
  .film__player.is-idle .film__bar--bottom { transform: none; }
}

/* A hairline reporting the loop's position. It is an indicator, not a control:
   no pointer events, nothing to press, hidden from assistive tech. */
.film__progress {
  position: absolute;
  inset: auto 0 0;
  z-index: var(--z-chrome);
  height: 2px;
  background: rgba(220, 228, 245, 0.16);
  pointer-events: none;
}
.film__progress span {
  display: block;
  width: 0;
  height: 100%;
  background: var(--light);
  transform-origin: left;
}

/* ────────────────────────────────────────────────────────── 10 · closing */
.closing {
  position: relative;
  overflow: clip;
  padding-block: clamp(96px, 15vw, 200px);
  background: var(--indigo);
}
/* mirrored, so the closing is the hero's ground read backwards rather than
   the same frame twice */
.closing__ground {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1);
  pointer-events: none;
}
/* behind the type only, and nowhere near the 35% ceiling */
.closing__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(102deg,
              rgba(5, 6, 22, 0.34) 0%,
              rgba(5, 6, 22, 0.16) 44%,
              rgba(5, 6, 22, 0) 76%);
  pointer-events: none;
}
.closing__inner { position: relative; z-index: var(--z-copy); }
.closing__title {
  max-width: 16ch;
  margin: 0;
  color: var(--ink);
  font-size: clamp(2.125rem, 6.4vw, 4.5rem);
  font-weight: 700;
  line-height: 1.0;
  letter-spacing: -0.04em;
  text-wrap: balance;
}

/* ──────────────────────────────────────────────────────── 11 · colophon */
.colophon {
  padding-block: clamp(40px, 6vw, 72px);
  border-top: 1px solid var(--line);
}
.colophon__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5) var(--s-7);
  flex-wrap: wrap;
}
.colophon__tag,
.colophon__legal {
  margin: 0;
  color: var(--ink-3);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}
.colophon__legal { margin-left: auto; font-variant-numeric: tabular-nums; }

/* ────────────────────────────────────────────────────────── 12 · reveals
   Armed by script.js only for elements sitting below the first screen, and
   released again by a timeout if the observer never fires. Anything on
   screen at load is simply visible. */
.reveal {
  opacity: 0;
  transform: translateY(22px) scale(0.988);
  transition: opacity 760ms var(--ease), transform 760ms var(--ease);
  will-change: opacity, transform;
}
.reveal.is-in { opacity: 1; transform: none; }
.reveal .tile__media { transform: scale(1.05); transition: transform 1500ms var(--ease); }
.reveal.is-in .tile__media { transform: none; }

/* ────────────────────────────────────────────────────── 13 · responsive */

/* Below the widest tier the dropped column and the alternating aspects go
   away: at two across they read as a mistake rather than a rhythm. */
@media (min-width: 1040px) {
  .tile--content { margin-top: clamp(28px, 4.4vw, 72px); }
  .tile--home    { margin-top: clamp(28px, 4.4vw, 72px); }
}

/* Down to two across, only the column count changes: the tile aspects hold,
   because each crop is tuned to keep the app's own header out of the meta's
   way and changing the ratio re-frames all four. The one place they do move
   is the single-column tier below, where a 0.60 tile runs the full width of
   the device and stands taller than the film. */
@media (max-width: 1039px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 860px) {
  .hero__inner { display: block; }
  .hero__copy { max-width: none; }
  .hero__cue { display: inline-flex; margin-top: clamp(28px, 5vh, 48px); }

  .tile--band {
    display: block;
    padding-top: clamp(44px, 9vw, 64px);
  }
  .tile__wordmark { width: clamp(140px, 30vw, 200px); }
  .tile__statement { max-width: 30ch; margin-top: clamp(20px, 4vw, 32px); }
}

@media (max-width: 620px) {
  :root {
    --shell: 100%;
    /* One column of portrait footage needs air between the frames. At the
       desktop 8px the base scrim of one tile melts into the top of the next
       and the four loops read as a single indigo smear. */
    --gap: 26px;
  }

  .grid {
    grid-template-columns: minmax(0, 1fr);
    /* Cards go edge to edge, per the poster grid: the tiles break out of the
       shell's gutter and the page furniture stays pinned inside it. Negative
       margins are safe here — `.screens` is `overflow: clip`, so nothing can
       reach the document's scroll width. */
    margin-left: calc(var(--pad-l) * -1);
    margin-right: calc(var(--pad-r) * -1);
  }

  /* Type on a full-bleed tile lines up with the page furniture above it
     rather than floating at its own container-relative inset. */
  .tile__caption {
    padding: var(--pad-l) var(--pad-r) clamp(18px, 5vw, 26px) var(--pad-l);
  }
  .tile__meta--start { left: var(--pad-l); }
  .tile__meta--end   { right: var(--pad-r); }
  .tile--band { padding: clamp(38px, 9vw, 56px) var(--pad-r) clamp(28px, 7vw, 40px) var(--pad-l); }

  /* Squarer tiles once they run full-bleed. At the desktop 0.60 a 375-wide
     tile stands 625px tall: taller than the film above it, so the supporting
     loops out-measure the thing they support, and the section alone ran to
     two thirds of the document. The alternation is kept — it is the only
     rhythm a single column has — just at a shorter pair of ratios. */
  .tile--poster {
    aspect-ratio: 0.70;
    /* Floor for short phones held upright, so the head of the next loop stays
       in frame rather than one poster filling the screen. */
    max-height: 78svh;
  }
  .tile--poster.tile--content,
  .tile--poster.tile--home { aspect-ratio: 0.80; }

  /* The reveal's scale would open a hairline of page ground down both edges
     of a tile that is meant to be flush with the screen. */
  .reveal { transform: translateY(22px); }

  .hero__orb { left: 68%; width: min(94vw, 460px); translate: -50% -60%; }

  .colophon__inner { display: grid; gap: var(--s-4); }
  .colophon__legal { margin-left: 0; }
}

/* ── short viewports: phones held sideways, and small laptops ──────────────
   The hero is sized in `svh`, so at 375 tall its whole budget is 375px. The
   desktop top padding alone is 140px of that, and the title would push the
   sub-heading and the cue out through the bottom of the frame. */
@media (max-height: 560px) {
  /* A phone turned sideways is 375px tall and the film is 16:9, so at full
     bleed the frame stands taller than the screen. Constrain the width so the
     height fits instead — the ratio is what must not move, since the film
     composes across its own frame. `.film__frame` carries an explicit
     `width: 100%`, which is what stops `max-width` + `auto` margins from
     collapsing a box whose children are all absolutely positioned. */
  .film__frame {
    max-width: calc(100svh * 16 / 9);
    margin-inline: auto;
  }
}

@media (max-height: 560px) and (min-width: 621px) {
  .hero__inner {
    padding-top: clamp(88px, 18vh, 120px);
    padding-bottom: calc(clamp(24px, 5vh, 40px) + var(--safe-b));
  }
  .hero__title { font-size: clamp(2rem, 5.4vh, 3.25rem); }
  .hero__sub { margin-top: clamp(12px, 2.4vh, 20px); }
  .hero__orb { width: clamp(220px, 62vh, 420px); }
}

/* ─────────────────────────────────────────────── 14 · reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  /* the composed page, immediately and permanently */
  .hero__line, .hero__sub, .hero__cue,
  .hero__ground, .hero__orb { opacity: 1; transform: none; }
  .reveal { opacity: 1; transform: none; }
  .reveal .tile__media { transform: none; }
}

/* ───────────────────────────────────────────────────────────── 15 · print */
@media print {
  .masthead, .film__progress, .film__open, .hero__cue, .skip-link { display: none; }
  body { background: #fff; color: #111; }
}

/* Founder line. These are the only outbound links on this site — the sites are
   connected by the person who started them, not by the products. */
.founder { margin-top: 14px; font-size: 0.8125rem; color: #8a93a6; }
.founder__also a { color: inherit; text-decoration: none; border-bottom: 1px solid rgba(255,255,255,.28); }
.founder__also a:hover { color: #fff; }


/* ==========================================================================
   Synth footer — the SynthCard material from trusynth.com.
   Ground #17120a, rim rgba(216,196,142,.16), the silk-brass WebGL foil, and
   the etched mark. Values are the source's own; the card fills the footer
   instead of holding the hero's 1.586 card ratio.
   ========================================================================== */
.synth {
  padding: clamp(20px, 3vw, 40px) clamp(16px, 4vw, 48px) clamp(28px, 4vw, 56px);
  background: transparent;
}

.synth__card {
  position: relative;
  width: 100%;
  max-width: none;
  margin: 0;
  border-radius: clamp(16px, 2.6vw, 28px);
  overflow: hidden;
  isolation: isolate;
  background: #17120a;
  border: 1px solid rgba(216, 196, 142, 0.16);
  box-shadow:
    0 40px 120px rgba(0, 0, 0, 0.85),
    0 8px 30px rgba(0, 0, 0, 0.6),
    inset 0 0 0 1px rgba(242, 232, 200, 0.14);
}
/* The lit top edge and shadowed bottom edge that give the metal its thickness. */
.synth__card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 246, 227, 0.35),
    inset 0 -1px 0 rgba(20, 14, 4, 0.5);
  z-index: 2;
}

.synth__foil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
}
/* Without WebGL the card keeps its ground and rim and simply does not shimmer. */
.synth__card.is-flat .synth__foil { display: none; }

/* The content sits on the foil, in the source's own ink. */
.synth__face {
  position: relative;
  z-index: 1;
  padding: clamp(34px, 6vw, 76px) clamp(20px, 4vw, 48px);
  text-align: center;
  color: #2b2210;
}
.synth__mark {
  width: clamp(96px, 11vw, 148px);
  height: auto;
  margin: 0 auto clamp(16px, 2.2vw, 24px);
}
.synth__label {
  margin: 0 0 10px;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
  font-size: clamp(9px, 0.95vw, 11px);
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  line-height: 1.9;
  color: rgba(43, 34, 16, 0.72);
}
.synth__label b { font-weight: 700; color: #2b2210; }
.synth__body {
  max-width: 54ch;
  margin: 0 auto clamp(18px, 2.4vw, 26px);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: rgba(43, 34, 16, 0.82);
}
.synth__link {
  display: block;
  width: var(--etch-w, min(46cqw, 380px));
  margin: clamp(26px, 4vw, 54px) auto 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: clamp(9px, 0.95vw, 11px);
  font-weight: 600;
  /* Ranged to the mark above it: a fixed width plus text-align-last stretches
     the single line so its ends sit under the logo's ends. The tracking is a
     floor — justification adds to it, never below it. */
  letter-spacing: 0.14em;
  text-align: justify;
  text-align-last: justify;
  text-transform: uppercase;
  color: rgba(43, 34, 16, 0.78);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 240ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* No ring on a mouse click. :focus-visible fires for keyboard only, so the
   keyboard path keeps an indicator — removing that outright would strand
   anyone not using a pointer. */
.synth__link:focus { outline: none; }
.synth__link:hover { background: rgba(242, 232, 200, 0.42); border-color: rgba(43, 34, 16, 0.6); }
.synth__link:focus-visible { outline: 2px solid #2b2210; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .synth__link { transition: none; } }

/* The mark is embossed by the shader, not painted over it: the etch in the
   foil is the logo. The <img> stays in the markup for the no-WebGL fallback
   and for assistive tech, and is hidden once the foil is live — two copies of
   the same mark, one stamped and one flat, is what made it read as a smudge. */
.synth__card:not(.is-flat) .synth__mark { display: none; }
.synth__card.is-flat .synth__mark {
  display: block;
  filter: brightness(0) invert(1);
  opacity: 0.86;
}
/* With the etch centred in the card, the copy needs room to clear it. */
.synth__card:not(.is-flat) .synth__face { padding-top: clamp(96px, 15vw, 190px); }

/* The card carries the etched mark and one link. Nothing else earns a place,
   so the face is sized to those two rather than to copy that is no longer here. */
.synth__card:not(.is-flat) .synth__face { padding-top: clamp(112px, 17vw, 210px); }
.synth__face { padding-bottom: clamp(28px, 4vw, 52px); }

.synth__card a, .synth__card button { -webkit-tap-highlight-color: transparent; }
