/* ============================================================
   AUDIBLE GOODS — marketing site

   Everything structural comes from tokens.css. This file only adds
   the handful of arrangements the site needs on top of the system:
   a masthead, a hero, the product screens, and a footer.

   If a rule here starts redefining colour or type, it belongs in
   tokens.css instead.

   The home page reads a screen at a time. Two mechanisms do that, and
   they are deliberately independent:

     SNAP     is pure CSS — scroll-snap on the document scroller. It is
              switched on only at 901px wide and 680px tall or more, because
              a mandatory snap point in front of a section too tall for the
              viewport makes the bottom of that section unreachable.
              Off, the page is an ordinary document; nothing else changes.

     SETTLE   is the lag. motion.js writes one value, --lag, and anything
              carrying data-depth is displaced by lag x depth, so the
              artwork trails further than the type and lands after it.
              With scripting off --lag never exists and every transform
              resolves to zero.
   ============================================================ */

/* The ?v= matches the one on this file in every page's <link>. See the
   note there; bump both together. */
@import url("./tokens.css?v=17");

/* ============================================================
   FULL-PAGE SCROLL
   ============================================================ */

html{scroll-behavior:smooth}

/* The masthead is fixed, so every screen reserves its height. motion.js
   measures the real thing and overwrites this; the value here is the
   one-row desktop height and only has to hold until scripts run. */
:root{--mast-h:65px}

/* The width here is not a taste call: it is the same 900px at which the
   showcase drops to one column. Stacked, a product screen is ~950px tall,
   and a mandatory snap point in front of a section taller than the
   viewport makes the bottom of that section unreachable — you release the
   wheel and it snaps back to the top of the screen you were trying to
   read past. Measured at 820x900: Saturn 945px, Polychrome 990px. So
   full-page scroll is on exactly where the two-column layout holds, and
   the query below must move if that breakpoint ever does. */
@media (min-width:901px) and (min-height:680px){
  /* scroll-behavior:auto is not a preference here, it is load-bearing.
     A mandatory snap container and a smooth programmatic scroll fight:
     an in-page jump either aborts and returns to where it started, or
     runs its animation and stops ~130px short of the snap point, which
     leaves the heading under the masthead and never re-snaps. Measured
     in this engine, both ways. Handing the animation entirely to snap
     fixes it — the jump is instant and the settle below plays the
     content in, which is a better arrival than a scroll tween anyway.

     It is scoped to exactly the query that turns snapping on, so the
     two cannot drift apart: wherever the page is an ordinary document
     the smooth scroll above still applies. */
  html.js[data-page="home"]{scroll-snap-type:y mandatory;scroll-behavior:auto}
  html.js[data-page="home"] .snap{scroll-snap-align:start;scroll-snap-stop:always}
}

.snap{
  position:relative;
  min-height:100vh;
  min-height:100svh;
  display:flex;flex-direction:column;justify-content:center;
  padding-block:calc(var(--mast-h) + var(--sp-7)) var(--sp-7);
}
/* The hairline between screens is the datasheet rule the old page used
   between sections. At a snap point it sits under the masthead and is
   invisible; it is there for the travel in between, and for every
   viewport where snapping is switched off. */
.snap + .snap{border-top:var(--ag-hair) solid var(--ag-rule)}

/* A zero-size target so an old ../#products link still lands somewhere
   sensible now that the two products no longer share a section. */
.anchor-alias{position:absolute;top:0;left:0;width:0;height:0;
  scroll-margin-top:var(--mast-h)}

/* ---------- the settle ------------------------------------------------

   ONE transform, driven by --lag, and it is the only thing allowed to
   write `transform` on these elements. The reveal underneath uses the
   independent `translate` property precisely so the two compose instead
   of overwriting each other — swapping either one for the other breaks
   both. --depth and --d are stamped on by motion.js from the single
   data-depth attribute in the markup. */

[data-depth]{
  transform:translate3d(0,calc(var(--lag,0px) * var(--depth,0)),0);
}
.showcase-art{will-change:transform}

html.js .reveal [data-depth]{
  translate:0 22px;opacity:0;
  transition:translate var(--ag-settle) var(--ag-ease) var(--d,0ms),
             opacity   var(--ag-settle) var(--ag-ease) var(--d,0ms);
}
html.js .reveal.is-in [data-depth]{translate:0 0;opacity:1}

/* Reduced motion takes all of it: no snap, no lag, no reveal. The
   selectors match the ones above rather than out-weighing them, so
   source order does the work. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  html.js[data-page="home"]{scroll-snap-type:none}
  [data-depth]{transform:none}
  html.js .reveal [data-depth]{translate:0 0;opacity:1;transition:none}
}

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

.skip{
  position:absolute;left:-9999px;top:0;z-index:20;
  font-family:var(--ag-label);font-size:var(--t-label);letter-spacing:.14em;
  text-transform:uppercase;text-decoration:none;
  background:var(--ag-fg);color:var(--ag-on-fg);padding:12px 18px;
}
.skip:focus{left:var(--sp-4);top:var(--sp-4)}

/* ---------- masthead ------------------------------------------------- */

/* Fixed, because with a snapping page there is no top of the document to
   scroll back to. Transparent over the first screen and picking up its
   ground once the page has moved — motion.js toggles .is-scrolled. */
.masthead{
  position:fixed;inset-inline:0;top:0;z-index:10;
  border-bottom:var(--ag-hair) solid transparent;
  transition:background var(--ag-slow) var(--ag-ease),
             border-color var(--ag-slow) var(--ag-ease);
}
.masthead.is-scrolled{background:var(--ag-bg);border-bottom-color:var(--ag-rule)}
/* Product pages are ordinary documents that begin under the masthead. */
html:not(.js) .masthead,body:not(.home) .masthead{background:var(--ag-bg);
  border-bottom-color:var(--ag-rule)}

body:not(.home) #main{padding-top:calc(var(--mast-h) + var(--sp-6))}

/* Once it has a ground it is covering the sheet, so it lays the sheet back
   down. Same two tiles, same size, and both are anchored to the viewport —
   the masthead is fixed at top:0 and body's pair is fixed to the same
   origin — so the dots run straight through the bottom rule instead of the
   band reading as a patch of different stock.

   Absolute rather than fixed: a fixed layer in here would be the full
   viewport tall and paint down the page. And they need the -1. An
   absolutely positioned child paints AFTER its parent's in-flow content, so
   at the default z-index these would lay dots over the lockup and the nav
   rather than behind them. .masthead is already a stacking context (fixed,
   z-index 10), so -1 puts them exactly where body's pair sits relative to
   the page: above the ground, below everything printed on it.

   They fade rather than switch, and they are declared on .masthead itself
   rather than on .is-scrolled, because the ground under them is a 280ms
   transition. Appearing instantly over a ground still fading in would put
   this tile on top of the page's own for a fifth of a second — 3.5%
   coverage in one band, which reads as a smear crossing the masthead every
   time the page leaves the first screen. Same duration, same easing, so
   stock and ground arrive together. */
.masthead::before,.masthead::after{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  opacity:0;transition:opacity var(--ag-slow) var(--ag-ease);
  -webkit-mask-size:var(--ag-fleck-tile) var(--ag-fleck-tile);
          mask-size:var(--ag-fleck-tile) var(--ag-fleck-tile);
  -webkit-mask-repeat:repeat;
          mask-repeat:repeat;
}
.masthead::before{
  background:var(--ag-fleck-hi);
  -webkit-mask-image:var(--ag-fleck-a);
          mask-image:var(--ag-fleck-a);
}
.masthead::after{
  background:var(--ag-fleck-lo);
  -webkit-mask-image:var(--ag-fleck-b);
          mask-image:var(--ag-fleck-b);
}
/* On exactly the three selectors above that give the masthead a ground. */
.masthead.is-scrolled::before,.masthead.is-scrolled::after,
html:not(.js) .masthead::before,html:not(.js) .masthead::after,
body:not(.home) .masthead::before,body:not(.home) .masthead::after{opacity:1}

@media print{.masthead::before,.masthead::after{display:none}}
@media (forced-colors:active){.masthead::before,.masthead::after{display:none}}

.masthead .ag-wrap{
  display:flex;align-items:center;justify-content:space-between;
  gap:var(--sp-5);padding-block:var(--sp-4);flex-wrap:wrap;
}
.brand-link{display:inline-flex;text-decoration:none;line-height:0}
/* 32px, not 28: at 28 the lockup lands at 114.5px wide, which is exactly the
   mark's minimum and leaves no headroom. 32px puts it at 131px. */
.brand-link img{height:32px;width:auto}

.nav{display:flex;align-items:center;gap:var(--sp-5);flex-wrap:wrap}
.nav a{
  font-family:var(--ag-label);font-size:var(--t-label);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;text-indent:.14em;
  text-decoration:none;color:var(--ag-fg-muted);
  transition:color var(--ag-fast) var(--ag-ease);
}
.nav a:hover{color:var(--ag-fg)}


/* ---------- the section rail ------------------------------------------

   A page that moves a screen at a time has to say where you are and how
   much is left. Engraved ticks rather than dots, and the label only when
   you are on it or pointing at it, so the rail stays a margin note. */

.rail{
  position:fixed;z-index:9;
  right:max(var(--sp-5),env(safe-area-inset-right));
  top:50%;margin-top:0;translate:0 -50%;
  display:flex;flex-direction:column;align-items:flex-end;gap:var(--sp-4);
}
.rail a{
  display:flex;flex-direction:row-reverse;align-items:center;gap:var(--sp-3);
  text-decoration:none;color:var(--ag-fg-faint);
  transition:color var(--ag-fast) var(--ag-ease);
}
.rail a:hover,.rail a[aria-current]{color:var(--ag-fg)}
.rail-tick{
  display:block;width:16px;height:var(--ag-hair);background:currentColor;
  transition:width var(--ag-slow) var(--ag-ease);
}
.rail a[aria-current] .rail-tick{width:34px}
.rail-name{
  font-family:var(--ag-label);font-size:var(--t-micro);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;
  opacity:0;translate:8px 0;
  transition:opacity var(--ag-slow) var(--ag-ease),
             translate var(--ag-slow) var(--ag-ease);
}
.rail a:hover .rail-name,
.rail a:focus-visible .rail-name,
.rail a[aria-current] .rail-name{opacity:1;translate:0 0}
/* Two reasons to drop it, and they need different widths. Below 1080px
   the rail would sit on top of the content; below the snap query there
   are no screens for it to index at all. The height here is the inverse
   of the snap query's and has to move with it. */
@media (max-width:1080px),(max-height:679px){.rail{display:none}}

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

/* One column. The mark used to sit on the right, which squeezed the plate
   to a width that broke the headline over three lines; the headline is the
   hero, so the mark gave way rather than the type. */
.hero-grid{display:grid;gap:var(--sp-8);width:100%}
/* The headline sits in the plate — the mark's old black field, borrowed
   back as a container. It inverts with the theme like every other plate:
   a black rectangle on paper, a paper one on ink, because a black panel
   on the dark ground would not be a panel at all.

   It is sized off ONE number: --plate-scale multiplies the type, the
   padding, the gutter and the mark together, so resizing the panel cannot
   quietly change its proportions — four values nudged by hand drift the
   moment one of them is edited alone. */
.hero .hero-plate{
  --plate-scale:.8;
  margin-block:var(--sp-4);
  padding:calc(var(--sp-5) * var(--plate-scale)) calc(var(--sp-6) * var(--plate-scale));
  display:flex;align-items:center;justify-content:space-between;
  gap:calc(var(--sp-7) * var(--plate-scale));
}
/* Held to a measure that breaks the line in two and balances the halves.
   The measure is in ch, so it tracks the size down and the break holds —
   which is why --plate-scale can move without re-breaking the headline. */
.hero .hero-plate .ag-display{
  margin:0;max-width:17ch;text-wrap:balance;
  font-size:calc(var(--t-display) * .8 * var(--plate-scale));
}
/* Sizing only — never `display`, which would out-specify .only-light /
   .only-dark and un-hide the wrong artwork. See the note further down. */
.hero-plate-mark{flex:0 0 auto}
.hero-plate-mark img{width:100%;max-width:calc(160px * var(--plate-scale));height:auto}

.scroll-hint{
  position:absolute;left:50%;bottom:var(--sp-6);translate:-50% 0;
  display:inline-flex;flex-direction:column;align-items:center;gap:var(--sp-3);
  font-family:var(--ag-label);font-size:var(--t-micro);font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;text-indent:.16em;
  color:var(--ag-fg-faint);text-decoration:none;
  transition:color var(--ag-fast) var(--ag-ease);
}
.scroll-hint:hover{color:var(--ag-fg)}
.scroll-hint-rule{
  display:block;position:relative;overflow:hidden;
  width:var(--ag-hair);height:36px;background:var(--ag-rule-strong);
}
.scroll-hint-rule::after{
  content:"";position:absolute;inset-inline:0;height:14px;
  background:currentColor;animation:hint 2.1s var(--ag-ease) infinite;
}
@keyframes hint{0%{top:-14px}55%,100%{top:36px}}
/* The hint promises that one flick moves one screen, so it may only
   appear where that is true. This is the exact inverse of the snap query
   above; the two move together. */
@media (max-width:900px),(max-height:679px),(prefers-reduced-motion:reduce){
  .scroll-hint{display:none}
}

@media (max-width:820px){
  .hero-grid{gap:var(--sp-6)}
  /* No room beside the headline at this width; the mark is decorative and
     the page already carries it in the masthead. */
  .hero-plate-mark{display:none}
}

/* ============================================================
   PHOTOGRAPHIC PLATES

   The first photographs on the site, and they are placed on the terms
   tokens.css already set: screened, never placed flat. Everything about
   the groove — its ruling, its trough, the two inks, the pre-squeeze
   — belongs to .ag-halftone. This section only says where the plates go
   and how they are printed, which is the placement decision.

   HOW HEAVILY THEY PRINT is set on --ht-in and never with opacity,
   because --ht-in is the knob tokens.css points at for a photograph that
   needs its own correction and because it is the true one: a press
   lightens a picture by carrying a thinner line, not by watering the ink.

   THE PRODUCT BAND IS MASKED rather than cropped at its lower edge. A
   plate that stopped on a hard line in open paper would be a pasted
   rectangle; thinned out with a gradient it dissolves into the stock,
   and because the mask takes the SCREEN's output it thins symmetrically
   in both themes — which the alternative of baking the fade into the
   JPEG does not. Inverted for ink, a white fade in the file is a black
   one, and a black one at this end of the wedge is 92% coverage: a solid
   bar exactly where the photograph was meant to disappear.

   THE HOME BAND IS CROPPED, on both edges, and being pinned is what
   earns it that: the top has the masthead's line to meet and the bottom
   has the copy sitting close underneath. An edge that lands on something
   else is a cut. It is only an edge floating in open paper that needs a
   fade to explain it.

   They carry no data-depth on purpose. The settle is the printing
   arriving on the page; a plate is what it arrives ON, and stock that
   holds still while the printing moves is the same reading the flecked
   ground already takes. It is also what lets them sit on a layer of
   their own and never be redrawn — see the note on promotion below.
   ============================================================ */

/* ONE HEIGHT FOR EVERY BAND, AND IT IS vw SO THE CROP DOES NOT MOVE.
   Each picture is cut for the box it fills, and object-fit only ever
   crops further — so a height that did not track the width would
   re-frame the photograph at every viewport, and no crop chosen here
   would mean anything. Every band is cut at 9:1 in plates.swift, and 1/9
   is 11.1%. Change a cut there and this has to move with it.

   THERE WERE TWO OF THESE until the home band was re-cut. It ran at
   6.6:1 against the product pages' 9:1, and carried a height of its own
   at 15.2vw, from when it was three pictures side by side and needed the
   room to hold them. One photograph does not, and a second variable that
   has to agree with the first is a second thing to get wrong.

   The clamp only takes over at the ends, where a band would otherwise be
   a sliver or a wall, and those are the only widths at which any of this
   is cropped at all. The floor costs about a seventh of the width off
   each end on a 390px phone, which is the edge of the frame and not the
   subject. What it buys is presence, not detail. */
:root{
  --band-h:clamp(56px,11.1vw,180px);    /* every band — 9:1 */
}

.plates,.plate-band{pointer-events:none}

/* THEY ARE PROMOTED, and this is the one performance note in the file.
   The screen is filter(contrast 60) over a hard-light groove ramp, and a
   filter is re-run whenever its element repaints. Left in the normal
   paint order, the band repainted at every scroll offset and took the
   product page from 122fps to 20 — measured, not guessed, and visible as
   a stutter in the illustration beside it, because motion.js writes --lag
   once per frame and there were six times fewer frames to write it on.
   On its own layer the screen is rasterised once and scrolling only moves
   it. The plates never change after load, so the layer is never redrawn:
   this is the case will-change is actually for. */
.plates .ag-halftone,.plate-band{will-change:transform}
/* ALL FOUR KNOBS are set ON the screened element and never on a box
   around it. .ag-halftone declares them itself, and a declaration on an
   element beats one inherited from its parent — so the obvious place,
   .plates, is the one place they do nothing at all. There is no warning
   when that happens: the plate simply prints at the default correction,
   which is full strength and several times the ink either of these
   wants, from a centre in the middle of the band, which is a bullseye.

   ONE CORRECTION SERVES BOTH, because neither band has type on it. The
   product page's band began as a watermark BEHIND its hero and needed a
   second, much lighter correction to survive that — a heading, a tagline,
   a lead and a button were printed straight over it. Both bands now take
   a band of the page to themselves, so both can hold a real tonal range
   and read as photographs rather than as grounds.

   The correction raises brightness to REMOVE ink, in both themes. In the
   light theme that is obvious — a brighter photograph lands further up
   the wedge, where coverage is low. In the dark theme it still holds,
   because --ht-polarity inverts the photograph BEFORE --ht-in reaches
   it: the same brightness lifts the inverted frame up the same wedge and
   spends less paper-coloured ink. The mirror survives the correction,
   which is the only reason one pair of numbers can serve both grounds.
   Measured on a 21-step wedge at the ruling and correction below:

     in    0   10   20   30   40   50   60   70   80   90  100
     ink  59   54   48   40   27   20   14    7    4    2    2

   THE CENTRE SITS BELOW THE BAND, and at this aspect that is the whole
   geometry. Every band is 9:1, home page and product page alike. Put
   the centre inside one and it is a bullseye with the rest of the
   picture running off sideways; put it far out to one SIDE and the
   grooves reach the far edge nearly parallel, which reads as corduroy.
   Below — 260% of the band's own height — the arcs bend gently across
   the full width and are still arcs at both ends. Both are percentages
   of the box, so the curvature is the same on a phone as on a desktop
   and only --ht-cell is absolute.

   ONE SWEEP FOR THE WHOLE HOME BAND, which is why there is nothing here
   for the home page specifically: one rule, one centre, both bands. It
   was load-bearing while home-band.jpg was three photographs butted
   together with no gutter — a screen restarted at each seam laid a
   visible discontinuity at 33% and 66%, which turned a composite that
   went to real trouble to read as ONE plate back into three pasted
   tiles. The band is one photograph now and has no seams to give away,
   so what is left is the plain reason: both bands want the same
   screen. */
.plates .ag-halftone,.plate-band.ag-halftone{
  --ht-cell:8px;
  --ht-cx:30%;
  --ht-cy:260%;
  --ht-in:contrast(.72) brightness(1.33);
}
/* ---------- the home page's ------------------------------------------

   One band across the top of the first screen: the desk all three
   products are for, cut at 9:1 by plates.swift. The picture is IN THE
   FILE and is not built here out of elements — it was three thirds in a
   three-cell grid once, and against a stale stylesheet they stacked
   vertically at full width, one under another. There is nothing here to
   get wrong now but a height.

   IT IS IN FLOW AND PINNED TO THE TOP, and both halves matter. Type on
   this page is set on paper, never on a photograph, and the early
   versions broke that the same way twice: laid over the hero absolutely,
   at whatever height looked right on a 900px screen, the band ran
   straight across the eyebrow above the headline on a 640px one. An
   absolute height is only ever a guess at (available - content) / 2, a
   number that moves with the viewport and with however the lead
   paragraph happens to wrap.

   So nothing here is positioned. The hero starts its flex column at
   flex-start and the band is the first item, which is what puts the band
   at the top of the screen; the negative margin then takes back the
   --sp-7 the snap padding holds open under the masthead, so the picture
   begins on the masthead's own line the way a product page's band does.
   The copy is the second item and takes margin-bottom:auto, so it
   follows the band at the padding's distance and the screen's slack all
   falls to the BOTTOM, under the scroll hint. It was margin-block:auto,
   centred in what the band left over, and centring put 167px between the
   picture and the eyebrow on a 900px screen — most of a band's height of
   empty paper, for nothing.

   CENTRED AS A PAIR is what this was, and the band's height came out of
   the copy's share of the screen: the type sat low, near the bottom
   edge, with the picture floating above it. Where the screen is too
   short to hold both, the shrink factors still say what gives — the band
   may shrink to nothing, the copy never shrinks at all — though it takes
   the bound below to let them say it. Padding rather
   than a margin is part of the same thing: a margin would survive that
   shrink and hold 64px of dead air open after the picture it was spacing
   had gone.

   BOTH EDGES ARE SQUARE, and there is no mask on this band at all.
   .ag-halftone takes height:100%, which resolves against the CONTENT
   box, so the picture ends exactly where the padding begins — and that
   padding is the whole of the gap down to the copy, which is the one
   number to turn if the two ever sit too close or too far apart. */
.hero{justify-content:flex-start}
.hero .plates{
  box-sizing:border-box;
  flex:0 1 auto;height:calc(var(--band-h) + var(--sp-8));min-height:0;
  /* The snap padding holds --sp-7 open under the masthead; this takes it
     back, so the picture starts on the masthead's own line. It is the
     masthead's number and not the gap below, which is why the two are
     different tokens even when they happen to be near each other. */
  margin-top:calc(var(--sp-7) * -1);
  padding-bottom:var(--sp-8);
  overflow:hidden;
}
/* The screened element fills what the padding leaves. It is kept separate
   from .plates rather than folded into it because .ag-halftone lays its
   grooves over the WHOLE element: given the padding too, it would print
   arcs onto the empty air below the picture. */
.hero .plates > .ag-halftone{height:100%}

/* The copy is what the screen is for. It does not give up height so a
   photograph can keep it, and the ONE auto margin is what sends the
   screen's slack to the bottom instead of splitting it above and below. */
.hero .hero-grid{flex:0 0 auto;margin-bottom:auto}

/* A DEFINITE HEIGHT IS WHAT MAKES THE SHRINK REAL. Without it the note
   above is a promise the layout does not keep: .snap sets min-height, so
   the column GROWS past the viewport instead of producing the negative
   free space a shrink factor needs, and the band never gives up a pixel.
   Measured at 1400x685 — the hero came out 696px against a 685px screen
   while .plates held its full 219.4px. Eleven px is nothing to look at,
   but a mandatory snap point in front of a section taller than the
   viewport is the exact failure the query at the top of this file exists
   to avoid: you release the wheel and it snaps back.

   Bounded, the factors already written there do the work — the band is
   flex:0 1 auto with min-height:0 and gives way, the copy is flex:0 0
   auto and does not. Both are svh with a vh fallback, matching .snap's
   own pair, or the bound and the minimum would disagree by the toolbar's
   height on a phone.

   IT IS SCOPED TO THE QUERY THAT TURNS SNAPPING ON, the same pairing the
   scroll hint keeps, and the two have to move together. Where the page
   is an ordinary document there is no snap point to miss, and a hero
   that scrolls is better than a band squeezed to fit. */
@media (min-width:901px) and (min-height:680px){
  .hero{max-height:100vh;max-height:100svh}
}

/* ---------- the product page's one ------------------------------------

   Full bleed across the top of the document, and IN FLOW like the home
   page's, for the same reason: the hero follows it rather than sitting
   on it. It was laid over the hero to begin with, which cost twice. The
   page no longer fitted on one screen — the band, the copy and the
   product drawing were stacked in the same 900px — and everything under
   it had to be corrected down to a watermark to stay readable, which is
   most of a photograph thrown away.

   The negative margin is what puts it under the masthead. #main reserves
   --mast-h plus a --sp-6 gap for the fixed masthead above it; the band
   wants the masthead's rule and nothing else between, so it takes that
   gap back. Padding on #main is what stops the margin collapsing through.

   The top edge is square on purpose, against the masthead's own rule.
   Only the bottom is thinned, into the clear air before the hero. */
.plate-band{
  height:var(--band-h);
  margin-top:calc(var(--sp-6) * -1);
  margin-bottom:var(--sp-6);
  overflow:hidden;
  --band-cut:linear-gradient(to bottom,
    #000 0,#000 58%,transparent 100%);
  -webkit-mask-image:var(--band-cut);
          mask-image:var(--band-cut);
}

/* Nothing here needs a width query any more. Both heights are a ratio of
   the width, so both bands hold the proportion their pictures were cut
   at, at every size, and each band is one file that cannot be
   re-laid-out. What used to live here — a grid collapsing to one column,
   two of three panels hidden, a second height for the band — was all
   repair for an arrangement assembled at runtime. */

/* A photograph is decoration here and nothing else carries it, so both
   placements drop out wherever the ground itself does. .ag-halftone
   already surrenders its screen in forced colours; a ghosted photograph
   behind type is not what that reader asked for either way. */
@media print{.plates,.plate-band{display:none}}
@media (forced-colors:active){.plates,.plate-band{display:none}}

/* ============================================================
   PRODUCT SCREENS

   The pair used to sit side by side in one grid, which read as a
   matched set. They are a sequence now, one screen each, artwork
   alternating sides so the second is recognisably the same object seen
   again rather than a comparison against the first. Polychrome leads on
   order. Saturn's wide unit gets the widest artwork column (--lead);
   no product leads on type size, so the titles are set at one scale.

   Each split below is written copy first, artwork second, for a screen
   with the artwork on the right. --flip moves the artwork to the left,
   and every split that is not even has a flipped twin to match.
   ============================================================ */

.showcase{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:var(--sp-6) var(--sp-8);align-items:center;width:100%;
}
.showcase--lead{grid-template-columns:minmax(0,1fr) minmax(0,1.08fr)}
.showcase--lead.showcase--flip{grid-template-columns:minmax(0,1.08fr) minmax(0,1fr)}
.showcase--flip .showcase-art{order:-1}

/* Polychrome is an upright unit and Saturn is a wide one, so they cannot
   share a column split. Given half the row a 0.61-aspect device caps out on
   height long before it uses the width, and sits in a lake of its own
   whitespace while the copy beside it is squeezed for no reason. The tall
   variant hands that width back to the copy and lets the device run taller
   instead. */
.showcase--tall{grid-template-columns:minmax(0,1.46fr) minmax(0,.54fr)}
.showcase--tall.showcase--flip{grid-template-columns:minmax(0,.54fr) minmax(0,1.46fr)}
/* Sized against Saturn rather than against its own column: an upright device
   allowed the same height as a wide one is half again as big on the page and
   reads as the more important of the two, which is backwards. */
/* width:auto matters. The base rule gives the image a full-width box and
   lets object-fit letterbox the artwork inside it, which silently re-centres
   an upright device and leaves an auto margin nothing to push against.
   Shrinking the box to the artwork lets the margin put the unit on the
   page's edge on whichever side it sits: the right margin here, in line
   with the masthead nav, or the left under --flip (margin-right:auto
   below), in line with every heading. */
.showcase--tall .showcase-art img{
  width:auto;max-width:100%;max-height:min(44svh,412px);object-fit:fill;
  margin-left:auto;
}
.showcase--tall.showcase--flip .showcase-art img{margin-left:0}

/* The accent rule off the product card. It is the one place each screen
   is allowed its product's colour, and it stays on the copy rather than
   spanning the screen — a full-bleed 2px band would sit under the fixed
   masthead at the snap point and never be seen. */
.showcase-copy{border-top:2px solid var(--pc);padding-top:var(--sp-5)}
.showcase-index{
  display:flex;align-items:center;gap:var(--sp-3);
  margin:0 0 var(--sp-4);max-width:none;
  font-family:var(--ag-label);font-size:var(--t-micro);font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ag-fg-faint);
}
/* The numeral is the only product colour left on the screen now that the
   eyebrow is gone. --pc-text, never --pc: the bright accents are built for
   the products' dark surfaces and measure under 2:1 as type on paper. */
.showcase-index .ag-figure{color:var(--pc-text)}

.product-title{margin-block:var(--sp-3) var(--sp-2);
  font-weight:600;letter-spacing:-.012em;line-height:1.15}
/* Both screens, one size. The title carries the whole product name —
   "Saturn Sound Printer", not "Sound Printer" under an eyebrow — and at
   the h1 step the longer of the two names wrapped inside its column. */
.showcase .product-title{font-size:var(--t-h2)}

.tagline{
  font-family:var(--ag-label);font-size:var(--t-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ag-fg-faint);display:block;
  margin-bottom:var(--sp-4);
}
.showcase-copy p{max-width:48ch}
.showcase .cta{display:flex;flex-wrap:wrap;gap:var(--sp-3);margin-top:var(--sp-5)}

/* Capped against the viewport, not just the column: on a short laptop
   an uncapped illustration is what pushes a screen past 100svh and
   quietly strands the buttons under the fold. */
.showcase-art img{
  display:block;width:100%;height:auto;
  max-height:min(56svh,540px);object-fit:contain;
}
.showcase--flip .showcase-art img{margin-right:auto}

/* Specs sit under each product as chips rather than a table — there are
   only four of them and a table would out-weigh the prose above it. */
.specs{
  list-style:none;margin:var(--sp-4) 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-4);
}
.specs li{
  font-family:var(--ag-label);font-size:var(--t-micro);letter-spacing:.06em;
  color:var(--ag-fg-faint);font-variant-numeric:tabular-nums;
}
.specs li::before{content:"— ";opacity:.55}

@media (max-width:900px){
  .showcase,.showcase--lead,.showcase--tall,
  .showcase--lead.showcase--flip,.showcase--tall.showcase--flip{grid-template-columns:1fr;gap:var(--sp-6)}
  /* Artwork after the copy on both, whichever side it takes on wide
     screens — a picture of a thing you have not been introduced to yet
     is just a rectangle. */
  .showcase--flip .showcase-art{order:0}
  /* Stacked, the upright unit has to give up more height than the wide one
     or it pushes the buttons under the fold on a short screen. */
  .showcase-art img{max-height:38svh}
  /* Stacked, every unit sits on the left margin under its copy. */
  .showcase--tall .showcase-art img{max-height:44svh;margin-left:0}
}

/* ---------- common ---------------------------------------------------- */

.community .ag-spec th{width:150px}
/* Three short rows in a whole screen read as a table that got lost. Given a
   screen to itself the table takes it: the rows open up with the viewport
   rather than sitting in the middle of it. Scoped to the same query that
   turns snapping on, since it is only a full screen there. */
@media (min-width:901px) and (min-height:680px){
  .community .ag-spec th,.community .ag-spec td{padding-block:clamp(10px,1.7svh,22px)}
}
@media (max-width:520px){
  .community .ag-spec th,.community .ag-spec td{display:block;width:auto;padding-right:0}
  .community .ag-spec th{border-bottom:0;padding-bottom:2px}
}

/* The email list, under the lanes. Two rows on a wide screen: the two
   names at equal widths, then the address under both of them, since
   addresses run long, with the button at its end. The note above runs the
   full width of the column. Stacked on a phone. The button is sized to
   the fields' height so the row reads as one control. */
.signup{margin-top:var(--sp-7)}
.signup-heading{color:var(--ag-fg);margin:0 0 var(--sp-2)}
.signup-note{font-size:var(--t-small);color:var(--ag-fg-muted);
  margin:0 0 var(--sp-4);max-width:none}
.signup-fields{display:grid;gap:var(--sp-3);
  grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto;align-items:end}
.signup-field--email{grid-column:1 / 3}
.signup-field .ag-label{display:block;margin-bottom:var(--sp-2);color:var(--ag-fg-faint)}
.signup .ag-btn{justify-content:center;padding-block:13px}
.signup .ag-btn:disabled{opacity:.5;cursor:default}
.signup-status{min-height:1.5em;margin:var(--sp-3) 0 0;
  font-size:var(--t-small);color:var(--ag-fg-muted)}

/* The line under a "Coming soon" CTA, saying why it is not a link. */
.status{margin:var(--sp-3) 0 0;font-size:var(--t-small);color:var(--ag-fg-muted)}
/* Off-screen rather than display:none: some bots skip fields that are not
   rendered, and those are the ones this is for. */
.signup-trap{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
@media (min-width:901px) and (min-height:680px){
  .signup{margin-top:clamp(24px,4svh,48px)}
}
@media (max-width:520px){
  .signup-fields{grid-template-columns:1fr}
  .signup-field--email{grid-column:auto}
}

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

/* On the home page the footer shares the closing screen with About and
   is pushed to the foot of it; on a product page it ends the document
   in the ordinary way. */
/* The closing screen carries two things at once. The auto margins give
   all the free space to the About block, which centres it in whatever is
   left above the footer, and push the footer to the foot of the screen —
   without taking it out of flow, so the same rule still reads correctly
   at the sizes where the section is an ordinary block. */
.closing .ag-row{margin-block:auto}
.closing .site-footer{margin-top:var(--sp-8)}

.site-footer{
  border-top:var(--ag-hair) solid var(--ag-rule);
  padding-block:var(--sp-6) var(--sp-2);
}
body:not(.home) .site-footer{margin-top:var(--sp-9);
  padding-block:var(--sp-7) var(--sp-8)}
.site-footer .ag-wrap{
  display:flex;justify-content:space-between;align-items:flex-end;
  gap:var(--sp-6);flex-wrap:wrap;
}
.site-footer img{height:36px;width:auto}
.footer-links{display:flex;flex-direction:column;gap:6px;text-align:right}
.footer-links a{
  font-family:var(--ag-label);font-size:var(--t-micro);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ag-fg-muted);text-decoration:none;
}
.footer-links a:hover{color:var(--ag-fg);text-decoration:underline;
  text-underline-offset:.2em}
.colophon{font-family:var(--ag-label);font-size:var(--t-micro);
  color:var(--ag-fg-faint);letter-spacing:.04em;margin:0;max-width:none}
@media (max-width:560px){
  .site-footer .ag-wrap{align-items:flex-start}
  .footer-links{text-align:left}
}

/* ---------- theme-paired artwork -------------------------------------- */
/* The lockups are flat ink-on-paper files, so the page swaps which one it
   shows rather than filtering a single image.

   These rules are the ONLY place `display` is set on the paired images.
   Sizing rules above deliberately omit it: a selector like `.footer img`
   would carry more specificity than `.only-dark` and un-hide the wrong
   artwork. */

.only-light,.only-dark{display:none}
.only-light{display:block}
@media (prefers-color-scheme:dark){
  html:not([data-theme="light"]) .only-light{display:none}
  html:not([data-theme="light"]) .only-dark{display:block}
}
html[data-theme="dark"] .only-light{display:none}
html[data-theme="dark"] .only-dark{display:block}

/* ============================================================
   PRODUCT PAGES
   ============================================================ */

/* The page belongs to one product, so it may carry that product's colour —
   but on the same terms as a card on the homepage: one rule and one label,
   not a tinted page. --pc comes from the .ag-product--<name> modifier on
   <body>, which sets the variable without the card's border. */
/* With the eyebrow folded into the title, this rule is the first product
   colour the page shows — keep it. */
.product-page .product-hero{border-top:2px solid var(--pc);padding-top:var(--sp-5)}

/* Two columns, because a page about an object should open with the object.
   The copy's own measure was already leaving the right half of this screen
   empty, so the drawing costs no height and the buttons stay where they
   were. It is the home page's artwork box reused, not a second one, so the
   same drawing is sized the same way in both places. */
.product-hero{
  padding-bottom:var(--sp-8);
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.04fr);
  gap:var(--sp-6) var(--sp-8);align-items:center;
}
.product-hero .ag-display{font-size:var(--t-h1);margin-block:var(--sp-3) var(--sp-2)}
.product-hero .ag-lead{max-width:50ch}
.product-hero .cta{display:flex;flex-wrap:wrap;gap:var(--sp-3);margin-top:var(--sp-6)}

/* The upright unit, on the same reasoning as .showcase--tall: given half
   the row a 0.61-aspect device runs out of height long before it uses the
   width. The width goes back to the copy and the device runs taller in a
   narrower column. Capping the HEIGHT is what shrinks the box to the
   artwork — width:auto alone resolves to the full column, and object-fit
   then letterboxes an upright device inside a box far wider than it is. */
.product-hero--tall{grid-template-columns:minmax(0,1fr) minmax(0,.62fr)}
.product-hero--tall .showcase-art img{
  width:auto;max-width:100%;max-height:min(52svh,470px);margin-inline-start:auto;
}

/* Then one cap for both, below both, because the screen is not empty above
   them any more: the photographic band takes --band-h off the top of the
   page, and the caps above were set when the drawing was the only thing
   there. The whole point of the band being a band is that the photograph,
   the copy and the drawing share one screen — at the old caps a 700px
   screen put the bottom of the drawing just under the fold. 48svh is what
   is left after the masthead, the band, its margin and the section's own
   padding — which on this section is a 2px accent rule and --sp-5 as well
   as the --sp-8 underneath. Measured, not derived, across the common
   sizes; the tightest is 1280x700 and the run that set this number is in
   the commit. Narrowing the band to 9:1 is what bought the last of it. */
.product-hero .showcase-art img{max-height:min(48svh,430px)}

/* Same breakpoint as the showcase: below it the artwork follows the copy,
   because a picture of a thing you have not been introduced to yet is just
   a rectangle. The stacked height caps come from .showcase-art itself. */
@media (max-width:900px){
  .product-hero,.product-hero--tall{grid-template-columns:1fr}
  /* Stacked, the unit crosses back to the page's left margin. Held to the
     right it would be the one thing on the page not starting where every
     heading and paragraph starts. */
  .product-hero--tall .showcase-art img{margin-inline-start:0}
}

/* Numbered because these really are sequences — a fixed signal order in one
   case, the order you actually do things in the other. Not decoration. */
.steps{list-style:none;counter-reset:step;margin:0;padding:0;max-width:var(--ag-measure)}
.steps li{
  counter-increment:step;display:grid;grid-template-columns:26px minmax(0,1fr);
  gap:var(--sp-3);padding:var(--sp-4) 0;
  border-top:var(--ag-hair) solid var(--ag-rule);
}
.steps li::before{
  content:counter(step,decimal-leading-zero);
  font-family:var(--ag-label);font-size:var(--t-micro);
  color:var(--pc-text,var(--ag-fg-faint));padding-top:.5em;
  font-variant-numeric:tabular-nums;
}
.steps b{display:block;font-family:var(--ag-display);font-weight:500;
  font-size:var(--t-h3);letter-spacing:-.006em;margin-bottom:2px;color:var(--ag-fg)}
.steps p{margin:0;color:var(--ag-fg-muted);font-size:var(--t-small)}
.steps p + p{margin-top:6px}

/* The product's terms, under its spec sheet: part of what you are told
   about the product, not site furniture, so it sits with the specs rather
   than in the footer. Quieter than the table, and underlined, because
   it is the one thing under there that goes somewhere else. */
.terms-link{margin:var(--sp-5) 0 0;font-size:var(--t-small);color:var(--ag-fg-muted)}
.terms-link a:hover{color:var(--ag-fg)}

.backlink{
  display:inline-block;margin-top:var(--sp-7);
  font-family:var(--ag-label);font-size:var(--t-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ag-fg-muted);text-decoration:none;
}
.backlink:hover{color:var(--ag-fg);text-decoration:underline;text-underline-offset:.25em}

.nav a[aria-current="page"]{color:var(--ag-fg)}

/* Community lanes: the destination sits at the end of its own line so the
   table still reads as a spec sheet rather than a list of buttons. */
.community-link{
  display:inline-block;margin-left:var(--sp-2);
  font-family:var(--ag-label);font-size:var(--t-label);
  letter-spacing:.12em;text-transform:uppercase;
  white-space:nowrap;text-decoration-color:var(--ag-rule-strong);
}

/* ============================================================
   TERMS PAGES
   ============================================================ */

/* A product's terms, one page each at /<product>/terms. The article is the
   app's own markup copied in whole (see the note above it on each page), so
   it is styled from outside: bare <h3> and <p>, no classes to add. The base
   h3 has no margin, which in a run of numbered sections reads as one block. */
.terms h3{margin:var(--sp-6) 0 var(--sp-2)}
/* The privacy page borrows .terms and opens on a heading, not a paragraph;
   without this its first heading sits a gap below the date beside it. */
.terms > h3:first-child{margin-top:0}
