/* Landing page styles.
   Tokens, couplings and verification traps: docs/relaunch-tokens.md
   The typeface is loaded by a <link> in the page head, not by @import
   here — an @import inside a linked sheet serialises the font request
   behind this file instead of running alongside it. */

/* Schibsted Grotesk starts at 400 — it has no 100/200/300. The page's light
   weights were remapped (100→400 body, 200/300→500 headings and nav) rather
   than left in place, where the browser would clamp them all to 400 and the
   type hierarchy would flatten into a single weight. */

:root {
  --canvas:   #f6f8f7;
  --ink:      #000000;
  --carbon:   #0f0e12;
  --rule:     #e5e5e5;
  --rule-dk:  #26252b;
  /* graphite = muted on the dark band (8:1 on carbon). On the light canvas
     #b2b2b2 is only ~2:1, so light-canvas muted text uses --muted instead. */
  --graphite: #b2b2b2;
  --muted:    #6e6e73;
  --accent:   #ff6b1a;
  --accent-t: #c94e05;  /* accent darkened for text on the light canvas (4.5:1) */

  --pad: clamp(22px, 5vw, 66px);
  --max: 1200px;

  --nav-h: 66px;        /* collapsed height — .story-viz sticks to this */
  --nav-h-max: 210px;   /* expanded height at the top of the page */

  /* Every font assignment routes through these two — change a typeface here,
     never at a use site. */
  --font-text:    'Schibsted Grotesk', -apple-system, sans-serif;
  --font-display: var(--font-text);
  --display-weight: 800;
  --display-track:  -.045em;
}

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

html { scroll-behavior: smooth; }

/* The nav is sticky and 66px tall, so an element scrolled to by its #fragment
   lands flush at y=0 — underneath the bar, with its top 66px covered. Found
   via the chapter skip links, but it was never about them: #livecompanion and
   #beatref are the nav's OWN targets and both landed clipped, which reads as
   "the jump is a bit off" rather than as a bug with a cause.
   On [id] rather than on the handful of sections that have anchors today:
   scroll-margin costs nothing on an element nothing ever scrolls to, and the
   alternative is a list that the next new anchor is silently missing from.
   var(--nav-h), not 66px — same coupling .story-viz has (relaunch-tokens.md).
   Safe to apply broadly here: nothing calls scrollIntoView() and there is no
   scroll-snap, the two other things scroll-margin would reach. */
[id] { scroll-margin-top: var(--nav-h); }

body {
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-text);
  font-weight: 400;
  font-size: 13px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video { display: block; width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }

.wrap { max-width: var(--max); margin: 0 auto; padding: 0 var(--pad); }

/* Zero radius everywhere. Sharp edges are the register. */

/* --t registered as a real <number> rather than left as a bare custom
   property: it is consumed inside rgb() and calc() chains, and a typed
   property keeps those substitutions well-defined instead of relying on
   raw token pasting. */
@property --t {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

/* ── NAV ───────────────────────────────────────────────────────────
   One element, two sizes, no state swap. --t runs 0 (top of page) → 1
   (collapsed) and every size below is a calc() over it. At --t:1 the
   second summand of the height is exactly (--nav-h - --nav-h-max), so
   the height lands on --nav-h precisely rather than approximately —
   which matters because .story-viz sticks to that value. */
nav {
  --t: 0;
  position: sticky; top: 0; z-index: 50;
  background: var(--canvas);
  border-bottom: 1px solid var(--rule);
  /* min-height, not height: at the expanded size four stacked rows are taller
     than --nav-h-max at wide viewports, and a fixed height clips them — the
     top row disappears off the top of the screen and the bottom row crosses
     the hairline. min-height still pins the collapsed end exactly, because
     one 15px row is far shorter than --nav-h. */
  min-height: calc(var(--nav-h-max) + (var(--nav-h) - var(--nav-h-max)) * var(--t));
  /* nav is the flex container so its own min-height centres the row. Giving
     .nav-in the min-height instead stacks it with the 1px border and the bar
     measures 67px, one off the value .story-viz sticks to. */
  display: flex; align-items: center;
}
.nav-in {
  position: relative;
  width: 100%; max-width: var(--max); margin: 0 auto;
  padding: calc(20px - 20px * var(--t)) var(--pad);
  /* center, because this row also holds the fox, and the fox is the tallest
     thing in the collapsed bar (38px against ~21px of text). The text's
     baseline problem is solved one level in, by .nav-row — putting
     `align-items: baseline` HERE instead looks right at a glance and pushes
     the whole text group to the TOP of a line box whose height the fox sets:
     measured 7.1px above centre collapsed, 15.4px expanded. */
  display: flex; align-items: center;
}

/* The wordmark and the links are one group with two requirements that pull
   against each other: they must share a baseline WITH EACH OTHER (two type
   sizes — 20/15px collapsed, 30/29px expanded — and anything else reads as a
   misaligned header), and the group must sit centred in the bar next to a fox
   that is taller than it. A single flex container cannot do both: baseline
   alignment places the baseline group at the START of the line.
   So: baseline in here, centred out there. */
.nav-row { display: flex; align-items: baseline; min-width: 0; }

/* The wordmark is the first nav entry and is present at every size, as it is
   in the reference. It stays on one line: the <br> in the markup is disabled
   here because display cannot be interpolated, and a line count that snaps
   mid-scroll is exactly the jump this nav is built to avoid. */
.nav-mark {
  flex-shrink: 0;
  margin-right: clamp(24px, 4vw, 60px);
  font-size: calc(clamp(22px, 2.2vw, 30px) + (20px - clamp(22px, 2.2vw, 30px)) * var(--t));
  font-weight: 500; letter-spacing: -.01em; line-height: 1.05;
}
.nav-mark i { font-style: normal; color: var(--accent); }
.nav-mark br { display: none; }

/* Each entry is a group: line-art icon, large label, small sub-links beneath.
   Collapsing folds the sub-links and the icon away and shrinks the label into
   the bar — the same --t, still no second layout and no class swap. */
.nav-links {
  display: flex; flex-wrap: wrap;
  align-items: flex-start;
  gap: calc(10px + 12px * var(--t)) clamp(16px, 2.4vw, 34px);
}
.nav-group { display: flex; flex-direction: column; }

.nav-top {
  /* baseline, and this is load-bearing for the wordmark two rules up.
     A flex container with no baseline-aligned child SYNTHESISES its baseline
     from the first child's bottom edge — here that is .nav-ico, whose height
     is calc(36px * (1 - --t)). So the baseline this row reports to the nav
     MOVED AS THE PAGE SCROLLED, and nothing above could align to it at both
     ends. With `baseline` the label's own text baseline is what the row
     reports, and it is the icon that lines up to the text instead. */
  display: flex; align-items: baseline; gap: calc(11px * (1 - var(--t)));
  font-size: calc(clamp(19px, 2.1vw, 29px) + (15px - clamp(19px, 2.1vw, 29px)) * var(--t));
  font-weight: 500;
  letter-spacing: calc(-.02em * (1 - var(--t)));
  line-height: 1.05;
  white-space: nowrap;
}

/* Icon and sub-links collapse to zero *size*, not merely zero opacity: left
   at full size behind opacity:0 they keep reserving the expanded layout and
   the bar never reaches --nav-h. */
.nav-ico {
  width: calc(36px * (1 - var(--t))); height: calc(36px * (1 - var(--t)));
  opacity: calc(1 - var(--t));
  flex-shrink: 0;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: square; stroke-linejoin: miter;
}
.nav-sub {
  display: flex; flex-direction: column;
  max-height: calc(74px * (1 - var(--t)));
  opacity: calc(1 - var(--t) * 2);
  overflow: hidden;
  margin-left: calc(47px * (1 - var(--t)));
  font-size: 13px; line-height: 1.55;
}

.nav-links a { color: var(--ink); transition: color .15s; }
.nav-sub a { color: var(--muted); }
.nav-links a:hover, .nav-sub a:hover { color: var(--accent-t); }

/* Where the reference sets its mission block. */
.nav-note {
  max-width: 21ch; margin-left: auto; align-self: flex-start;
  font-size: 10.5px; line-height: 1.5; color: var(--muted);
  max-height: calc(90px * (1 - var(--t)));
  opacity: calc(1 - var(--t) * 2);
  overflow: hidden;
}

/* ── NAV ICON ANIMATION ────────────────────────────────────────────
   Two marks that show what each product does rather than decorating it.
   Pure CSS on SVG geometry — no library, no rAF, nothing to schedule.

   LIVEcompanion: the playhead sweeps the clip grid and the strip along the
     top edge lights as it passes — the strip reading the running set.
   BEATref: the lower waveform compresses horizontally until its peaks line
     up with the reference above it — the stretch to your project tempo. */
@keyframes headSweep {
  0%          { transform: translateX(0);    opacity: 1; }
  70%, 88%    { transform: translateX(33px); opacity: 1; }
  100%        { transform: translateX(33px); opacity: 0; }
}
@keyframes stripLit {
  0%, 52% { opacity: .22; }
  68%     { opacity: 1; }
  100%    { opacity: .22; }
}
@keyframes refLock {
  0%       { transform: scaleX(1.16); opacity: .3; }
  62%,100% { transform: scaleX(1);    opacity: 1; }
}
@keyframes flapOpen {
  0%       { transform: scaleY(1); }
  50%      { transform: scaleY(.15); }
  100%     { transform: scaleY(1); }
}
.ico-head  { animation: headSweep 3.4s ease-in-out infinite; }
.ico-strip { opacity: .22; animation: stripLit 3.4s ease-in-out infinite; }
.ico-ref   { transform-origin: 3px 30px; animation: refLock 3.4s ease-in-out infinite; }
.ico-axis  { opacity: .28; }
.ico-flap  { transform-origin: 20px 9.5px; animation: flapOpen 3.4s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .ico-head, .ico-strip, .ico-ref, .ico-flap { animation: none; }
  .ico-strip { opacity: 1; }
}

/* ── HERO ── */
.hero { padding: clamp(56px, 11vw, 130px) 0 clamp(48px, 8vw, 96px); }
.hero-in { display: flex; align-items: center; gap: clamp(24px, 6vw, 72px); flex-wrap: wrap; }

/* The fox now lives in the nav, at the right, and shrinks with everything
   else. Its eyes keep the pupil tracking and the blink loop — the ids the
   script binds to (eyesSvg, pupil*Wrap, Iris-*, lid*) moved with it. */
.nav-fox {
  position: relative; flex-shrink: 0;
  align-self: center;
  margin-left: clamp(14px, 2vw, 30px);
  width: calc(62px - 24px * var(--t));
}
.nav-fox img { width: 100%; }
.nav-fox svg.eyes { position: absolute; inset: 0; width: 100%; height: 100%; }

.hero-txt { flex: 1; min-width: 280px; }

/* ── HEADLINE BLOCK ────────────────────────────────────────────────
   The reference's arrangement: a heavy uppercase headline set as tight as
   the face allows, a small line-art object hung off its right edge, and a
   data table beneath that object. The headline is the one place on the page
   that carries weight — everything else stays at 100–300, so this reads as
   deliberate rather than as a stray bold. */
/* The one orange element the reference allows itself, here doing real work:
   both products are in alpha and the badge is the call for testers. The
   indicator blinks on a steps(1) timing rather than fading — a hard on/off
   reads as an instrument light, a crossfade reads as decoration. */
.badge {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 13px;
  margin-bottom: clamp(20px, 3vw, 34px);
  border: 1px solid var(--ink);
  font-size: 11px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .1em;
  transition: background .15s, color .15s;
}
.badge b {
  font-weight: 500; color: var(--muted);
  margin-left: 10px; padding-left: 10px;
  border-left: 1px solid var(--rule);
}
.badge-dot { width: 8px; height: 8px; flex-shrink: 0; background: var(--accent); }
@keyframes rec { 0%, 58% { opacity: 1; } 59%, 100% { opacity: .12; } }
.badge-dot { animation: rec 1.7s steps(1) infinite; }
.badge:hover { background: var(--ink); color: var(--canvas); }
.badge:hover b { color: var(--graphite); border-left-color: var(--rule-dk); }
@media (prefers-reduced-motion: reduce) { .badge-dot { animation: none; } }

.head-row {
  display: flex; align-items: flex-start;
  gap: clamp(18px, 3vw, 40px);
  margin-bottom: 30px;
}
h1 {
  font-family: var(--font-display);
  font-size: clamp(38px, 7.2vw, 92px);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
  line-height: .92;
  text-transform: uppercase;
}
h1 i { font-style: normal; color: var(--accent); }

.head-side { padding-top: 6px; flex-shrink: 0; }
.tag { display: flex; align-items: center; gap: 8px; }
.tag svg {
  width: 46px; height: 34px; flex-shrink: 0;
  fill: none; stroke: var(--ink); stroke-width: 2.4;
  stroke-linecap: square; stroke-linejoin: miter;
}
.tag-t { font-size: 13px; font-weight: 500; letter-spacing: -.01em; }
.tag-t i { font-style: normal; color: var(--accent); }

/* Hairlines only — the same separator the rest of the page uses. */
.spec-list { margin-top: 14px; font-size: 12px; }
.spec-list > div {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 5px 0;
  border-top: 1px solid var(--rule);
}
.spec-list dt { color: var(--muted); }
.spec-list dd { text-align: right; }
.spec-list dd.hot { color: var(--accent-t); }
.lede { font-size: 19px; font-weight: 400; line-height: 1.15; max-width: 46ch; }
.meta {
  margin-top: 24px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .1em; font-size: 11px;
}

/* ── SCROLL STORY ─────────────────────────────────────────────────
   All eight frames are the same 2000px-wide capture with the strip at
   identical pixels, so the images are TOP-anchored inside a fixed
   aspect box: the strip never moves between frames and only the panel
   below it grows. That is what makes a crossfade read as the app
   changing rather than as two pictures swapping.
   The viz sticks; the empty .story-step spacers beside it in the same
   grid cell are what gives the section its scroll length. */
.story { position: relative; display: grid; grid-template-columns: 1fr; }
.story-viz, .story-steps { grid-area: 1 / 1; pointer-events: none; }

/* align-self:start is load-bearing — a grid item defaults to stretch, which
   would give the viz the full 7-screen track height and leave sticky nothing
   to do. It has to take its own content height to stick. */
.story-viz { position: sticky; top: var(--nav-h); align-self: start; padding: 24px 0; }

/* --story-ar / --story-arn are both written by build-index.py from ONE value
   in content.json ("story_ar"), so they cannot drift. The max-width is how the
   frame's height gets capped: an aspect-ratio box given a max-height keeps its
   full width and silently breaks its ratio instead, which on the taller
   BEATref frame pushed the caption off the bottom of the screen. */
.story-frames {
  position: relative;
  width: 100%;
  max-width: calc(58vh * var(--story-arn, 2.4096));
  margin-inline: auto;
  aspect-ratio: var(--story-ar, 2000 / 830);   /* tallest frame — shorter ones sit top-aligned */
  background: var(--carbon);
  border: 1px solid var(--rule);
  overflow: hidden;
}

/* ── The camera ──
   One transform over the whole frame stack, not per image. That is the
   difference between a tour and a slideshow: the crossfade and the move are
   independent, so the picture can change while the camera keeps travelling,
   and two consecutive beats that share a rect read as the app changing under
   a held shot.
   transform-origin 0 0 makes the maths trivial — translate the rect's corner
   to the origin, then scale by 1/width. The translate percentages resolve
   against the element's own box, which is exactly the frame. */
.story-cam {
  position: absolute; inset: 0;
  --zx: 0; --zy: 0; --zw: 1;
  transform-origin: 0 0;
  transform: scale(calc(1 / var(--zw)))
             translate(calc(var(--zx) * -100%), calc(var(--zy) * -100%));
  transition: transform 1.1s cubic-bezier(.33, .1, .25, 1);
  will-change: transform;
}
.story-frames img {
  position: absolute; top: 0; left: 0; width: 100%; height: auto;
  opacity: 0; transform: scale(1.008); transform-origin: 50% 0;
  transition: opacity .65s ease, transform .65s ease;
}
.story-frames img.on { opacity: 1; transform: none; }

.story-caps { position: relative; min-height: 136px; margin-top: 22px; }
.story-cap {
  position: absolute; inset: 0;
  opacity: 0; transform: translateY(6px);
  transition: opacity .4s ease, transform .4s ease;
}
.story-cap.on { opacity: 1; transform: none; }
.story-cap h3 { font-size: 26px; font-weight: 500; letter-spacing: -.02em; line-height: 1.15; margin: 10px 0; }
.story-cap p { max-width: 64ch; line-height: 1.6; }

.story-step { height: 100vh; }
.story-step:first-child { height: 62vh; }  /* frame 0 is already on screen */

/* ── CHAPTER: dark band ── */
.band { background: var(--carbon); color: var(--canvas); position: relative; }
/* The demo sits inside the content measure instead of bleeding to the edges.
   Full-bleed at 78vh with object-fit:cover made it the largest thing on the
   page by a wide margin — and cropping a 16:9 capture to a viewport-height
   box cut the plugin's own edges off. Contained, at its native ratio, it
   reads as one product's demo rather than as the site's subject. */
.band figure { max-width: var(--max); margin: 0 auto; padding: 40px var(--pad) 0; }
.band img, .band video { object-fit: contain; }
/* Below the image, not over it — the screenshots have their own dark chrome
   at the bottom edge and an overlaid label disappears into it. */
.band-label {
  /* Same measure as the figure above it, or the label sits flush to the
     viewport edge while the media is centred and they stop lining up. */
  display: block; max-width: var(--max); margin: 0 auto;
  padding: 15px var(--pad) 0;
  color: var(--graphite); text-transform: uppercase; letter-spacing: .1em; font-size: 11px;
}
.band-head { padding: 44px var(--pad); max-width: var(--max); margin: 0 auto; }
.band-head h2 { font-size: clamp(28px, 4.5vw, 40px); font-weight: 500; letter-spacing: -.02em; line-height: 1.11; }
.band-head p { color: var(--graphite); font-size: 19px; font-weight: 400; line-height: 1.2; max-width: 56ch; margin-top: 15px; }

/* The second way into a chapter: jump the scroll story and land on the spec
   grid. Sits under the chapter's opening sentence, i.e. BEFORE the story —
   below it, a reader would have to do the scrolling the link exists to save.
   --accent, not --accent-t: .chap-foot a is the same link in the same role,
   but on the light canvas, where the darkened variant is the readable one.
   This one sits on --carbon, so it takes the light variant. Same pattern,
   correct ground — not a new chromatic element (docs/relaunch-tokens.md). */
.skip {
  display: inline-block; margin-top: 22px;
  color: var(--accent); font-size: 15px;
  /* NOT --rule-dk: it is 1.27:1 on --carbon, i.e. no line at all, and the
     hover would read as an underline appearing from nothing rather than
     changing colour. Same reason --rule-dk came off .strip-shot above. */
  padding-bottom: 3px; border-bottom: 1px solid rgba(255, 107, 26, .45);
  transition: border-color .15s;
}
.skip:hover { border-bottom-color: var(--accent); }

/* ── The product shot: the strip, on its own ──
   16:1, because that IS the product's shape — a bar across the top of the
   screen. It sits in the measure like the .band figures above it.
   Two nested boxes: on a phone .strip-scroll scrolls and .strip-shot carries
   the gradient that says so — one box cannot do both, the gradient would
   travel with the image as it is swiped.

   CONTRAST, not decoration: the strip's UI is within a few points of --carbon,
   so the separation has to be built rather than found. Three moves, in order
   of how much each actually contributes:

     1. the plate — a pool of light on the band, so the dark strip has
        something lighter to sit against. This is the one doing the work.
     2. a bright rim — --rule-dk (#26252b) is barely above the band it
        separates; at this size the edge of the shot was effectively invisible.
     3. a deep shadow, so the lit plate reads as behind it rather than around it.

   Colour is deliberately NEUTRAL. docs/relaunch-tokens.md rations orange to
   the fox's eyes and the beta badge — "the reference allows itself exactly
   one chromatic element". Light lifts this shot further than tint does
   anyway, and costs nothing from that budget. */
.strip-shot {
  border: 1px solid rgba(255, 255, 255, .3);
  position: relative;
  box-shadow: 0 26px 64px -20px rgba(0, 0, 0, .95),
              0  0    0     1px rgba(255, 255, 255, .05),
              0  1px  0     0   rgba(255, 255, 255, .09) inset;
}
.strip-shot img { display: block; width: 100%; height: auto; }

/* The plate. Two overlapping soft washes that drift in opposite directions,
   so the pool of light never settles into a shape you can name.
   NOT z-index: -1 — .band creates no stacking context, so a negative child
   paints behind the band's own background and disappears. Instead both stay at
   auto z-index and PAINT ORDER does the work: .strip-scroll is positioned too
   (that is what its `position: relative` below is for, nothing else), and among
   positioned auto-z siblings the later one in the DOM wins. */
.strip-shot::before {
  /* Absolute insets, NOT percentages: the shot is 16:1, so a percentage that
     looks generous horizontally is a handful of pixels vertically — less than
     the blur radius, and the light never leaves the box. */
  content: ''; position: absolute; inset: -60px -40px;
  background:
    radial-gradient(50% 62% at 26% 50%, rgba(233, 238, 255, .34), transparent 70%),
    radial-gradient(44% 58% at 72% 50%, rgba(255, 255, 255, .26), transparent 72%);
  filter: blur(44px);
  pointer-events: none;
  animation: stripPlate 16s ease-in-out infinite;
}
.strip-scroll { position: relative; }

/* On the landing page the shot and the sentence below it are two sibling
   .bands, each with its own --carbon background. The later one paints over
   the plate and cuts it off at the seam with a hard horizontal line. Both are
   position: relative already, so one z-index settles the paint order.
   Stays under the sticky nav (z-index: 50). */
.band:has(.strip-shot) { z-index: 1; }

@keyframes stripPlate {
  0%, 100% { transform: translateX(-3%) scale(1);    opacity: .8; }
  50%      { transform: translateX( 3%) scale(1.09); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .strip-shot, .strip-shot::before { animation: none; }
}

/* .band-head's own 44px plus .band figure's 40px put 84px between the
   sentence and the picture it introduces, and at this image's height that
   reads as a gap rather than a pairing. Scoped to .band-product: .band-head
   is shared with the landing page and /beatref, where it stands alone and
   wants its full padding. */
.band-product figure { padding-top: 0; }

/* The kicker above a cell heading. Named .row-n from the .rows block that
   used to close the LIVEcompanion chapter; that block is gone (the six .spec
   cells replaced it) but the class is still what render_features and
   render_lc_features emit, so it outlived its namesake. The rest of .rows /
   .row went with the markup — no page emitted them any more, and CSS that
   selects nothing is this site's most-repeated bug, not dead weight worth
   keeping "just in case". */
.row-n { color: var(--muted); letter-spacing: .1em; font-size: 11px; }

/* ── SPEC GRID (both chapters, and /beatref) ── */
.specs { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--rule); }
.spec {
  padding: 32px var(--pad) 40px;
  border-left: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.spec:nth-child(3n+1) { border-left: 0; }
/* height:auto lets the viewBox aspect drive it, so all six diagrams render
   at the same scale and full column width. A fixed height letterboxes them. */
.spec svg { width: 100%; height: auto; margin-bottom: 22px; }
.spec .row-n { display: block; }
.spec h3 { font-size: 19px; font-weight: 500; letter-spacing: -.01em; line-height: 1.15; margin: 10px 0 10px; }
.spec p { line-height: 1.6; }

/* ── CHAPTER FOOT ── */
.chap-foot {
  display: flex; gap: 32px; flex-wrap: wrap; align-items: baseline;
  padding: 32px 0 clamp(48px, 8vw, 88px);
}
.chap-foot a { color: var(--accent-t); }
.chap-foot a:hover { text-decoration: underline; }
.chap-foot .note { color: var(--muted); margin-left: auto; text-transform: uppercase; letter-spacing: .1em; font-size: 11px; }

/* ── NEWSLETTER ── */
.news { border-top: 1px solid var(--rule); padding: clamp(48px, 8vw, 88px) 0; }
.news h2 { font-size: clamp(28px, 4.5vw, 40px); font-weight: 500; letter-spacing: -.02em; line-height: 1.11; }
.news p { color: var(--muted); font-size: 19px; font-weight: 400; margin-top: 12px; max-width: 42ch; }
.news-row { display: flex; margin-top: 32px; max-width: 460px; border: 1px solid var(--ink); }
.news-row input {
  flex: 1; min-width: 0; border: 0; outline: 0; background: transparent;
  font-family: inherit; font-size: 13px; font-weight: 400; color: var(--ink);
  padding: 14px 15px;
}
.news-row input::placeholder { color: var(--muted); }
.news-row button {
  border: 0; border-left: 1px solid var(--ink); background: var(--ink); color: var(--canvas);
  font-family: inherit; font-size: 13px; font-weight: 500; letter-spacing: .02em;
  padding: 14px 22px; cursor: pointer;
  display: flex; align-items: center; gap: 8px;
  transition: background .15s;
}
.news-row button:hover { background: var(--accent); }
.news-row .sib-hide-loader-icon { display: none; width: 13px; height: 13px; }
.news-row .sib-hide-loader-icon path { fill: currentColor; }
.news-err { display: block; min-height: 18px; margin-top: 8px; color: var(--accent-t); font-size: 11px; }
#error-message, #success-message {
  display: none; max-width: 460px; margin-top: 12px;
  padding: 11px 15px; border: 1px solid var(--rule); background: #fff; font-size: 11px;
}
#error-message { border-color: var(--accent); color: var(--accent-t); }

/* ── FOOTER ── */
footer { background: var(--carbon); color: var(--canvas); padding: 33px 0; }
.foot-in { max-width: var(--max); margin: 0 auto; padding: 0 var(--pad); display: flex; gap: 22px; flex-wrap: wrap; align-items: center; }
.foot-in a { color: var(--canvas); transition: color .15s; }
.foot-in a:hover { color: var(--accent); }
.foot-sep { color: var(--rule-dk); }
.foot-right { margin-left: auto; display: flex; align-items: center; gap: 12px; color: var(--graphite); }
.foot-right img { height: 18px; width: auto; opacity: .5; filter: saturate(0) brightness(3); transition: opacity .2s; }
.foot-right a:hover img { opacity: 1; }
/* Trademark line. Same max-width and padding as .foot-in — it is a second
   row of the same footer, not a separate band. --graphite, not --muted:
   this sits on the dark carbon band (relaunch-tokens.md). */
.foot-note {
  max-width: var(--max); margin: 18px auto 0; padding: 0 var(--pad);
  color: var(--graphite); font-size: 11px; line-height: 1.6;
}
.foot-note a { color: var(--graphite); text-decoration: underline; }
.foot-note a:hover { color: var(--accent); }

/* ── SCROLL REVEAL ── */
.rv { opacity: 0; transform: translateY(8px); transition: opacity .5s ease, transform .5s ease; }
.rv.in { opacity: 1; transform: none; }

/* ── RESPONSIVE ── */
@media (max-width: 860px) {
  .specs { grid-template-columns: 1fr 1fr; }
  .spec:nth-child(3n+1) { border-left: 1px solid var(--rule); }
  .spec:nth-child(2n+1) { border-left: 0; }
}
@media (max-width: 760px) {
  /* Full-bleed the frames — a DAW screenshot needs every pixel it can get. */
  /* max-width has to go too, or the height cap keeps the frame narrow and
     "full-bleed" quietly becomes "centred with gutters". */
  .story-frames {
    max-width: none;
    /* width:auto is what actually makes this full-bleed, and leaving it out is
       why the frame sat off-centre on every phone: the base rule sets
       width:100%, a percentage resolves against the containing block, and a
       negative margin does not widen a box that has already been given a
       width — it only shifts it. So the left margin pulled the frame to the
       viewport edge, the right margin moved nothing, and the frame ended one
       --pad (44px at 390px) short on the right, with the caption below it
       still correctly inset. Full-bleed on one side reads as "not centred",
       which is exactly what it was. */
    width: auto;
    margin: 0 calc(-1 * var(--pad)); border-left: 0; border-right: 0;
  }
  .story-step { height: 84vh; }
  .story-cap h3 { font-size: 21px; }
}
@media (max-width: 620px) {
  .specs { grid-template-columns: 1fr; }
  .spec { border-left: 0 !important; }

  /* ── The strip on a phone ──
     Scaled to fit, a 16:1 bar is 19px tall. So it keeps a readable height and
     runs off the side, and the reader swipes it — the strip genuinely IS
     wider than a telephone, and pretending otherwise costs every field the
     story is about to spend a beat on.
     width:auto is load-bearing: the global `img { width: 100% }` would
     otherwise squash 2000x121 into the measure at 76px tall — distorted
     rather than clipped, which reads as a bad screenshot, not a bad rule.
     overflow-x lives on this box alone; the page body must never scroll
     sideways. */
  .strip-scroll { overflow-x: auto; overscroll-behavior-x: contain; }
  .strip-shot img { width: auto; height: 76px; }

  /* The hint that there is more to the right. On .strip-shot, which does not
     scroll — put it on .strip-scroll and it travels with the image, so it
     stops marking the edge the moment it is needed. */
  /* (position: relative now lives on the base rule, for the plate.)
     z-index: 2 is NOT required today — ::after is the last positioned auto-z
     sibling, so it already paints on top. It is here against the future edit
     that gives .strip-scroll a real z-index for some other reason, which would
     otherwise drop this fade under the image it exists to fade. */
  .strip-shot::after {
    content: ''; position: absolute; top: 0; right: 0; bottom: 0; width: 56px;
    background: linear-gradient(to right, rgba(15, 14, 18, 0), var(--carbon));
    pointer-events: none; z-index: 2;
  }

  /* ── The phone nav ──
     This block replaces `.nav-links a[data-opt] { display: none }`, which was
     the intended fix and never ran: render_nav() emits no data-opt anywhere,
     so the selector matched nothing. A selector that matches nothing is not an
     error, so the sub-links stayed visible on phones and nobody was told.

     What that cost, measured at 390px before this block existed:

       expanded   290px  — 33% of the screen was navigation
       COLLAPSED   92px  — and --nav-h is 66px

     The second number is the real damage. `.story-viz` sticks at
     `top: var(--nav-h)`, so every scroll story ran 26px underneath the bar on
     every phone, several screens below the fold, looking like a story problem
     rather than a nav one. See docs/relaunch-tokens.md, coupling 1.

     The bar has to physically FIT in --nav-h at t=1, and it only does once
     the sub-links and the note are gone: three 15px labels then wrap to two
     short rows inside 66px instead of three tall ones. Re-measure the
     collapsed height if you add anything back here. */
  /* --nav-h-max is a desktop number. With the sub-links, the note and the
     wordmark gone, the bar's own content at rest is 102px, and 210px left
     more than half the bar empty. Still a min-height, so a narrower phone
     that wraps the labels pushes the bar taller rather than clipping it
     (docs/relaunch-tokens.md, coupling 3). --nav-h is NOT touched: that is
     the collapsed height, and .story-viz sticks to it. */
  :root { --nav-h-max: 108px; }

  .nav-sub { display: none; }
  /* Squeezed to zero width by the time the viewport is this narrow — but it
     kept reserving its 90px of height, so it was pushing the bar down while
     rendering nothing at all. */
  .nav-note { display: none; }

  /* .nav-top is white-space: nowrap, so .nav-links cannot shrink below the
     widest label. At the desktop sizes that min-content width plus the
     wordmark plus the fox came to more than the viewport, and the fox — last
     in the row and flex-shrink: 0 — was pushed 18px off the right edge. */
  /* On a phone the mark is the fox, not the wordmark. Both are links to "/",
     so nothing is lost, and dropping the text is what buys the three product
     labels enough room to sit on one line instead of wrapping. */
  .nav-mark { display: none; }
  /* margin-left:auto keeps it on the right edge now that .nav-note — which
     used to be the auto-margin element pushing it there — is gone too. */
  .nav-fox { width: calc(46px - 14px * var(--t)); margin-left: auto; }
  .nav-top { font-size: calc(17px + (15px - 17px) * var(--t)); }
  .nav-ico { width: calc(26px * (1 - var(--t))); height: calc(26px * (1 - var(--t))); }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .rv { opacity: 1; transform: none; transition: none; }
  /* The story still needs opacity to switch frames — cut the easing, not the swap. */
  .story-frames img, .story-cap { transition-duration: .01ms; transform: none; }
  /* The camera keeps its transform: the rect is what a beat is *about*, so
     removing it would show the wrong part of the frame rather than a calmer
     version of the right one. Only the travel goes. */
  .story-cam { transition-duration: .01ms; }
}

/* ── ABOUT ────────────────────────────────────────────────────────────
   Portrait beside the text, ruled off from the newsletter below it. The
   photo is capped rather than full-bleed: it is the only human image on
   the page and at full width it outweighs both products. */
.about { border-top: 1px solid var(--rule); padding: clamp(44px, 7vw, 92px) 0; }
.about-in {
  display: grid;
  grid-template-columns: minmax(180px, 300px) 1fr;
  gap: clamp(24px, 5vw, 64px);
  align-items: start;
}
.about-pic { border: 1px solid var(--rule); background: #eceeed; }
.about-pic img { filter: grayscale(1) contrast(1.04); }
.about-txt h2 {
  font-size: clamp(24px, 3.4vw, 36px);
  font-weight: 500; letter-spacing: -.02em; line-height: 1.1;
  margin: 11px 0 18px;
}
.about-txt p { max-width: 56ch; line-height: 1.65; margin-bottom: 13px; }
.about-links { display: flex; flex-wrap: wrap; gap: 22px; margin-top: 22px; }
.about-links a {
  font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); transition: color .15s;
}
.about-links a:hover { color: var(--accent-t); }

@media (max-width: 620px) {
  .about-in { grid-template-columns: 1fr; }
  .about-pic { max-width: 260px; }
}

/* ── SUBPAGE: SECTION HEAD + CTA ──────────────────────────────────────
   Used by /beatref, which shares this sheet with the landing page rather
   than carrying a second design of its own. */
.feat-head { padding: clamp(40px, 6vw, 76px) 0 clamp(24px, 3vw, 36px); max-width: 62ch; }
.feat-head h2 {
  font-size: clamp(24px, 3.4vw, 36px);
  font-weight: 500; letter-spacing: -.02em; line-height: 1.12;
  margin: 11px 0 16px;
}
.feat-head p { color: var(--muted); line-height: 1.65; }

.cta { border-top: 1px solid var(--rule); padding: clamp(48px, 8vw, 96px) 0; }
.cta h2 {
  font-size: clamp(26px, 4vw, 42px);
  font-weight: 500; letter-spacing: -.025em; line-height: 1.08;
}
.cta p { max-width: 52ch; margin: 14px 0 26px; line-height: 1.65; color: var(--muted); }
.cta-link {
  display: inline-block;
  padding: 11px 20px;
  border: 1px solid var(--ink);
  font-size: 12px; text-transform: uppercase; letter-spacing: .1em;
  transition: background .15s, color .15s;
}
.cta-link:hover { background: var(--ink); color: var(--canvas); }

/* ── LIVEcompanion FEATURE ROWS ───────────────────────────────────────
   Text and screenshot alternate sides. Rows whose feature has no
   screenshot yet (the source ships a placeholder for six of them) drop to
   a single column rather than leaving an empty frame. */
.lc-feat {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(22px, 4vw, 60px);
  align-items: center;
  padding: clamp(30px, 4.5vw, 56px) 0;
  border-bottom: 1px solid var(--rule);
}
.lc-feat:first-child { border-top: 1px solid var(--rule); }
.lc-feat.reverse .lc-txt { order: 2; }
.lc-feat.textonly { grid-template-columns: 1fr; }
.lc-feat.textonly .lc-txt { max-width: 72ch; }
.lc-txt h3 {
  font-size: clamp(20px, 2.2vw, 26px);
  font-weight: 500; letter-spacing: -.02em; line-height: 1.15;
  margin: 9px 0 12px;
}
.lc-txt p { max-width: 52ch; line-height: 1.65; margin-bottom: 11px; }
.feat-list { list-style: none; margin-top: 14px; }
.feat-list li {
  position: relative;
  padding-left: 17px;
  margin-bottom: 5px;
  color: var(--muted); line-height: 1.55;
}
/* A square, not a bullet — round dots are the one shape this register
   does not use. */
.feat-list li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 5px; height: 5px; background: var(--rule);
}
.lc-pic { background: #eceeed; border: 1px solid var(--rule); }

@media (max-width: 720px) {
  .lc-feat, .lc-feat.reverse { grid-template-columns: 1fr; }
  .lc-feat.reverse .lc-txt { order: 0; }
}

/* ══ LEGAL PAGES (imprint.html, impressum.html) ══════════════════════════
   Two hand-written pages, not generated — they carry no nav and no story, so
   they take the tokens and nothing else. Kept here rather than inline in each
   page so the two language versions cannot drift apart. */
.legal-top {
  display: flex; align-items: baseline; gap: 22px; flex-wrap: wrap;
  padding: 26px 0; border-bottom: 1px solid var(--rule);
  font-size: 12px; text-transform: uppercase; letter-spacing: .1em;
}
.legal-top .lang { margin-left: auto; display: flex; gap: 12px; }
.legal-top a { color: var(--muted); }
.legal-top a:hover { color: var(--ink); }
.legal-top a[aria-current] { color: var(--accent-t); }

.legal { max-width: 640px; font-size: 14px; padding: 56px 0 88px; }
.legal h1 {
  font-family: var(--font-display);
  font-weight: var(--display-weight); letter-spacing: var(--display-track);
  font-size: clamp(34px, 6vw, 54px); line-height: 1; margin-bottom: 10px;
}
.legal .updated {
  color: var(--muted); text-transform: uppercase; letter-spacing: .1em;
  font-size: 11px; margin-bottom: 48px;
}
.legal section { border-top: 1px solid var(--rule); padding: 22px 0; }
.legal h2 {
  font-size: 11px; font-weight: 500; text-transform: uppercase;
  letter-spacing: .12em; color: var(--accent-t); margin-bottom: 10px;
}
.legal h3 { font-size: 14px; font-weight: 500; margin: 16px 0 6px; }
.legal p { line-height: 1.7; margin-bottom: 10px; }
.legal p:last-child { margin-bottom: 0; }
.legal address { font-style: normal; line-height: 1.7; }
.legal a:not(.legal-top a) { border-bottom: 1px solid var(--rule); }
.legal a:hover { color: var(--accent-t); border-color: currentColor; }
.legal ul { list-style: none; margin: 8px 0 0; }
.legal li {
  position: relative; padding-left: 17px; margin-bottom: 5px;
  color: var(--muted); line-height: 1.6;
}
.legal li::before {
  content: ""; position: absolute; left: 0; top: .6em;
  width: 5px; height: 5px; background: var(--rule);
}
.legal .fine { color: var(--muted); font-size: 12px; line-height: 1.65; }
