/*
 * Shared structure for every scroll-scrub site. Colour, type and proportion come from theme.css,
 * which the builder writes per client. Nothing here is brand specific.
 *
 * The reading layout is an asymmetric editorial grid: the heading holds a narrow left column and
 * the prose a wider right one. A page of paragraphs centred in one narrow stack reads as a draft,
 * however good the pictures between them are.
 */

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

/* Never scroll-behavior: smooth on html. It animates the jump, so anything that scrolls and then
   measures reads a position the page has not reached yet. */

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-size: 18px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, video, canvas { max-width: 100%; }

::selection { background: var(--accent); color: var(--light); }

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

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 5;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding: 22px clamp(16px, 4vw, 56px);
  /* A fixed header needs a ground, not a blend mode: it has to read on a light frame and a dark one. */
  background: linear-gradient(to bottom, rgba(6, 10, 22, 0.55), rgba(6, 10, 22, 0));
  color: var(--light);
  transition: background 260ms ease, color 260ms ease;
}

.nav-mark {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: var(--light);
  text-decoration: none;
}

.nav nav { display: flex; gap: 26px; flex-wrap: wrap; font-size: 0.95rem; }

.nav a:not(.nav-mark) {
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 3px;
  transition: color 180ms ease, border-color 180ms ease;
}

.nav a:not(.nav-mark):hover,
.nav a:not(.nav-mark):focus-visible,
.nav a[aria-current="page"] { color: var(--light); border-bottom-color: currentColor; }

/* Over paper the white header vanishes. Switched by the controller from what is actually behind
   it, not from which page it is, so a page that mixes films and reading bands stays legible
   through the whole scroll. A solid band rather than a fade: a gradient over running text makes
   the paragraph look like it is dissolving as it scrolls under the header. */
.nav.on-paper {
  background: var(--paper);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  color: var(--ink);
}

.nav.on-paper .nav-mark { color: var(--ink); }
.nav.on-paper a:not(.nav-mark) { color: color-mix(in srgb, var(--ink) 58%, transparent); }

.nav.on-paper a:not(.nav-mark):hover,
.nav.on-paper a:not(.nav-mark):focus-visible,
.nav.on-paper a[aria-current="page"] { color: var(--ink); border-bottom-color: currentColor; }

/* ---------- scroll-scrubbed films ---------- */

.scroll-section { position: relative; background: var(--ink); }

.scroll-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  z-index: 1;
  /* Opacity is driven by the controller from the stage's real position, so consecutive films
     dissolve through the page ground instead of cutting. */
  will-change: opacity;
}

/* A section scrubs EITHER a frame sequence into the canvas or an encoded video,
   and the controller swaps one for the other. Identical geometry, so the choice
   of source never moves anything. object-fit does for the video what the
   controller's cover arithmetic does for the canvas. */
.scroll-stage canvas,
.scroll-stage video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  will-change: transform;
}

.scroll-section + .scroll-section { margin-top: 0; }

/* A film arriving out of a reading band. Without this the picture butts against the page on a
   hard horizontal line and the page reads as two documents stacked rather than one that keeps
   going. The gradient sits at the section's own top, so it travels up with the leading edge. */
.scroll-section.after-band::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(110px, 18vh, 200px);
  background: linear-gradient(to bottom, var(--paper), transparent);
  z-index: 2;
  pointer-events: none;
}

/* The scrim keeps type readable on EVERY frame, instead of picking a colour that works on shot
   three and fails on shot seven. Its gradient is written per theme. */
.scroll-scrim { position: absolute; inset: 0; }

.scroll-copy {
  position: absolute;
  top: clamp(104px, 17vh, 190px);
  left: clamp(16px, 4vw, 56px);
  max-width: min(46ch, 70%);
  color: var(--light);
}

.scroll-copy h1,
.scroll-copy h2 {
  margin: 0 0 20px;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(2.2rem, 6vw, 4.4rem);
  line-height: 1.0;
  letter-spacing: var(--display-tracking);
  text-wrap: balance;
}

.scroll-section:not(.is-hero) .scroll-copy h2 {
  font-size: clamp(1.9rem, 4.6vw, 3.2rem);
}

/* Beats are tied to scroll the same way the frames are. Only the live one is readable. */
.scroll-beat {
  margin: 0;
  color: var(--muted);
  font-size: clamp(1.02rem, 1.5vw, 1.22rem);
  line-height: 1.5;
  max-width: 38ch;
  position: absolute;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 420ms ease, transform 420ms ease;
  pointer-events: none;
}

.scroll-beat.is-live { opacity: 1; transform: none; }

.cta {
  display: inline-block;
  margin-top: clamp(80px, 12vh, 150px);
  padding: 14px 28px;
  background: var(--light);
  color: var(--ink);
  font-weight: 600;
  font-size: 0.98rem;
  text-decoration: none;
  transition: opacity 500ms ease, transform 500ms ease, background 200ms ease, color 200ms ease;
}

.cta:hover, .cta:focus-visible { background: var(--accent); color: var(--light); }

/* The heading and its button belong to the opening beat. Once the shot tightens they leave, so
   nothing is stranded on top of the product. The button returns for the closing beat. */
.scroll-copy h1,
.scroll-copy h2,
.scroll-copy .cta { transition: opacity 500ms ease, transform 500ms ease; }

.scroll-copy.past-intro h1,
.scroll-copy.past-intro h2 {
  opacity: 0;
  transform: translateY(-10px);
  pointer-events: none;
}

.scroll-copy.past-intro .cta {
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
}

.scroll-copy.past-intro.at-outro .cta { opacity: 1; transform: none; pointer-events: auto; }

@media (prefers-reduced-motion: reduce) {
  .scroll-beat,
  .scroll-copy h1,
  .scroll-copy h2,
  .scroll-copy .cta { transition: none; }
}

/* ---------- reading bands ---------- */

main { background: var(--paper); position: relative; z-index: 1; }

main.inner { padding-top: 96px; }

.band {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 9fr);
  gap: clamp(28px, 5vw, 88px);
  padding: clamp(72px, 13vh, 170px) clamp(16px, 4vw, 56px);
  max-width: 1320px;
  margin: 0 auto;
}

.band + .band { border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent); }

.band-head h2 {
  margin: 0;
  font-family: var(--display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-tracking);
  line-height: 1.08;
  font-size: clamp(1.7rem, 2.9vw, 2.6rem);
  text-wrap: balance;
}

/* A short note beside the heading, where a project genuinely has one. Not an eyebrow label. */
.band-kicker {
  margin: 14px 0 0;
  font-size: 0.95rem;
  line-height: 1.45;
  color: color-mix(in srgb, var(--ink) 55%, transparent);
  max-width: 28ch;
}

.band-body > p { margin: 0 0 1.15em; max-width: 68ch; }
.band-body > p:last-child { margin-bottom: 0; }

.standfirst {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.25rem, 2.1vw, 1.7rem);
  line-height: 1.34;
  letter-spacing: -0.015em;
  margin: 0 0 1.1em;
  max-width: 34ch;
}

/* The lead block at the top of an inner page: one column, set large.
   The grid gap is for the two columns, not for stacked lines: left on, it opens a 88px hole
   between a heading and the line that belongs to it. */
.band.lead {
  grid-template-columns: 1fr;
  gap: 0;
  padding-bottom: clamp(40px, 7vh, 80px);
}

.band.lead h1 {
  margin: 0 0 20px;
  font-family: var(--display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-tracking);
  line-height: 1.02;
  font-size: clamp(2.6rem, 7vw, 5.2rem);
  text-wrap: balance;
}

.band.lead .standfirst { max-width: 40ch; }

/* The one oversized moment a page is allowed. Spend the boldness here and nowhere else. */
.band.is-statement {
  grid-template-columns: 1fr;
  gap: 0;
  padding-top: clamp(90px, 16vh, 200px);
  padding-bottom: clamp(90px, 16vh, 200px);
}

.statement {
  margin: 0;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.9rem, 4.6vw, 3.4rem);
  line-height: 1.12;
  letter-spacing: var(--display-tracking);
  max-width: 22ch;
  text-wrap: balance;
}

.statement-note {
  margin: 28px 0 0;
  font-size: 0.95rem;
  color: color-mix(in srgb, var(--ink) 55%, transparent);
}

/* A spec read as an index, not decorated as a card. */
.facts { margin: 44px 0 0; display: grid; gap: 0; max-width: 60ch; }

.facts > div {
  display: grid;
  grid-template-columns: minmax(7rem, 11rem) 1fr;
  gap: clamp(16px, 3vw, 40px);
  padding: 15px 0;
  border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  align-items: baseline;
}

.facts > div:last-child { border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); }

.facts dt {
  font-family: var(--display);
  font-weight: 500;
  font-size: 0.93rem;
  letter-spacing: 0.01em;
  color: color-mix(in srgb, var(--ink) 70%, transparent);
}

.facts dd { margin: 0; font-size: 0.99rem; line-height: 1.5; }

/* ---------- footer ---------- */

.foot {
  background: var(--ink);
  color: var(--light);
  padding: clamp(72px, 12vh, 150px) clamp(16px, 4vw, 56px) clamp(40px, 6vh, 70px);
  position: relative;
  z-index: 1;
}

.foot-top { max-width: 1320px; margin: 0 auto; }

.foot-mark {
  margin: 0;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(3.2rem, 11vw, 9rem);
  line-height: 0.92;
  letter-spacing: -0.045em;
}

.foot-line {
  margin: clamp(20px, 3vh, 34px) 0 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  line-height: 1.34;
  letter-spacing: -0.015em;
  max-width: 30ch;
  color: var(--muted);
}

.foot-cols {
  max-width: 1320px;
  margin: clamp(56px, 9vh, 110px) auto 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: clamp(28px, 4vw, 64px);
}

.foot-col h2 {
  margin: 0 0 12px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
  color: color-mix(in srgb, var(--light) 55%, transparent);
}

.foot-col p { margin: 0 0 6px; font-size: 0.98rem; color: var(--muted); }

.foot-base {
  max-width: 1320px;
  margin: clamp(56px, 9vh, 110px) auto 0;
  padding-top: 26px;
  border-top: 1px solid color-mix(in srgb, var(--light) 18%, transparent);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  flex-wrap: wrap;
  font-size: 0.92rem;
  color: color-mix(in srgb, var(--light) 55%, transparent);
}

.foot-base p { margin: 0; }
.foot-base nav { display: flex; gap: 24px; flex-wrap: wrap; }

.foot-base a {
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}

.foot-base a:hover, .foot-base a:focus-visible { color: var(--light); border-bottom-color: currentColor; }

/* ---------- tablet ---------- */

@media (max-width: 1000px) {
  .band { grid-template-columns: 1fr; gap: clamp(18px, 3vh, 30px); }
  .band-kicker { max-width: 46ch; }
}

/* ---------- phones ---------- */

@media (max-width: 767px) {
  body { font-size: 17px; }

  /* Five real routes will not sit beside the brand mark at phone width: they wrap into a second
     row and crowd the heading. The mark takes its own line and the routes become one row that
     scrolls sideways, so every route stays reachable without a menu button or any JavaScript. */
  .nav {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 14px clamp(16px, 4vw, 56px) 12px;
  }

  .nav nav {
    flex-wrap: nowrap;
    gap: 20px;
    font-size: 0.95rem;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    margin: 0 calc(-1 * clamp(16px, 4vw, 56px));
    padding: 0 clamp(16px, 4vw, 56px) 2px;
  }

  .nav nav::-webkit-scrollbar { display: none; }
  .nav nav a { white-space: nowrap; }

  .scroll-copy { max-width: 100%; top: clamp(128px, 19vh, 190px); }
  .cta { margin-top: clamp(60px, 10vh, 110px); }
  main.inner { padding-top: 112px; }

  .facts > div { grid-template-columns: 1fr; gap: 3px; padding: 13px 0; }
  .statement { max-width: 100%; }
  .foot-base { flex-direction: column; align-items: flex-start; gap: 18px; }
}

/* ---------- reading motion ---------- */

/* Only hide what the script is going to bring back. Guarding on the class the script sets means a
   blocked or failed script leaves a readable page rather than an invisible one. */
.js-reveal:not(.reduce-motion) .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity 780ms cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 780ms cubic-bezier(0.22, 0.61, 0.36, 1),
    filter 780ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

/* A heading arrives from further and out of focus, so it resolves rather than slides. */
.js-reveal:not(.reduce-motion) .reveal-head { transform: translateY(30px); filter: blur(7px); }
.js-reveal:not(.reduce-motion) .reveal-lead { transform: translateY(22px); filter: blur(4px); }
.js-reveal:not(.reduce-motion) .reveal-row  { transform: translateY(12px); }

.js-reveal:not(.reduce-motion) .reveal.is-in {
  opacity: 1;
  transform: none;
  filter: none;
  will-change: auto;
}

/* The statement lights word by word, tied to scroll position rather than triggered by it, so the
   reader sets the pace. No transition on the words: the scroll IS the timeline. */
.statement .word { opacity: 1; }
.js-reveal:not(.reduce-motion) .statement.is-split .word { opacity: 0.16; }

/* The measure. A technical rule drawn down the empty left column as the band is read. This brand
   is built on one last and one set of measurements, so the mark is the idea, not an ornament. */
.measure {
  display: block;
  position: relative;
  width: 1px;
  height: clamp(56px, 13vh, 150px);
  margin-top: clamp(24px, 4vh, 44px);
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--ink) 42%, transparent),
    color-mix(in srgb, var(--ink) 6%, transparent)
  );
  transform: scaleY(0);
  transform-origin: top center;
}

.measure::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 15px;
  height: 1px;
  background: color-mix(in srgb, var(--ink) 42%, transparent);
}

.reduce-motion .measure { display: none; }

@media (max-width: 1000px) {
  /* With the grid collapsed to one column the rule has no column to live in. */
  .measure { display: none; }
}

/* ---------- liquid glass ---------- */

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

/* Over a film the header becomes a pane of glass rather than a gradient: the picture behind it
   bends at the edge instead of merely softening, which is the whole difference between liquid
   glass and a blur. Over paper it stays the solid band, because glass on a flat ground is just
   a smudge. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .nav:not(.on-paper) {
    background: linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.10),
      rgba(255, 255, 255, 0.03) 62%,
      rgba(255, 255, 255, 0) 100%
    );
    -webkit-backdrop-filter: blur(16px) saturate(1.7);
    backdrop-filter: blur(16px) saturate(1.7);
    border-bottom: 1px solid rgba(255, 255, 255, 0.14);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.26),
      inset 0 -1px 0 rgba(255, 255, 255, 0.06),
      0 14px 40px rgba(4, 8, 18, 0.26);
  }

  /* Chromium only. Everywhere else the rule is dropped and the blur above stands on its own. */
  @supports (backdrop-filter: url(#liquid)) {
    .nav:not(.on-paper) { backdrop-filter: url(#liquid) blur(14px) saturate(1.7); }
  }

  /* The call to action is the one control on the page, so it is the one that gets the full
     treatment: refracted ground, a lit top edge, and a specular sweep that moves on hover. */
  .cta {
    position: relative;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.12);
    color: var(--light);
    -webkit-backdrop-filter: blur(14px) saturate(1.8);
    backdrop-filter: blur(14px) saturate(1.8);
    border: 1px solid rgba(255, 255, 255, 0.26);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.42),
      inset 0 -8px 18px rgba(255, 255, 255, 0.06),
      0 10px 30px rgba(4, 8, 18, 0.3);
  }

  @supports (backdrop-filter: url(#liquid-soft)) {
    .cta { backdrop-filter: url(#liquid-soft) blur(12px) saturate(1.8); }
  }

  /* The specular highlight. A single pass of light across the pane, not a pulsing glow. */
  .cta::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
      110deg,
      transparent 20%,
      rgba(255, 255, 255, 0.4) 46%,
      rgba(255, 255, 255, 0.08) 56%,
      transparent 72%
    );
    transform: translateX(-130%);
    transition: transform 900ms cubic-bezier(0.22, 0.61, 0.36, 1);
    pointer-events: none;
  }

  .cta:hover, .cta:focus-visible {
    background: rgba(255, 255, 255, 0.2);
    color: var(--light);
    border-color: rgba(255, 255, 255, 0.5);
  }

  .cta:hover::after, .cta:focus-visible::after { transform: translateX(130%); }

  .reduce-motion .cta::after { display: none; }
}

/* ---------- beats that carry an argument ---------- */

/* With one continuous film running a whole page, a beat stops being a caption and becomes the
   section: a heading and a short argument, held over the picture on the scrim. */
.scroll-beat {
  display: block;
  /* Held to the left third. The film moves, so the clear zone moves with it: a wider column
     reads fine on one shot and lands on the product two seconds later. */
  max-width: 34ch;
}

.scroll-beat h3 {
  margin: 0 0 14px;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.7rem, 3.4vw, 2.7rem);
  line-height: 1.06;
  letter-spacing: var(--display-tracking);
  color: var(--light);
  text-wrap: balance;
}

.scroll-beat p {
  margin: 0 0 0.8em;
  font-size: clamp(1rem, 1.45vw, 1.18rem);
  line-height: 1.5;
  color: var(--muted);
  max-width: 33ch;
}

.scroll-beat p:last-child { margin-bottom: 0; }

/* The hero headline owns the opening beat only, so once the film has started the beats have the
   stage to themselves and nothing is ever stacked on top of anything else. */
.scroll-copy.past-intro .scroll-beat { max-width: 36ch; }

@media (max-width: 767px) {
  .scroll-beat, .scroll-beat p { max-width: 100%; }
  .scroll-beat h3 { font-size: clamp(1.5rem, 7vw, 2rem); }
}
