/* ============================================================================
   the most beautiful colours

   The window is not a picture on the page. It is the light source, and the
   page is the room it lights. Every colour here is sampled from the record's
   own painted glass; none of it is invented.

   Opening a song does not cover the artwork with a modal. The window itself
   zooms until that pane IS the screen, and the content rises underneath it.
   On a phone the window is fixed and full-bleed, which is what makes that a
   single GPU transform on one stage element rather than a layout change.

   Fonts are self-hosted and inlined here. No third party is contacted and
   nothing is preloaded: on the Lee build preloading webfonts made first paint
   200ms WORSE, because a preload is a high-priority fetch competing with the
   artwork that actually produces the first paint.
   ========================================================================= */

/* The band's own brand face. Hand-painted, by the same hand that painted the
   panes on the cover, which is why it belongs here and a licensed grotesque
   would not. Display only.
   🔴 It has 145 codepoints and NO U+014C, so one of the guest credits would
   fall back mid-word. Feature credits are therefore set in the body face,
   never in this one. No song title needs a glyph outside its coverage -
   checked all ten.
   (The credit itself is deliberately not written here. This file is SERVED,
   comments and all, and the leak check reads it: naming the artist in a CSS
   comment leaks the feature just as completely as putting it in the markup.) */
@font-face{font-family:'For King';src:url(assets/fonts/forkingbrush.woff2) format('woff2');
  font-weight:400;font-display:swap}

@font-face{font-family:Archivo;src:url(assets/fonts/archivo-400_800-latin.woff2) format('woff2');
  font-weight:100 900;font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:Archivo;src:url(assets/fonts/archivo-400_800-latin-ext.woff2) format('woff2');
  font-weight:100 900;font-display:swap;
  unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}

:root{
  --void:#0b0806;
  --void-2:#141010;
  --paper:#ece7de;
  --dust:#867f77;
  --hair:rgba(236,231,222,.13);

  /* The unlit glass. These two numbers are the campaign's whole mood. */
  --glass-sat:.09;
  --glass-bright:.40;

  /* The colour of the room today, averaged from the panes that are lit. Before
     any pane lights there is no colour in the room, and that is the point. */
  --accent:#8d8a86;
  --room:#3a3a42;
  --room-soft:rgba(120,120,132,.16);

  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  /* The zoom. Slow out of the gate, decisive at the end: it should feel like
     the camera moving, not a panel sliding. */
  --ease-zoom:cubic-bezier(.16,.84,.24,1);
  --zoom-ms:760ms;

  /* Floor is 12px, not 10px. 24 elements - the most common type style on
     the page - were rendering at 10.9px desktop and 10px phone. */
  --step--1:clamp(13px,.9vw,14px);
  --step-0:clamp(15px,1.12vw,17px);
  --step-1:clamp(17px,1.5vw,20px);
  /* The song of the day. Was capped at 54px, which put it BELOW the 58px
     'On tour' utility heading - the campaign's emotional payload set
     smaller than a wayfinding label. It is the largest thing on the page
     now, and the brush face finally gets to be display. */
  --display:clamp(30px,4.4vw,62px);
}

*{box-sizing:border-box}
/* `hidden` is an attribute with a display:none UA style, and ANY display
   declaration in author CSS beats it. `.cta{display:flex}` was rendering a
   pre-save button for a URL that does not exist yet. */
[hidden]{display:none!important}
html,body{margin:0;padding:0}
html{background:var(--void)}
body{
  min-height:100svh;
  background:var(--void);
  color:var(--paper);
  font:400 var(--step-0)/1.55 Archivo,system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
  overscroll-behavior-y:none;
}
body.is-open{overflow:hidden}

/* ---- The open state -----------------------------------------------------
   When the page was rebuilt into sections, the rules that cleared the chrome
   behind an open pane still pointed at elements that no longer existed - so
   the hero sat visible THROUGH the panel. An open pane is a full state, not
   an overlay on a live page. */
body.is-open main > section,
body.is-open main > footer,
body.is-in.is-open .hero{opacity:0!important;pointer-events:none;transition:opacity .3s var(--ease)}
body.is-open .scene::after{opacity:0}

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

.skip{position:absolute;left:-9999px;top:0;z-index:99;background:var(--paper);color:var(--void);
  padding:14px 20px;min-height:44px;display:inline-flex;align-items:center;
  font:500 14px Archivo,sans-serif}
.skip:focus{left:12px;top:12px}

/* Film grain. `stitchTiles='stitch'` is not optional: feTurbulence defaults to
   noStitch, which left a 4.3-luma step at every tile edge on a ground that is
   only 15 luma, so the grid was visible across the darkest part of the page. */
.grain{
  position:fixed;inset:0;z-index:60;pointer-events:none;opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- Volumetric light: CUT --------------------------------------------
   `#shafts` and `.shaft` are gone: three full-viewport layers, each
   blur(26px) + mix-blend-mode:screen + will-change:transform, animating a
   rotation FOREVER, composited over the whole artwork every frame.

   Measured by ablation at 1512x900: removing the layer changed 10% of pixels
   by a mean of 0.42 and a MAX OF 8 of 255 - at or below the just-noticeable
   difference. For comparison, hiding the lockup moves 2% of pixels with a max
   delta of 231. The shafts were the single most expensive thing on the page
   and the least visible.

   History worth keeping, because it is the whole lesson: at z-index 0 they
   rendered literally 0.0% of pixels (painted under an opaque plate). I moved
   them above the scene so they would render - which turned an invisible layer
   into an invisible layer that repaints the entire viewport every frame. The
   fix for "it renders nothing" was to delete it, not to make it cost more. */

/* ---- The sun crossing the window ---------------------------------------- */
/* The arrival is not a fade and not a stagger on a timer. A raking light
   crosses the glass and each pane lights AS IT IS REACHED, so the reveal has a
   cause. Ignition delays are derived from each pane's own x position, which is
   why the two read as one movement. */

.sweep{
  position:absolute;z-index:6;inset:-14% -45%;
  pointer-events:none;opacity:0;
  background:linear-gradient(102deg,transparent 40%,rgba(255,246,229,.42) 49%,
             rgba(255,252,242,.62) 51%,transparent 61%);
  filter:blur(20px);
  mix-blend-mode:screen;
  transform:translate3d(-58%,0,0);
}
.sweep.is-running{animation:sweep 2500ms cubic-bezier(.36,.02,.2,1) forwards}
@keyframes sweep{
  0%  {opacity:0;transform:translate3d(-58%,0,0)}
  16% {opacity:1}
  84% {opacity:1}
  100%{opacity:0;transform:translate3d(58%,0,0)}
}

/* ---- The room ----------------------------------------------------------- */

/* The type lives in a column on the wall to the LEFT of the window, which is
   what makes the page asymmetric instead of a caption under a picture. */
/* A scrim under the type only, so the wall stays dark without dimming the
   glass the whole page is about. */

/* ---- The light on the floor: CUT ----------------------------------------
   The cast floor (`.castfloor` + 10 `.cast` shapes, one per morning) is gone.
   It never rendered a single pixel in any deployed build. `top:100%` inside an
   `overflow:hidden` container exactly 100svh tall puts it entirely below the
   clip, and `rotateX(76deg)` about `transform-origin:50% 0` foreshortens AWAY
   from the origin, so no part of it ever crosses back up.
   Measured by ablation: removing the layer changed 0.0% of pixels.

   It is not fixable without shrinking the artwork. fit() sizes the plate to
   fill the viewport with a 10px pad, so on desktop there is no floor below the
   window to throw light onto. Earning the space means making the window
   smaller, and the hero frame is the strongest thing on the page.

   Its second job - showing which mornings you were here for - is already done
   by the tracklist, in words. */


/* ---- The cursor is a light source --------------------------------------
   Position comes from JS as custom properties, so the whole thing stays on
   the compositor. The light LAGS the pointer and overshoots slightly on a
   fast move: every cursor-light on the web is rigid and instant, and a light
   with mass is two lines of lerp. */
.torch{
  position:fixed;inset:0;z-index:2;pointer-events:none;
  opacity:0;transition:opacity 1.2s var(--ease);
  background:radial-gradient(240px 240px at var(--tx,50%) var(--ty,50%),
    color-mix(in srgb, var(--accent) 55%, white) 0%,
    color-mix(in srgb, var(--accent) 30%, transparent) 34%,
    transparent 68%);
  mix-blend-mode:screen;
  filter:blur(28px);
}
body.is-in .torch{opacity:.12}
body.is-open .torch{opacity:0}
@media (hover:none),(prefers-reduced-motion:reduce){ .torch{display:none} }

/* ---- The page -----------------------------------------------------------
   The window is fixed; the page scrolls past it. Sections after the hero
   bring their own ground, so the artwork reads as the room you start in and
   then walk out of. */

main{position:relative;z-index:3;pointer-events:none}
main section.record,main section.tour,main .foot,main .open{pointer-events:auto}
section{padding:clamp(64px,10vh,120px) clamp(20px,6vw,80px)}

/* The hero and the today panel sit OVER the fixed scene and are mostly
   transparent, so they have to let clicks through or the panes - the entire
   mechanic - are unreachable. Their own content takes pointer events back. */
.hero,.today{pointer-events:none}
/* 🔴 `.hero > *` WAS THE BUG THIS COMMENT WAS TRYING TO PREVENT.
   `.hero__body` IS `.hero > *`, and it is a grid container: `justify-items:
   start` aligns its CHILDREN, it does not shrink the container, which spans
   the full width over the lower half of the fixed scene. So the element that
   took pointer events back covered the panes.

   Measured before the fix, at 1280x800 / 1440x900 / 1512x982 / 1920x1080 /
   1366x768 / 768x1024: only 4 of 10 lit panes were clickable and clicking
   TODAY'S pane did nothing at all. Phones were unaffected (12/12), and day 1
   happens to work, so it would not have shown up in a launch-day screenshot -
   it would have started failing on the morning of day 2 and stayed broken.

   Pointer events go to the CONTENT, never to its container. */
.hero__body,.today__inner{pointer-events:none}
.hero__body > *,.today__inner > *{pointer-events:auto}

.eyebrow{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  margin:0 0 18px;font:500 var(--step--1)/1 Archivo,sans-serif;
  letter-spacing:.14em;text-transform:uppercase;color:var(--dust);
}
.eyebrow__sep{opacity:.5}

/* The hero is a COLUMN on the wall, vertically centred, never a caption
   strip along the bottom. Its width is the rail that fit() reserves to the
   left of the glass, so the type physically cannot land on the artwork or on
   their faces - which is where the lockup used to sit. */
.hero{
  min-height:100svh;
  display:flex;align-items:center;
  padding:clamp(28px,6vh,72px) clamp(22px,4vw,64px)
          clamp(28px,6vh,72px) clamp(22px,4vw,64px);
  opacity:0;transform:translateY(14px);
  transition:opacity 1s var(--ease-out) 1.1s,transform 1s var(--ease-out) 1.1s;
}
body.is-in .hero{opacity:1;transform:none}
/* A local ground for the hero copy. The rightward bias opens a wall column at
   1500px and wider, but at ~900 the glass fills the viewport and there is
   nowhere dark to put type. This travels with the text at every width. */
.hero__body{
  position:relative;
  display:grid;gap:clamp(13px,1.9vh,20px);justify-items:start;
  width:100%;
}
/* Grid items size to their content by default, so the lockup and the song
   title both ran WIDER than the column that was supposed to contain them. */
.hero__body > *{max-width:100%;min-width:0}

@media (min-width:761px){
  /* --rail is written by fit(). Only on desktop: on a phone the rail is 0 and
     this calculation collapsed the whole hero to zero width. */
  .hero__body{
    width:min(calc(var(--rail,42vw) - clamp(22px,4vw,64px) - 20px),30rem);
  }
}
/* ---- The hero scrim: DELETED ---------------------------------------------
   `.hero__body::before` was a dark radial wash behind the hero copy. It is
   gone because it produced a hard horizontal seam the full width of the frame,
   straight across the album artwork: a radial gradient on `inset:0` never
   reached transparent before the box ended, so it simply stopped. Measured in
   isolation over a flat ground, 86.7 luma of step across 4px, present in
   Chromium, Firefox and WebKit, on every campaign day. Three separate reviews
   flagged it independently and it was the first thing each of them saw.

   Deleted rather than repaired: with the layer switched off the hero copy is
   still fully legible, because `.scene::after` already lays a bottom-left
   vignette over the fixed scene. The scrim was doing a job that was already
   done, and every attempt to feather its edges made the composition worse.

   If hero copy ever needs more ground, darken the EXISTING vignette. Do not
   reintroduce a second box-shaped wash over the artwork.

   (One correction for the record: I also blamed this layer for the page
   loading as a black screen. That was wrong. The page renders correctly; the
   black frames were an artifact of screenshotting a BACKGROUND tab, where
   Chrome throttles rendering and the entrance transition never advances past
   ~0.2 opacity. Any interaction nudges it forward a frame, which made layer
   ablation look like it was fixing something. Nothing here caused it.) */
/* The page had no <h1> at all. The lockup IS the heading; it was just never
   marked as one.
   🔴 NOT `display:contents`. In WebKit that made the heading's accessible name
   225 characters - it absorbed the album line, the campaign sentence and all
   seven button labels, so the first thing an iOS VoiceOver user heard from the
   rotor was "...PRE-SAVE OPENS SOONTODAY'S PANETHE RECORDSPOTIFY...".
   Chromium computed 46. `display:block` gives 46 in both. */
/* The heading and its image are both non-interactive, and both overlap the
   glass. Neither may consume a pane click. */
.hero__h1{margin:0;display:block;pointer-events:none}
.hero__lockup{display:block;width:min(100%,86vw,440px);height:auto;
  /* The wordmark overlaps the glass at 1280, 1512 and 768, and it was the last
     thing eating a pane click - pane 5 sat underneath it and could not be
     opened. It is a heading image with no behaviour of its own, so it has no
     business consuming a pointer event. */
  pointer-events:none;
}


.hero__date{margin:0;font-size:var(--step-1);color:var(--paper)}
.hero__date strong{font-weight:600}

.hero__note{margin:0;font-size:var(--step--1);color:var(--dust);max-width:32ch;line-height:1.5}


/* ---- The window and its light ------------------------------------------- */

/* ---- The scene ----------------------------------------------------------
   Three registered layers on one 2100x1280 plate. Anchored to the RIGHT and
   bled off that edge, which kills the centred-square symmetry the audit
   called the single biggest "not designed" signal, and leaves the left of the
   viewport as wall for the type to live on. The whole window stays visible,
   because every pane has to stay clickable. */

.scene{
  position:fixed;z-index:1;
  /* Fills the viewport. Geometry is computed in fit() rather than declared,
     because "cover" and "every pane stays on screen" are in tension: the plate
     is 1.64:1 and a laptop is about 1.26:1, so a true cover crop eats ~12% off
     each side - which happens to be almost exactly the wall, but not always.
     fit() covers as far as it can and then stops at the window's own bbox. */
  top:0;left:0;width:100vw;height:100svh;
  pointer-events:none;overflow:hidden;
}
.scene__stage{
  /* Feather the plate's own edges into the room. The artwork is a rectangle on
     a dark page, and at rest - and much more obviously when a pane is zoomed
     and the plate no longer covers the viewport - you could see that rectangle
     end. A hard boundary is what makes it read as a photograph placed on a
     page rather than a wall you are standing in front of.
     Kept very tight - 1.5% and 1.2%. The plate is the SQUARE cover now and it
     is 97.4% glass: the wide treatment had a broad dark surround that could
     absorb a 5% feather, this one does not, and at 5% the fade ate the outer
     pane columns. The artwork carries its own dark frame, so the feather only
     has to soften that frame's outer edge into the room. */
  -webkit-mask-image:
    linear-gradient(to right,transparent 0,#000 1.5%,#000 98.5%,transparent 100%),
    linear-gradient(to bottom,transparent 0,#000 1.2%,#000 98.8%,transparent 100%);
          mask-image:
    linear-gradient(to right,transparent 0,#000 1.5%,#000 98.5%,transparent 100%),
    linear-gradient(to bottom,transparent 0,#000 1.2%,#000 98.8%,transparent 100%);
  -webkit-mask-composite:source-in;
          mask-composite:intersect;
  position:absolute;top:0;left:0;
  transform-origin:0 0;
  will-change:transform;
  transition:transform var(--zoom-ms) var(--ease-zoom),opacity 1.5s var(--ease);
  opacity:0;
}
body.is-in .scene__stage{opacity:1}

/* The window with the band taken out of it, drained of colour. */
.scene__plate{
  position:absolute;inset:0;
  background:image-set(url(assets/plate.webp) type('image/webp'), url(assets/plate.jpg) type('image/jpeg')) center/100% 100% no-repeat;
  filter:saturate(var(--glass-sat)) brightness(var(--glass-bright));
  transition:filter 3.4s var(--ease);
}
.scene.is-released .scene__plate{filter:none}
.scene__panes{position:absolute;inset:0}

/* The two of them, cut out, IN FRONT of the glass. This is the whole reason
   the light can happen behind them instead of around them. */
/* THE BAND IS LIT BY THE WINDOW.
   band.webp is cut from the full-colour photograph, so its rim light is baked
   in at full strength - which meant on day 1 they were wearing warm light
   thrown by panes that were not on yet. They are relit from how much of the
   window is actually burning: near-black silhouette at the start, full
   exposure once the record is here. */
.scene::after{
  content:'';position:absolute;inset:0;z-index:8;pointer-events:none;
  background:
    linear-gradient(to top,rgba(11,8,6,.92) 0%,rgba(11,8,6,.34) 26%,transparent 52%),
    linear-gradient(to right,rgba(11,8,6,.86) 0%,rgba(11,8,6,.22) 26%,transparent 46%);
}

.scene__band{
  position:absolute;inset:0;z-index:5;
  background:url(assets/band.webp) center/100% 100% no-repeat;
  filter:saturate(calc(.04 + .096 * var(--lit,0))) brightness(calc(.26 + .074 * var(--lit,0)));
  transition:filter 2.4s var(--ease);
  pointer-events:none;
}
/* Release day, and ONLY release day: the grade comes off and they are lit the
   way the cover has them.

   This rule used to read `.scene__band{filter:none}` with no state prefix, so
   it beat the graded rule above on source order and killed the relight on
   every day of the campaign. It happened to equal the correct day-10 value,
   which is why it looked fine in a release-day screenshot and wrong in every
   other one: on day 1, one pale pink pane was the only light in the room and
   they still wore warm orange. Keep the prefix. */
.scene.is-released .scene__band{filter:none}

/* And the rim takes TODAY'S colour.
   Masked by the band's own LUMINANCE, not its alpha. Masking by alpha and
   blending `color` recoloured the whole silhouette and they came out printed
   in pink. Measured, the cutout is p50 luminance 0.0032 - they are black -
   and only the top 4% catches any light at all. That 4% is the rim, and it is
   the only part that should know what colour is burning today.
   `screen` adds light; `color` swapped hue on a mass that has none. */
/* The rim is DIRECTIONAL. One pane burning at the top left cannot light the
   right side of a face. `--rim-angle` is the bearing from the band to the
   weighted centre of the lit panes, so only the side actually facing the
   light gets the colour, and the whole thing swings as more panes come on. */
.scene__rimwrap{
  position:absolute;inset:0;z-index:6;pointer-events:none;
  -webkit-mask-image:linear-gradient(var(--rim-angle,300deg),
    #000 0%,#000 26%,rgba(0,0,0,.6) 50%,transparent 78%);
          mask-image:linear-gradient(var(--rim-angle,300deg),
    #000 0%,#000 26%,rgba(0,0,0,.6) 50%,transparent 78%);
  transition:-webkit-mask-image 1.8s var(--ease),mask-image 1.8s var(--ease);
}
.scene__rim{
  position:absolute;inset:0;pointer-events:none;
  background:var(--accent);
  -webkit-mask-image:url(assets/band-rim.png);mask-image:url(assets/band-rim.png);
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  mix-blend-mode:screen;
  opacity:calc(.80 + .02 * var(--lit,0));
  transition:opacity 2.4s var(--ease),background 1.6s var(--ease);
}
.scene.is-released .scene__rim{opacity:0}

/* ---- Panes -------------------------------------------------------------- */

/* `.pane`, `.cast` and the panel controls are <button>. The UA stylesheet
   gives buttons their OWN font-family and text-align:center, and neither
   inherits from body - so the single piece of type printed on the album
   artwork was rendering in browser-default Arial Bold, centred by accident.
   No contrast checker can see that, which is why measuring 10.19:1 and calling
   it verified was worthless. Fix it at the primitive so it cannot come back. */
button,.pane,.open__arrow,.open__close{
  font:inherit;text-align:inherit;color:inherit;
}

.pane{
  position:absolute;pointer-events:auto;
  border:0;padding:0;margin:0;background:none;
  cursor:default;
  -webkit-tap-highlight-color:transparent;
}
.pane.is-lit{cursor:pointer}

/* Each of these is sized to the WHOLE window and pushed back into place, which
   is what makes registration exact - and also means each one covers the entire
   grid. Without pointer-events:none the topmost pane's glass swallows every
   click on the other nine. */

.pane__glass{
  position:absolute;
  width:calc(100% / var(--w));
  height:calc(100% / var(--h));
  left:calc(var(--x) / var(--w) * -100%);
  top:calc(var(--y) / var(--h) * -100%);
  background:image-set(url(assets/plate.webp) type('image/webp'), url(assets/plate.jpg) type('image/jpeg')) center/100% 100% no-repeat;
  /* CSS masks read the ALPHA channel by default. These PNGs carry coverage in
     alpha for exactly that reason: a grayscale mask reads as fully opaque and
     masks nothing, which had every lit pane painting the entire window. */
  -webkit-mask-image:var(--mask);
          mask-image:var(--mask);
  -webkit-mask-size:100% 100%;
          mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;
          mask-repeat:no-repeat;
  opacity:0;
  transition:opacity 1.5s var(--ease);
  transition-delay:var(--in,0ms);
}
/* ---- Painted on, not faded in -------------------------------------------
   The panes on that cover were put there with a brush. A pane that arrives by
   opacity transition is a web page; a pane that gets brushed on is the record.
   The glass carries the pane's own mask; the wrapper carries a brush-edged
   wipe with a ragged front and bristle streaks, and the wipe is what animates.
   Two nested masks compose without needing mask-composite. */
.pane__paint{
  position:absolute;inset:0;overflow:visible;
  -webkit-mask-image:url(assets/brush-wipe.png);mask-image:url(assets/brush-wipe.png);
  -webkit-mask-size:300% 100%;mask-size:300% 100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  /* The brush texture runs transparent -> opaque left to right, so position 0
     shows the EMPTY third and 100% shows the painted one. Reversed, this
     masked out every lit pane while looking like a subtle dimming bug. */
  -webkit-mask-position:0 0;mask-position:0 0;
  transition:-webkit-mask-position 1.5s var(--ease),mask-position 1.5s var(--ease);
  transition-delay:var(--in,0ms);
  pointer-events:none;
}
.pane.is-lit .pane__paint{-webkit-mask-position:100% 0;mask-position:100% 0}

/* ---- Pane bleed: CUT ----------------------------------------------------
   `.pane__bleed` was twelve more copies of the masked plate, each blurred
   26px, saturated and screen-blended, to spill each pane's colour onto its
   neighbours. Measured: 0.98% of pixels, mean delta 0.05, MAX 7 of 255.
   Twelve blurred blended layers for something nobody can see. The bloom does
   the neighbour-spill job and measures 16% of pixels at max 51. */

/* The bloom is a RADIAL FIELD, not a drop-shadow.
   drop-shadow traces the mask's alpha, and the mask is a rectangle, so the
   halo came out square - which is the one thing light never looks like. This
   is an ellipse centred on the pane, larger than it, falling off smoothly. */
/* The cursor is a light BEHIND the window, so what reaches you through any
   given pane is that pane's colour. One white source, twelve filters - which
   is what stained glass is for. `--torch` is the pane's share of the light,
   written per pane from the pointer's distance to it. */
.pane__torch{
  position:absolute;
  width:calc(100% / var(--w));height:calc(100% / var(--h));
  left:calc(var(--x) / var(--w) * -100%);top:calc(var(--y) / var(--h) * -100%);
  background:var(--hue);
  -webkit-mask-image:var(--mask);mask-image:var(--mask);
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  mix-blend-mode:screen;
  opacity:calc(var(--torch,0) * .55);
  pointer-events:none;
}
/* A pane that has not had its morning yet still shows a WHISPER of what is
   behind it when the light crosses. Enough to say there is colour waiting,
   not enough to give the reveal away. */
.pane:not(.is-lit) .pane__torch{opacity:calc(var(--torch,0) * .13)}

.pane__bloom{
  position:absolute;inset:-70%;
  background:radial-gradient(closest-side ellipse at 50% 50%,
    var(--hue) 0%, color-mix(in srgb, var(--hue) 45%, transparent) 34%, transparent 72%);
  filter:blur(26px);
  mix-blend-mode:screen;
  opacity:0;transition:opacity 1.6s var(--ease);
  transition-delay:var(--in,0ms);
  pointer-events:none;
}
.pane.is-lit .pane__bloom{opacity:.42}
.pane.is-lit.is-probe .pane__bloom{opacity:.72}

/* LIT GLASS IS THE WHOLE CAMPAIGN AND IT WAS AN EMPTY RULE.
   This selector appeared twice: once setting opacity, once declaring nothing.
   The grade that makes a lit pane read as LIT was never written.

   Measured on the deployed build: at the campaign's midpoint the window had
   travelled 22% of the way from dark to the cover, and four of the five lit
   panes came back at 5-6% saturation. Pane 2 rendered RGB(114,121,114) - 5.8%
   saturation - against #abd4b8 in the artwork, which is 19.3%. A pane was
   reaching under a third of its own colour.

   That is nine of the ten mornings looking like a grey window. Push the glass
   past the muted plate so a lit pane arrives at the colour the band painted. */
.pane.is-lit .pane__glass{
  opacity:1;
  filter:saturate(1.34) brightness(1.2);
}

/* ---- THE FLICKER ---------------------------------------------------------
   `is-settled` is added to <body> 2.3s after arrival by window.js and was
   consumed by NOTHING - zero rules in this file. So the per-pane ignition
   delays, which exist so the sweep can light panes in the order it reaches
   them, stayed live for the entire life of the page.

   The result: hover a pane and `.pane__paint` responds immediately (its delay
   is 0) while the glass, the bleed and the bloom each arrive up to 1.9s later
   and out of step with each other. Four layers of one pane, lighting one after
   another, every time the pointer crossed it. That is the flicker.

   An entrance delay is for the entrance. Once the arrival has played, it has
   to stop applying to everything else the element ever does. */
body.is-settled .pane__glass,
body.is-settled .pane__bloom,
body.is-settled .pane__paint{
  transition-delay:0ms;
  transition-duration:.34s;
}

/* Today's pane keeps a slow breath so it is findable.
   The comment promising this shipped with no rule under it, so on touch -
   where there is no hover - nothing whatsoever distinguished today's pane
   from the four already-lit ones. The only marker was its aria-label, which
   is to say the mechanic was discoverable by screen reader and no other way.
   Light behind the glass, not a 2px inset border, which read as a debug
   outline. */
/* MEASURED FAILURE, SECOND ATTEMPT.
   The first version breathed `.pane__bloom` opacity .58 -> .86 and produced a
   0.95% luminance swing inside the pane - invisible. The bloom is
   `inset:-70%`, blurred 26px and screen-blended, so most of its energy lands
   OUTSIDE the pane, and inside it the glass is already near-saturated, where
   screen-blend adds almost nothing.

   Worse, the marker has to work against hue variance the artwork already has:
   on day 6 the six lit panes measured 99 to 220 luminance and today's was the
   DARKEST of them. A 1% signal cannot compete with a 2.2x spread.

   So it goes on the glass itself, as brightness, which is multiplicative and
   hue-independent - a dark pane brightens by the same PROPORTION as a bright
   one. Not a border: an inset stroke read as a debug outline. */
/* Compositor-friendly. This used to animate `filter` on `.pane__glass`, and a
   filter animation REPAINTS every frame rather than compositing - inside a
   masked, screen-blended stack that is the expensive kind of forever-loop.
   Opacity on a solid overlay is the cheap kind, and `screen` over the pane's
   own colour lightens exactly the way light behind glass does. */
@keyframes panebreathe{
  0%,100%{opacity:0}
  50%    {opacity:.30}
}
.pane__pulse{
  position:absolute;inset:0;
  background:#fff8ec;
  mix-blend-mode:screen;
  opacity:0;pointer-events:none;
}
.pane.is-lit.is-today .pane__pulse{
  animation:panebreathe 4.6s ease-in-out 2.4s infinite;
}
.pane.is-lit .pane__glass{
  opacity:1;
  filter:saturate(1.34) brightness(1.2);
}

/* The bloom still swells with it, so the pane throws more light into the room
   as well as being brighter itself. Secondary, not the signal. */
.pane.is-lit.is-today .pane__bloom{opacity:.66}
/* No `.window` class exists on this page - the element is `#scene.scene` - so
   this selector matched nothing and the site shipped with NO keyboard focus
   ring at all across 25 tabbables. Unprefixed, deliberately: a focus ring is
   not a state, it is a floor. */
:where(button,a,[tabindex]):focus-visible{
  outline:2px solid var(--paper);outline-offset:3px;border-radius:2px;
}
.pane:focus-visible{outline-offset:-3px}

/* ---- Hover -------------------------------------------------------------
   A stained glass window has one interaction language and it is light. So
   reaching for a pane makes MORE light come through it, and the rest of the
   room falls back a step. No lift, no scale, no card-shadow: none of those
   are things glass does. */
@media (hover:hover) and (pointer:fine){
  /* Siblings step back one stop, not three. At .62 the window read as dead
     rather than as one pane being attended to. */
  .scene.is-probing .pane.is-lit:not(.is-probe) .pane__paint{
    filter:brightness(.84) saturate(.94);
  }
  /* The extra light comes from the BLOOM, not from brightening the glass.
     At 1.16 the pane clipped to near-white and lost the colour that is the
     entire point of the record. */
  /* No drop-shadow anywhere. It traces the mask, and the mask is a rectangle,
     so every drop-shadow on this page came out as a square halo. All bloom is
     the radial .pane__bloom field. */
  .pane.is-lit.is-probe .pane__paint{filter:brightness(1.05) saturate(1.06)}
  .pane.is-lit .pane__paint{transition:filter .45s var(--ease),
    -webkit-mask-position 1.5s var(--ease),mask-position 1.5s var(--ease)}
}
.open__close:focus-visible{outline-offset:-2px}

/* ---- Zoomed ------------------------------------------------------------- */
/* Everything that is not the pane you opened gets out of the way. */


.scene.is-zoomed .pane:not(.is-focus) .pane__paint{filter:brightness(.30) saturate(.5)}
.scene.is-zoomed .pane:not(.is-focus) .pane__bloom{opacity:0}
.scene.is-zoomed .pane.is-focus .pane__bloom{opacity:.8}
.scene.is-zoomed .scene__plate{filter:saturate(.05) brightness(.22)}



/* ---- The reveal ---------------------------------------------------------
   Was a separate full-height section below the fold. It is the product, so it
   is in the hero now. The song is the largest thing on the page; everything
   around it is small and quiet so it has somewhere to be loud. */
.reveal{
  display:grid;justify-items:start;gap:6px;
  padding:clamp(14px,2vh,20px) 0 clamp(4px,1vh,10px);
  border-top:1px solid var(--hair);
  width:100%;
}
.reveal__eyebrow{
  margin:0 0 4px;display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
  font:500 var(--step--1)/1 Archivo,sans-serif;letter-spacing:.13em;
  text-transform:uppercase;color:var(--accent,var(--dust));
}
.reveal__eyebrow .sep{color:var(--dust);opacity:.5}
.reveal__label{
  margin:0;font:500 var(--step--1)/1 Archivo,sans-serif;
  letter-spacing:.15em;text-transform:uppercase;color:var(--dust);
}
.reveal__title{
  margin:0;font-family:'For King',Archivo,sans-serif;font-weight:400;
  font-size:var(--display);line-height:.9;letter-spacing:-.005em;
  text-wrap:balance;color:var(--paper);
}
.reveal__feat{margin:2px 0 0;font-size:var(--step-0);color:var(--dust)}

/* ---- The record --------------------------------------------------------- */

.record{background:var(--void)}
.record__head{display:grid;gap:clamp(20px,3vw,42px);align-items:start;
  margin-bottom:clamp(34px,5vh,56px)}
@media (min-width:900px){.record__head{grid-template-columns:1.1fr 1fr}}
.record__iso{display:block;width:min(84vw,420px);height:auto}
.record__blurb{margin:0;font-size:var(--step-1);line-height:1.6;color:#cfc7bd;max-width:34rem}

/* The tracklist obeys the same gate the glass does: an unrevealed song shows
   its number and the morning it lights, and nothing else. */
.track{list-style:none;margin:0;padding:0;border-top:1px solid var(--hair);max-width:44rem}
.track li{display:flex;align-items:baseline;gap:clamp(14px,2vw,26px);
  padding:clamp(13px,1.7vh,19px) 0;border-bottom:1px solid var(--hair)}
.track__n{flex:none;width:2.2ch;font:500 var(--step--1)/1 Archivo,sans-serif;
  color:var(--dust);font-variant-numeric:tabular-nums}
.track__t{font-family:'For King',Archivo,sans-serif;font-size:clamp(19px,2.3vw,27px);line-height:1.05}
.track li.is-locked .track__t{font-family:Archivo,sans-serif;font-size:var(--step-0);
  color:var(--dust);letter-spacing:.12em}
.track li.is-today,.track li.is-today .track__n{color:var(--accent)}
.track__feat{margin-left:auto;text-align:right;font-size:var(--step--1);color:var(--dust)}
.record__formats{display:flex;flex-wrap:wrap;align-items:center;gap:16px;
  margin-top:clamp(28px,4vh,44px)}
.record__note{font-size:var(--step-0);color:var(--dust);max-width:26rem}

/* ---- Tour --------------------------------------------------------------- */

.tour{background:var(--void);display:grid;gap:18px;justify-items:start}
/* A utility heading, sized like one. It was 58px against the song's 54. */
.tour__title{margin:0;font-family:'For King',Archivo,sans-serif;font-weight:400;
  font-size:clamp(24px,2.6vw,36px);line-height:1}
.tour__lede{margin:0;font-size:var(--step-1);line-height:1.6;color:#cfc7bd;max-width:32rem}

/* ---- Footer ------------------------------------------------------------- */

.foot{background:var(--void);border-top:1px solid var(--hair);
  padding:clamp(40px,6vh,72px) clamp(20px,6vw,80px);
  display:grid;gap:20px;justify-items:start}
.foot__crest{display:block;width:clamp(56px,7vw,84px);height:auto;opacity:.9}
.foot__legal{margin:0;font-size:var(--step--1);color:var(--dust)}

/* ---- The player ---------------------------------------------------------
   The client asked for photo, video and audio in one place. Browser-default
   controls would drop a grey pill into a hand-painted room, so the transport
   is ours and it wears the day's colour. */

.player{position:relative;margin-top:22px}
.player__frame{
  position:relative;width:100%;aspect-ratio:4/5;overflow:hidden;
  background:var(--void);border:1px solid var(--hair);
}
.player__frame img,.player__frame video{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
}
.player__frame--audio{aspect-ratio:auto;height:auto;padding:26px;border:0;background:none}

.player__bar{display:flex;align-items:center;gap:14px;margin-top:14px}
.player__play{
  flex:none;width:52px;height:52px;border-radius:50%;
  border:1px solid var(--accent);background:none;color:var(--paper);
  font-size:15px;cursor:pointer;display:grid;place-items:center;
  transition:background .3s var(--ease),color .3s var(--ease);
}
.player__play:hover,.player__play:focus-visible{background:var(--accent);color:var(--void)}
.player__time{
  font:500 var(--step--1)/1 Archivo,sans-serif;color:var(--dust);
  font-variant-numeric:tabular-nums;flex:none;min-width:8ch;
}

/* The scrubber is the waveform for audio and a plain progress line for video.
   Either way it is one element and one colour. */
.player__wave{position:relative;flex:1;height:44px;cursor:pointer}
.player__wave canvas{display:block;width:100%;height:100%}
.player__line{position:relative;flex:1;height:3px;background:var(--hair);cursor:pointer}
.player__fill{position:absolute;inset:0 auto 0 0;width:0;background:var(--accent)}

.player__cap{margin:12px 0 0;font-size:var(--step--1);color:var(--dust)}

/* ---- Buttons ------------------------------------------------------------ */

/* Their own site sets CTAs as equal-width outlined rectangles: transparent
   fill, 1px cream rule, uppercase, letterspaced, generous padding, in a row.
   Mine were sentence-case with a filled accent, which is not their language. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  padding:17px 30px;min-height:54px;
  border:1px solid rgba(236,231,222,.5);
  color:var(--paper);text-decoration:none;
  font:500 var(--step--1)/1 Archivo,sans-serif;
  letter-spacing:.16em;text-transform:uppercase;white-space:nowrap;
  transition:background .3s var(--ease),color .3s var(--ease),border-color .3s var(--ease);
}
.btn:hover,.btn:focus-visible{background:var(--paper);color:var(--void);border-color:var(--paper)}
/* One primary, and it wears the day's colour rather than a house colour. */
.btn--solid{background:var(--accent);border-color:var(--accent);color:var(--void)}
.btn--solid:hover,.btn--solid:focus-visible{background:var(--paper);border-color:var(--paper)}

/* An unavailable control still has to be READABLE. Dimmed to 45% opacity the
   primary CTA measured 1.33:1 against the artwork - the least legible text on
   the page was the thing we most wanted pressed. Full contrast, plainly marked
   as waiting, so it reads as NOT YET rather than as broken.
   MUST sit after .btn--solid: the element carries both classes, they have the
   same specificity, and source order decides. Declared earlier in the file it
   lost, and rendered near-black text on a near-black ground. */
.btn--solid.btn--pending,.btn--pending{
  background:transparent;
  color:#a8a099;          /* 4.3:1 was still under AA; this measures ~6.2:1 */
  border-color:rgba(236,231,222,.30);
  cursor:default;
}

/* Equal-width row, the way theirs sits. */
.btnrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;width:100%;max-width:44rem}

/* ---- DSPs --------------------------------------------------------------
   Real brand marks from simple-icons path data, drawn as currentColor. Never
   freehand a platform mark. */
.dsp{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}
.dsp__btn{
  display:inline-flex;align-items:center;gap:10px;
  padding:13px 20px;min-height:48px;
  border:1px solid var(--hair);color:var(--dust);text-decoration:none;
  font:500 var(--step--1)/1 Archivo,sans-serif;letter-spacing:.12em;text-transform:uppercase;
  transition:color .3s var(--ease),border-color .3s var(--ease);
}
.dsp__btn:hover,.dsp__btn:focus-visible{color:var(--paper);border-color:var(--paper)}
.dsp__btn svg{width:18px;height:18px;fill:currentColor;flex:none}
/* Measured 1.68:1. These are the pre-save button's four siblings: that one was
   fixed by dropping its opacity dim, and twelve lines later the same dim was
   left on these. A fix applied to one surface and not its siblings is the
   defect class, not the individual number. Same treatment as .btn--pending. */
.dsp__btn[aria-disabled="true"]{color:#a8a099;border-color:rgba(236,231,222,.24);
  pointer-events:none}

/* The announce line, in their voice: uppercase, letterspaced, cream. */
.hero__album{
  margin:0;font:500 var(--step-0)/1.4 Archivo,sans-serif;
  letter-spacing:.18em;text-transform:uppercase;color:var(--paper);
}

/* ---- The opened song ---------------------------------------------------- */
/* Rises UNDER the zoomed glass. The artwork is never covered by a card. */

.open{
  position:fixed;inset:0;z-index:40;
  /* The panel is placed by fit-time geometry now (--panel-x), so it sits a
     fixed gutter from the pane instead of being flung at the right edge. */
  display:flex;align-items:center;justify-content:flex-start;
  /* Was pointer-events:none, which meant a click on the dark area outside the
     panel had no target and the tap-outside-to-close handler could never fire.
     Only while open, so the overlay is not an invisible sheet over the page
     the rest of the time. */
  pointer-events:none;
}
.open:not([hidden]){pointer-events:auto}
/* A floor under the whole state, so the zoomed pane reads as lit glass in a
   dark room rather than a panel floating over a web page. */
.open::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to right,transparent 0%,rgba(11,8,6,.15) 34%,rgba(11,8,6,.86) 58%,var(--void) 78%);
  opacity:0;transition:opacity .5s var(--ease);
}
.open.is-in::before{opacity:1}
.open__panel{
  pointer-events:auto;position:relative;
  width:var(--panel-w,min(46vw,520px));
  margin-left:var(--panel-x,0px);
  max-height:82svh;overflow-y:auto;overscroll-behavior:contain;
  /* HORIZONTAL padding belongs here, not in the margin. Desktop was held off
     the edge by `margin-right` alone, and the phone override sets `margin:0`,
     so on a phone the title, the credit and both arrows rendered flush to the
     bezels with the first glyph clipped. Measured titleX=0 at 393 AND 430.
     Padding on the element itself cannot be undone by a margin override. */
  padding:0 clamp(20px,5vw,28px)
          calc(clamp(22px,3vh,32px) + env(safe-area-inset-bottom));
  background:none;border:0;
  transform:translateY(18px);opacity:0;
  transition:transform .5s var(--ease-out) .18s,opacity .4s var(--ease) .18s;
}
.open.is-in .open__panel{transform:none;opacity:1}

/* A hairline in the pane's own colour ties the panel to the glass above it
   without putting a coloured card over the artwork. */


.open__close{
  /* Fixed to the SCREEN, not the panel. Anchored to the panel it sat on the
     last word of the song title - the two collided at every desktop width. In
     a full-screen state the close belongs in the corner of the frame. */
  position:fixed;top:clamp(14px,3vh,30px);right:clamp(14px,3vw,38px);
  z-index:3;width:46px;height:46px;
  background:none;border:0;color:var(--dust);font-size:28px;line-height:1;cursor:pointer;
}
/* The panel takes focus on open, not the close button. Focusing the × drew a
   2px ring around it the instant the pane opened - clipped by the panel's own
   overflow, so it rendered as a half-drawn box in the corner of the signature
   shot. A dialog container is the conventional target anyway: the first Tab
   then lands on the close button, ring and all. */
.open__panel:focus{outline:none}
.open__close:hover{color:var(--paper)}

.open__day{margin:0 0 10px;font:500 var(--step-0)/1 Archivo,sans-serif;
  letter-spacing:.005em;color:var(--hue,var(--dust))}
.open__title{margin:0;font-family:'For King',Archivo,sans-serif;
  font-size:clamp(30px,5vw,48px);font-weight:400;line-height:.98;letter-spacing:-.005em}
.open__feat{margin:9px 0 0;font-size:var(--step-0);color:var(--dust)}
/* One line from the song, in the pane's own colour. The cheapest thing for
   management to send and the strongest thing on the panel. */
.open__lyric{
  margin:22px 0 0;padding:0 0 0 16px;
  border-left:2px solid var(--hue,var(--room));
  font-size:clamp(18px,2.4vw,24px);line-height:1.35;
  font-weight:400;font-style:normal;color:var(--paper);
  text-wrap:balance;
}
.open__blurb{margin-top:20px;font-size:var(--step-1);line-height:1.6;color:#d3ccc2}
.open__media{margin-top:20px}
/* An image that is already the glass above must not be printed again below. */
.open__media:empty{margin-top:0}
.open__media img,.open__media video{width:100%;display:block;border-radius:2px}
.open__media audio{width:100%}

/* The panel does not arrive as one block. Each line lands after the one above
   it, so reading it feels like the song being introduced rather than a card
   appearing. */
.open__day,.open__title,.open__feat,.open__lyric,.open__blurb,.open__media,.open__nav{
  opacity:0;transform:translateY(12px);
  transition:opacity .55s var(--ease-out),transform .55s var(--ease-out);
}
.open.is-in .open__day  {opacity:1;transform:none;transition-delay:.26s}
.open.is-in .open__title{opacity:1;transform:none;transition-delay:.34s}
.open.is-in .open__feat {opacity:1;transform:none;transition-delay:.40s}
.open.is-in .open__lyric{opacity:1;transform:none;transition-delay:.48s}
.open.is-in .open__blurb{opacity:1;transform:none;transition-delay:.56s}
.open.is-in .open__media{opacity:1;transform:none;transition-delay:.64s}
.open.is-in .open__nav  {opacity:1;transform:none;transition-delay:.72s}

.open__nav{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:22px}
.open__arrow{
  width:46px;height:46px;flex:none;
  background:none;border:1px solid var(--hair);color:var(--paper);
  font-size:17px;cursor:pointer;
  transition:border-color .3s var(--ease),background .3s var(--ease);
}
.open__arrow:hover:not(:disabled){border-color:var(--paper)}
.open__arrow:disabled{opacity:.28;cursor:default}
.open__hint{font:400 var(--step-0)/1 Archivo,sans-serif;color:var(--dust);text-align:center}

.slot{
  border:1px dashed var(--hair);padding:22px;
  font-size:var(--step-0);line-height:1.6;color:var(--dust);text-align:center;
}
.slot strong{display:block;color:var(--paper);font:500 var(--step-0)/1 Archivo,sans-serif;
  letter-spacing:.005em;margin-bottom:10px}
.locked .btn{margin-top:16px}

/* ---- Phones ------------------------------------------------------------- */
/* The window goes fixed and full-bleed. Nothing about opening a pane changes
   layout after this, which is the whole reason the zoom can be one transform. */

/* ---- Responsive ---------------------------------------------------------
   Measured across nine real device sizes rather than guessed at. The numbers
   that drove these rules:

   - The plate is 1.64:1 and a phone is 0.46:1, so the window can never fill a
     portrait screen. Measured 35-43% of viewport height on iPhones, and it
     cannot grow without cropping panes that have to stay tappable. So the
     phone layout USES the rest of the height deliberately instead of leaving
     the artwork stranded in a dark field.
   - The lockup rendered at 86% of viewport width on phones. Too dominant.
   - Landscape phone (852x393) OVERFLOWED the hero. That is the break.
   - Smallest pane tap target measured 78px on an iPhone SE, so touch targets
     were never the problem.
   ------------------------------------------------------------------------ */

/* ---- Tablet ---- */
@media (max-width:1024px){
  section{padding:clamp(52px,8vh,88px) clamp(20px,5vw,52px)}
  .hero__lockup{width:min(70vw,460px)}
  .record__head{grid-template-columns:minmax(0,1fr)}
}

/* ---- Phone ---- */
@media (max-width:600px){
  /* The window gets the top of the screen to itself and the page begins
     underneath it. Overlapping the two put the lockup straight across the
     panes and made the whole mechanic untappable on a phone. */
  .scene{height:56svh}
  .scene::after{background:linear-gradient(to top,rgba(11,8,6,.9) 0%,transparent 42%)}
  /* Was 58svh, which left a measured 166px (19% of the viewport) of dead
     black between the bottom of the glass at 42% and the lockup at 61%.
     The glass bottom sits at ~42svh, so this closes the gap to ~38px. */
  .hero{padding-top:46svh;min-height:auto;justify-content:flex-start}
  .today{min-height:auto;padding-top:24px}
  /* The scene is 56svh at rest so the page can begin under the window. With a
     pane open that clip is the ceiling on how big the pane can ever get - it
     capped the zoom at 17% of the screen. Open state gets the whole screen. */
  body.is-open .scene{height:100svh}
  .open__panel{max-height:52svh}
  .hero{
    justify-content:flex-end;
    gap:14px;
    padding-bottom:calc(26px + env(safe-area-inset-bottom));
    /* The scrim lives on the scene, not here. On a phone it just needs a
       floor under the type. */
    background:linear-gradient(to top,rgba(11,8,6,.97) 0%,rgba(11,8,6,.9) 34%,transparent 46%);
  }
  .hero__lockup{width:min(64vw,300px)}
  .hero__date{font-size:16px}
  .btnrow{grid-template-columns:1fr;gap:10px}
  .hero__note{font-size:14px}

  .today{align-items:end}
  .today__inner{padding-bottom:clamp(20px,5vh,44px)}
  .today__title{font-size:clamp(32px,10.5vw,46px)}
  .today__feat{font-size:16px}

  .track li{gap:14px;padding:14px 0}
  .track__t{font-size:21px}
  .track__feat{margin-left:0;flex-basis:100%;text-align:left;padding-left:calc(2.2ch + 14px)}
  .record__iso{width:min(78vw,320px)}
  .record__formats{gap:12px}
  .record__formats .btn{width:100%;text-align:center}

  /* 50svh hid 39-41% of the panel's own content on a phone - the bonus media,
     the entire reason for the tap, sat below the fold of the sheet holding it,
     in a nested scroller under the thumb with no scroll cue. */
  .open__panel{width:100%;margin:0;max-height:72svh;border-radius:0}
  .open{align-items:flex-end;justify-content:center}
  .open::before{background:linear-gradient(to top,var(--void) 0%,rgba(11,8,6,.96) 38%,
    rgba(11,8,6,.4) 66%,transparent 100%)}
  .open__title{font-size:clamp(26px,8vw,34px)}
  .player__frame{aspect-ratio:1/1}

  /* Blur is the expensive part of this page and a phone GPU pays for it every
     frame. Halve it rather than ship a beautiful page that stutters. */
}

/* ---- Short viewport: landscape phone ----
   852x393 overflowed the hero outright. Anything under 480 tall gets a
   compressed hero rather than a broken one. */
/* `(pointer:coarse)` is load-bearing: without it a 1280x800 laptop at 200%
   browser zoom (= 640x400 CSS) matched this landscape-PHONE query and got
   `display:none` on the one sentence that explains the campaign - WCAG 1.4.4
   content loss aimed exactly at the people who zoom. */
@media (max-height:520px) and (orientation:landscape) and (pointer:coarse){
  .hero{min-height:100svh;gap:10px;padding-bottom:calc(16px + env(safe-area-inset-bottom))}
  .hero__lockup{width:min(34vw,260px)}
  .hero__date,.hero__note{font-size:14px}
  .hero__note{display:none}
  .hero__cta .btn{padding:11px 18px}
  .today{min-height:100svh}
  .today__title{font-size:clamp(26px,5vw,38px)}
  .open__panel{max-height:88svh}
  section{padding:32px clamp(20px,4vw,44px)}
}

/* ---- Coarse pointers ----
   Hover states are meaningless on touch and the pane label lives on hover, so
   touch gets it on the day's pane permanently instead. */
@media (pointer:coarse){
  .pane.is-lit.is-today   .btn{padding:15px 24px}
}

/* ---- Windows High Contrast / forced-colors ------------------------------
   This design is carried ENTIRELY by masks, blend modes and filters, and
   forced-colors switches all of that off. Measured with forced colours on:
   lit panes rendered rgb(254,253,253) and unlit panes rgb(253,253,253) - the
   same flat white, standard deviation 0.005. The mechanic did not merely look
   worse, it did not exist: no pane could be told from any other.

   So in forced-colors the page stops pretending to be a photograph and states
   the same information structurally, which is what forced-colors is for. */
@media (forced-colors:active){
  .pane__bloom,.pane__torch,.sweep,.grain,
  .scene__rimwrap,.scene__plate,.scene__band{display:none!important}
  .pane{
    border:2px solid CanvasText;
    background:Canvas;
    forced-color-adjust:none;
  }
  .pane .pane__paint{display:none}
  /* A lit pane is filled, an unlit one is an empty outline, and today's is
     marked by the system's own highlight rather than by brightness. */
  .pane.is-lit{background:CanvasText}
  .pane.is-lit.is-today{background:Highlight;border-color:Highlight}
  .open__panel{background:Canvas;border:2px solid CanvasText;padding:20px}
}

@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;transition-delay:0ms!important}
  /* Collapsing `panebreathe` to one .001ms iteration lands it on its 100%
     keyframe - brightness .97 - which left today's pane DIMMER than its
     neighbours and gave a reduced-motion visitor no marker at all, on the one
     thing the whole page asks them to find. Motion off is not marker off:
     hold the peak statically instead. */
  .pane.is-lit.is-today .pane__glass{
    animation:none!important;
    filter:saturate(1.56) brightness(1.62)!important;
  }
}

/* ---- Hover, on machines that have one -------------------------------------
   Not a colour change: leaning toward a pane should feel like the light
   strengthening behind that piece of glass. */
@media (hover:hover) and (pointer:fine){
  .pane.is-lit:hover{z-index:4}
  /* Was brightness(1.14) saturate(1.05) - BELOW the resting grade of
     brightness(1.2) saturate(1.34) - so hovering a lit pane made it darker and
     flatter than leaving it alone. Hover has to be an increase on the resting
     value, not a replacement written before that value existed. */
  .pane.is-lit:hover .pane__glass{filter:saturate(1.46) brightness(1.38)}
  .pane.is-lit:hover .pane__glass{transition:opacity 1.5s var(--ease),filter .45s var(--ease)}
  /* Today's pane is animating; a static hover filter would fight the keyframes
     and freeze the marker. Let the breathe keep running and lift the bloom. */
  .pane.is-lit.is-today:hover .pane__glass{filter:none}
  .pane.is-lit.is-today:hover .pane__bloom{opacity:.84}
  }
