  /* Newsreader, served from this site rather than Google Fonts, so a visit sends
     nothing to Google -- nothing leaves the page for anyone until a visitor
     says yes to the pixel. Variable files (weight and optical size), the same
     ones Google serves, latin and latin-ext only. OFL, licence in /fonts. */
  @font-face{font-family:"Newsreader";font-style:normal;font-weight:400 500;font-display:swap;
    src:url(/fonts/newsreader-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
  @font-face{font-family:"Newsreader";font-style:normal;font-weight:400 500;font-display:swap;
    src:url(/fonts/newsreader-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
  @font-face{font-family:"Newsreader";font-style:italic;font-weight:400;font-display:swap;
    src:url(/fonts/newsreader-italic-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
  @font-face{font-family:"Newsreader";font-style:italic;font-weight:400;font-display:swap;
    src:url(/fonts/newsreader-italic-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
:root{
    --night:#0a0d08;
    --edge:#21251d;   /* sampled from the page's own top edge */
    --lantern:#f1dfa4;
    --paper:#efe9da;
    --muted:rgba(239,233,218,.78);
    --pad:20px;
  }
  *{box-sizing:border-box;margin:0}
  /* the glow deliberately bleeds past both edges; clip rather than hidden, since
     hidden still leaves a scroll container the page can be dragged around in.
     hidden first as the fallback for browsers without clip. */
  /* overscroll-behavior on both axes, not just x. The page is one screen with
     nothing to scroll, so a drag was doing nothing but rubber-banding the whole
     thing — worst inside in-app browsers, which wrap their own scroller around
     it. none also takes out pull-to-refresh, which has nothing to refresh here. */
  html,body{color:var(--paper);overflow-x:hidden;overflow-x:clip;overscroll-behavior:none}
  /* only the root's colour reaches iOS's pull-to-refresh overscroll — a fixed
     background is clipped to the viewport — so match it to the page's top edge
     and the strip reads as more garden instead of a black void */
  /* svh everywhere below, never dvh: dvh grows as Safari's chrome collapses on
     scroll, and the title's font-size is partly dvh-based, so the type resized
     mid-scroll. svh is the chrome-visible height and never moves. text-size-adjust
     stops iOS inflating the type on its own for good measure. */
  html{background:var(--edge);-webkit-text-size-adjust:100%;text-size-adjust:100%}
  body{background:var(--night)}
  body{font-family:"Newsreader",Georgia,serif;-webkit-font-smoothing:antialiased;min-height:100svh;min-height:min(100svh,var(--vh,100svh))}

  .bg{position:fixed;inset:0;z-index:0;overflow:hidden}
  /* inset gives the parallax room to move without exposing an edge */
  @property --bgs{syntax:'<number>';inherits:false;initial-value:1}
  @keyframes settle{from{--bgs:1.07}to{--bgs:1}}
  .bg::before{content:"";position:absolute;inset:-40px;
    background:url(garden-bg.webp) 32% center/cover no-repeat;
    filter:blur(5px) brightness(.66) saturate(1.12);
    transform:translate3d(var(--px,0px),var(--py,0px),0) scale(var(--bgs,1));
    will-change:transform;
    animation:settle 2.8s cubic-bezier(.16,.68,.24,1) backwards}
  /* heavy only where type sits: top for the name, bottom for the title block */
  .bg video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
    /* Each canvas comes in two cuts from its full-resolution original: 1080x1920
       for portrait screens, and a landscape band for wide ones (1440 or 1920
       across), so neither gets stretched far past its pixels and neither needs
       blur to hide it. */
    /* Opaque from the first paint, so the garden underneath never shows through
       before the clip has decoded its first frame. That opaque layer is the clip's
       own first frame as a still (--still-*, set on the page's <video> and
       preloaded in its <head>): a page change painted before the clip was ready,
       and a plain dark fill there was a dark blink every time you went to a
       record (Carter, 10 Oct 2026). Night under the still, in case that is late too. */
    background:var(--night) var(--still-tall,none) center/cover no-repeat;
    filter:brightness(.8) saturate(1.06)}
  /* the same split as the <source>s: the wide cut from square outwards */
  @media (min-aspect-ratio:1/1){.bg video{background-image:var(--still-wide,none)}}
  .bg::after{content:"";position:absolute;inset:0;
    background:linear-gradient(to bottom,
      rgba(10,13,8,.52) 0%,
      rgba(10,13,8,.30) 26%,
      rgba(10,13,8,.44) 56%,
      rgba(10,13,8,.62) 84%,
      rgba(10,13,8,.70) 100%)}
  .page{position:relative;z-index:1;max-width:460px;margin:0 auto;min-height:100svh;min-height:min(100svh,var(--vh,100svh));display:flex;flex-direction:column;
    /* --consent-h is the bar's measured height while it is showing, 0 otherwise.
       Without it the bar sits directly over the Listen button -- the one thing on
       the page -- so the column gives up that much room instead. */
    padding:calc(8px + env(safe-area-inset-top)) var(--pad) calc(clamp(16px,5.04svh,47px) + env(safe-area-inset-bottom) + var(--consent-h,0px));
    transition:padding-bottom .5s cubic-bezier(.16,.68,.24,1)}

  /* name centred on the page's own axis, so it shares the centre line with the
     kicker, cover, title and button. The nav comes out of the flow to do it —
     otherwise the name would centre in whatever space the icons leave over. */
  header{flex:none;display:flex;align-items:center;justify-content:center;height:48px;position:relative}
  .links{position:absolute;left:-11px;top:50%;transform:translateY(-50%)}
  /* Mirrors .links on the other side, so the name stays the only thing in flow
     and keeps the exact centre it had when it was up here alone.

     Centred by stretching to the header's height, not by translateY(-50%) like
     .links. This element runs focusIn, whose keyframes set transform themselves,
     and an animated transform replaces the static one rather than adding to it --
     so for the length of the reveal it sat half its height low, then jumped into
     place as the animation released it. .links never animates transform, which
     is why only this one flicked. */
  .nav{position:absolute;right:-10px;top:0;bottom:0;display:flex;align-items:center;
    font-size:.6875rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
  /* The pages (Oct 10, 2026, Carter's design), on the right, socials moved left as
     in his September sketch. Desktop spells them out -- Home, Music, Privacy policy,
     the current one lit. A phone gets a hamburger that drops Home and Music down
     one after the other, with Privacy policy set a little lower, under a line;
     the lines fold into an X while it's open. */
  .nav-link{display:none;height:100%;align-items:center;padding:0 10px;color:inherit;
    text-decoration:none;transition:color .2s ease}
  .nav-link:hover,.nav-link[aria-current]{color:var(--paper)}
  /* Privacy's old extra room, split around Music: Home and Privacy stay put and
     Music sits centred between them, the gaps equal */
  .nav-link[href="/music"]{margin:0 7px}
  .nav-burger{display:flex;align-items:center;height:100%;padding:0 10px;font:inherit;color:inherit;
    background:none;border:0;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:color .2s ease}
  .nav-burger:hover,.nav-burger[aria-expanded="true"]{color:var(--paper)}
  .nav-burger svg{width:21px;height:21px;display:block;overflow:visible}
  .nav-burger path{transition:transform .28s cubic-bezier(.16,.68,.24,1),opacity .2s ease;
    transform-box:fill-box;transform-origin:center}
  .nav-burger[aria-expanded="true"] path:nth-child(1){transform:translateY(5px) rotate(45deg)}
  .nav-burger[aria-expanded="true"] path:nth-child(2){opacity:0}
  .nav-burger[aria-expanded="true"] path:nth-child(3){transform:translateY(-5px) rotate(-45deg)}
  @media (min-width:900px){
    .nav-link{display:flex}
    .nav-burger,.nav-list{display:none}
  }
  /* Phones: the hamburger turns the header itself into the menu (option D, Oct 10,
     2026). The name and the socials fade out, and Home, Music and Privacy policy
     write themselves in from the left edge, left to right -- the same soft wipe the
     name arrives with -- so the words read the way they appear even with the button
     on the right. Transitions, not keyframes: closing, or tapping again halfway,
     reverses smoothly from wherever it is instead of jumping. The open state is html.menu-open,
     set before first paint and kept by app.js. */
  .nav-list{position:absolute;left:-9px;right:44px;top:0;bottom:0;margin:0;padding:0;list-style:none;
    display:flex;align-items:center;font-size:.6875rem;letter-spacing:.16em;text-transform:uppercase;
    visibility:hidden;transition:visibility 0s linear .45s}
  .nav-list a{display:flex;align-items:center;height:48px;padding:0 9px;color:var(--muted);
    text-decoration:none;white-space:nowrap;text-shadow:0 1px 14px rgba(0,0,0,.5);
    -webkit-mask-image:linear-gradient(to right,#000 56%,transparent 66%);
    mask-image:linear-gradient(to right,#000 56%,transparent 66%);
    -webkit-mask-size:300% 100%;mask-size:300% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
    -webkit-mask-position:100% 0;mask-position:100% 0;
    transition:-webkit-mask-position .3s cubic-bezier(.4,0,.6,1),mask-position .3s cubic-bezier(.4,0,.6,1),color .2s ease}
  /* closing: the last word goes first, quickly */
  .nav-list li:nth-child(2) a{transition-delay:.05s}
  .nav-list li:nth-child(1) a{transition-delay:.1s}
  .nav-list a:hover,.nav-list a[aria-current]{color:var(--paper)}
  /* Privacy sits apart by the X, so reaching for Music doesn't land on it -- same
     shading and hover as the others, just not crowded in with them */
  .nav-list li:last-child{margin-left:auto}
  @media (max-width:899px){
    .artist,.links{transition:opacity .3s ease .25s}
    .menu-open header .artist,.menu-open header .links{opacity:0;pointer-events:none;transition:opacity .2s ease}
    .menu-open header .nav-list{visibility:visible;transition:visibility 0s}
    /* opening: left to right, once the name has gone */
    .menu-open header .nav-list a{-webkit-mask-position:0 0;mask-position:0 0;
      transition-duration:.55s,.55s,.2s;transition-timing-function:cubic-bezier(.2,.36,.34,.96)}
    .menu-open header .nav-list li:nth-child(1) a{transition-delay:.12s}
    .menu-open header .nav-list li:nth-child(2) a{transition-delay:.2s}
    .menu-open header .nav-list li:nth-child(3) a{transition-delay:.28s}
  }
  @media (max-width:359px){.nav-list a{padding:0 6px;letter-spacing:.1em}}
  @media (prefers-reduced-motion:reduce){
    .nav-list a{-webkit-mask-image:none;mask-image:none;opacity:0;transition:opacity .2s ease}
    .menu-open header .nav-list a{opacity:1}
  }
  /* the name is the way home -- the convention everywhere, and it costs no room */
  a.artist{text-decoration:none}
  /* below this the centred name and the icons would touch */
  @media (max-width:339px){
    header{justify-content:space-between}
    .links,.nav{position:static;transform:none}
    .links{order:-1}
    .nav{order:1}
    .nav-burger{padding-right:0}
  }
  .artist{font-style:italic;font-size:1.375rem;color:rgba(239,233,218,.85)}
  /* written, not typed: a soft-edged wipe travels left to right in one
     continuous pass. If masks aren't supported the properties are ignored
     and the text just shows. */
  .artist{
    /* geometry matters here: at rest the element must sit ENTIRELY inside the
       black band, and when swept fully outside it. 300% wide with black to 52%
       gives a black run of 156% of the element and a clear run of 102% — at 0%
       the element covers only the mask's first 33%, at 100% only its last 33%. */
    /* soft band was 42% of the element wide, so letters ghosted up rather than
       being revealed. 56->66 narrows it to 30% — still no hard edge, but the
       stroke has somewhere definite to arrive. */
    -webkit-mask-image:linear-gradient(to right,#000 56%,transparent 66%);
    mask-image:linear-gradient(to right,#000 56%,transparent 66%);
    -webkit-mask-size:300% 300%;mask-size:300% 300%;
    -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
    animation-name:write;animation-duration:1.5s;animation-delay:.3s;
    -webkit-mask-position:0% center;mask-position:0% center;
    animation-timing-function:cubic-bezier(.16,.68,.24,1);animation-fill-mode:backwards}
  @keyframes write{
    from{-webkit-mask-position:100% center;mask-position:100% center}
    to{-webkit-mask-position:0% center;mask-position:0% center}}
  .links{display:flex}
  .links a{width:44px;height:44px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted)}
  .links svg{width:21px;height:21px}

  /* the cover lights the room around it */
  .cover{flex:1 1 0;min-height:0;margin-top:clamp(12px,3svh,32px);
    container-type:size;display:flex;flex-direction:column}
  /* sinks to the foot of its free space so the garden shows above it, centred
     on the same axis as the type below */
  /* margin-top auto keeps the slack above the square, which is where it sat when
     the kicker was up here holding it. The 31px reserve that used to come off
     100cqh went with the kicker — nothing shares the container now. */
  .sq{position:relative;width:min(90cqw,100cqh);aspect-ratio:1;isolation:isolate;
    margin:auto auto 0}
  /* bottom spill kept short: it would otherwise wash out the title and add stray scroll height */
  .glow{position:absolute;top:-18%;left:-18%;z-index:-1;width:136%;height:124%;object-fit:cover;
    filter:blur(52px) saturate(.5) brightness(.95);opacity:.34;pointer-events:none;
    animation:breathe 7s ease-in-out infinite alternate}
  @keyframes breathe{from{opacity:.26}to{opacity:.4}}
  .art{position:relative;width:100%;height:100%;border-radius:4px;overflow:hidden;
    box-shadow:0 24px 60px -20px rgba(0,0,0,.9);
    transform:translate3d(var(--ax,0px),var(--ay,0px),0);will-change:transform}
  .art img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}

  header,.release{position:relative;z-index:2}
  .release{flex:none;margin-top:clamp(20px,4.5svh,48px);text-align:center}
  h1,.sub,.artist{text-shadow:0 1px 18px rgba(0,0,0,.45)}

  /* The gallery. A release page is built to be exactly one screen; this one
     grows with the catalogue, so it starts at the top and is allowed to run on.
     Its top-left link is Home rather than Music -- a link to the page you are
     already on is no use, and this is the one page with somewhere else to be. */
  .page--list{justify-content:flex-start}
  /* Two across at every width, filling in rows of two as the catalogue grows.
     Three across left an empty slot beside two releases and shrank each cover to
     141px on a desktop; one per row made the page a feed rather than a gallery.
     Capped so a wide window does not blow the covers up past what they need. */
  .grid{list-style:none;margin:clamp(20px,5svh,48px) auto 0;padding:0;
    display:grid;grid-template-columns:repeat(2,1fr);gap:clamp(14px,3.6vw,28px);
    max-width:640px;width:100%}
  .grid a{display:block;text-decoration:none;color:inherit;
    transition:transform .22s cubic-bezier(.16,.68,.24,1)}
  .grid img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;
    border-radius:10px;box-shadow:0 10px 30px -12px rgba(0,0,0,.7)}
  .grid .t{display:block;margin-top:11px;font-size:1.0625rem;color:var(--paper)}
  .grid .m{display:block;margin-top:3px;font-size:.6875rem;letter-spacing:.16em;
    text-transform:uppercase;color:var(--muted)}
  @media (hover:hover){.grid a:hover{transform:translateY(-3px)}}


  /* Not found. Centred in the column rather than hung off the top, because there
     is nothing else on the page to balance it against. */
  /* Header stays at the top where a header goes -- centring the whole column put
     it halfway down the screen with a hole above it. The message takes the slack
     instead, so it sits in the middle of what is left. */
  .page--lost{text-align:center}
  .lost{margin:auto 0}
  /* The number is the page. It says at a glance that the address was wrong rather
     than that the site is broken, which a sentence alone could not. */
  .lost .code{margin:0;font-size:clamp(4.5rem,22vw,8rem);line-height:.92;
    letter-spacing:-.02em;color:var(--paper)}
  .lost h1{margin-top:6px;font-size:clamp(1.25rem,5vw,1.75rem);font-weight:400;
    letter-spacing:0;color:var(--muted)}
  .lost-go{display:flex;justify-content:center;gap:clamp(16px,6vw,32px);
    margin:clamp(22px,5svh,40px) 0 0}
  .lost-go a{font-size:.6875rem;letter-spacing:.16em;text-transform:uppercase;
    color:var(--muted);text-decoration:none;padding:10px 2px;
    border-bottom:1px solid rgba(239,233,218,.22);transition:color .2s ease,border-color .2s ease}
  .lost-go a:hover{color:var(--paper);border-color:rgba(239,233,218,.5)}


  /* the privacy page: a reading column under the same header as every page */
  .doc{width:100%;max-width:600px;margin:clamp(20px,5svh,48px) auto 0;padding-bottom:clamp(32px,8svh,72px)}
  .doc h1{font-size:clamp(2rem,9vw,2.75rem);font-weight:400;letter-spacing:-.01em;line-height:1.05;color:var(--paper);margin:0}
  .doc .doc-date{margin:10px 0 1.6em;font-size:.6875rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
  .doc h2{margin:2em 0 .4em;font-size:1.0625rem;font-weight:500;color:var(--paper)}
  .doc p{margin:0 0 .9em;font-size:1rem;line-height:1.6;color:rgba(239,233,218,.78)}
  /* the same soft shadow the title and name carry, so text over the garden's bright
     patches stays crisp */
  .doc{text-shadow:0 1px 14px rgba(0,0,0,.55)}
  .doc .choice{text-shadow:none}
  .doc a{color:var(--paper);text-underline-offset:2px}
  /* questions sit apart, like a small footer to the page */
  .doc .doc-ask{margin-top:2.2em}
  /* the sign-off, then the back-to-top arrow centred beneath it at the foot */
  .doc-end{display:flex;flex-direction:column;align-items:center;margin-top:2.4em;text-align:center}
  .doc .doc-sign{margin:0;color:var(--paper)}
  .to-top{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:18px;padding:8px 14px;
    border:0;background:none;color:var(--muted);cursor:pointer;font:inherit;font-size:.6875rem;
    letter-spacing:.16em;text-transform:uppercase;-webkit-tap-highlight-color:transparent;transition:color .2s ease}
  .to-top:hover{color:var(--paper)}
  .to-top svg{width:21px;height:21px;display:block}
  .choice{margin:.2em 0 1.2em;padding:18px;border-radius:12px;background:rgba(13,17,10,.55);
    box-shadow:inset 0 0 0 1px rgba(239,233,218,.12)}
  .choice .choice-now{margin:0 0 14px;color:var(--paper)}
  .choice [data-when]{display:none}
  .choice[data-state="yes"] [data-when="yes"],.choice[data-state="no"] [data-when="no"],
  .choice[data-state="gpc"] [data-when="gpc"],.choice[data-state="unset"] [data-when="unset"]{display:inline}
  .choice .consent-act{flex-wrap:wrap}
  .choice button[aria-pressed="true"]{box-shadow:inset 0 0 0 1.5px var(--paper)}
  /* On a desktop the page is one screen, so there is nothing to scroll and no way
     back up to offer. The column widens a touch and the gaps tighten so it fits a
     laptop window; a shorter window just scrolls. */
  @media (min-width:900px){
    .doc{max-width:660px;margin-top:clamp(16px,4svh,40px);padding-bottom:0}
    /* the global h1 pads itself for the title's mask sweep; a heading here has no sweep */
    .doc h1{padding-block:0}
    /* no lone word left on a paragraph's last line ("policy.") */
    .doc p{text-wrap:pretty}
    .to-top{display:none}
  }
  /* line-height .9 puts the cap-height outside the element's own box. The padding
     was added to contain the glyphs for a mask sweep that no longer exists, but
     it is not free: it grows the box from 54px to 88px, and the column's flex
     distribution is tuned around that height. Take it out and everything below
     the title moves up 6px. It stays. */
  h1{font-weight:500;font-size:clamp(2.75rem,min(16vw,7.5svh),4.75rem);line-height:.9;letter-spacing:-.035em;font-variation-settings:"opsz" 72;
    padding-block:.28em;margin-block:-.28em}
  /* was the release date; now the kicker, moved down out of the artwork's way.
     Italic to set it apart from the title it sits under and to rhyme with the
     masthead. Renamed off .when because it no longer says when anything is. */
  .sub{margin-top:12px;font-size:1.0625rem;color:var(--paper);font-style:italic}
  /* The menu only covers the title when it opens upward, so only then does the
     title get out of the way -- it dissolves rather than vanishing, the same
     defocus the page arrives with. Dropping downward covers nothing, and fading
     the title there would just lose it for no reason. */
  h1,.sub{transition:opacity .3s ease,filter .3s ease}
  .menu-up h1,.menu-up .sub{opacity:0;filter:blur(7px)}

  /* One bar, two destinations. Three columns with equal ends, and the Spotify
     link spans the first two — so the label sits in the middle column, which is
     the bar's own centre line, even though the bar is split unevenly.
     The ends are 66px, not 58: the toggle is the smaller target and the one you
     do not want hit by accident, so it takes 8px off the Spotify half. Widening
     only the right end would have pushed the label off centre, so both grew. */
  .presave{margin-top:clamp(16px,6.33svh,59px);display:grid;grid-template-columns:66px 1fr 66px;
    height:68px;border-radius:14px;overflow:hidden;
    background:var(--lantern);color:var(--night);font-size:1.1875rem;font-weight:500;
    box-shadow:0 8px 26px -10px rgba(0,0,0,.6);transition:transform .15s ease}
  .go{grid-column:1 / 3;display:grid;grid-template-columns:66px 1fr;align-items:center;
    color:inherit;text-decoration:none}
  /* A real crossfade: both labels sit in the same cell, so one is always on screen.
     Fading a single element out and back left a beat with the button blank, which
     read as a blink. They pass each other vertically — the outgoing one lifts away,
     the incoming one rises into place, the same direction the menu opens. */
  #label{position:relative;align-self:stretch}
  /* They dissolve through blur, not straight opacity. Two sharp words crossfading
     are both legible at the midpoint and read as a ghosted double-image; blurred,
     the overlap is just a soft haze resolving into the new word. It is also the
     gesture the rest of the page already uses to arrive. */
  #label span{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
    transition:opacity .34s ease,filter .34s ease,transform .4s cubic-bezier(.16,.68,.24,1)}
  #label .l1{opacity:1;filter:blur(0px);transform:none}
  /* the open-state rules have to carry #label too, or the resting rule's ID
     outranks them and both labels stay hidden */
  #label .l2{opacity:0;filter:blur(5px);transform:translateY(6px)}
  .presave.alt-label #label .l1{opacity:0;filter:blur(5px);transform:translateY(-6px)}
  .presave.alt-label #label .l2{opacity:1;filter:blur(0px);transform:none}
  @media (prefers-reduced-motion:reduce){
    #label span{transition:opacity .34s ease}
    #label .l2,.presave.alt-label #label .l1,
    #label .l1,.presave.alt-label #label .l2{transform:none;filter:none}
  }
  /* inset shadow rather than a border, so it can't add a pixel to the bar's height */
  .alt{grid-column:3;display:flex;align-items:center;justify-content:center;cursor:pointer;
    color:inherit;text-decoration:none;box-shadow:inset 1px 0 0 rgba(10,13,8,.15)}
  .presave svg{width:22px;height:22px;display:block;margin:0 auto}
  .alt svg{width:18px;height:18px;opacity:.66}
  /* the page's focus ring is cream — invisible on a cream button — and the bar
     clips its own overflow, so these need a dark ring set inside the edge */
  .go:focus-visible,.alt:focus-visible{outline:2px solid var(--night);outline-offset:-5px}
  .presave:has(:active){transform:scale(.985)}

  /* The menu is the bar, repeated. Same height, same width, same cream, same
     corner — and the same 58px|1fr|58px columns, so every label lands on the very
     same centre line as "Listen Now" below it and the marks stack down the left. */
  .lw{position:relative}
  /* 2x2 -- the shape the toggle's own icon draws. Stacked, the four rows are
     ~306px and bury the title behind them; paired they are 142px and the artwork
     and most of the page stay visible.

     The tiles shorten on a short window, because that is where the sheet runs out
     of room between the artwork and the bar and starts climbing over the artwork
     -- by 16px at 320x568, 13px at 360x640, 8px at 375x667. The artwork is the
     one thing this menu must not cover, so the tiles give way instead. Anything
     ~740px tall and up is unaffected and stays at 68px.

     The 44px floor is set by a phone held sideways, which is the shortest window
     that matters: 844x390 leaves 103px between artwork and bar and a 54px floor
     wanted 116. Portrait never reaches the floor. */
  .sheet{position:absolute;bottom:calc(100% + 10px);left:0;right:0;margin:0;padding:0;list-style:none;
    display:grid;grid-template-columns:1fr 1fr;gap:8px;
    opacity:0;visibility:hidden;transform:translateY(12px) scale(.985);transform-origin:bottom center;
    transition:opacity .2s ease,transform .26s cubic-bezier(.16,.68,.24,1),visibility 0s linear .26s}
  /* Direction is chosen at open time by measuring, not by screen width: drop only
     when the rows actually fit below. On a wide but short window dropping ran the
     menu 52px past the bottom of the viewport and added a scrollbar. */
  .sheet.drop{bottom:auto;top:calc(100% + 10px);transform-origin:top center;
    transform:translateY(-12px) scale(.985)}
  /* equal specificity with .sheet.drop, so this has to stay below it */
  .sheet.open{opacity:1;visibility:visible;transform:none;transition-delay:0s,0s,0s}
  /* The bar centres its mark in a 66px column, which lands it 22px in. A tile is
     164px on a phone, and 66px of that leaves 98px for a label -- six short of
     "Amazon Music", which then wraps. So the inset is set directly instead:
     22px of margin in a column just wide enough for it. Same vertical line as the
     bar's mark, 120px left for the label. */
  /* The label centres on the tile itself, not on what is left of it beside the
     mark. The mark comes out of flow at a fixed 22px -- the bar's own line -- and
     the label sits in the middle column of three, the outer two being equal
     fractions, so it lands dead centre.

     Dead centre measures correct and looks wrong: the mark carries weight on the
     left, so an evenly centred label leaves a narrow gap beside it and a wide one
     after -- 19px against 63px on "Apple". --bias is carried as margin on the
     label, which shifts it half the margin off centre no matter how wide the tile
     or the word is. A fraction ratio was the obvious way to do this and it is the
     wrong one: it biases by a share of the free space, so the shortest label
     drifts furthest and a desktop tile drifts more than a phone's.

     At 26px every label lands 13px right -- Apple, Amazon and YouTube on the very
     same centre line, so the tiles agree down each column.

     --clear is the one exception: the floor under the left track, which only
     "SoundCloud" is long enough to reach. 32px of track plus the 26px bias puts
     its left edge at 58px, which leaves its right edge 22px off the tile -- the
     mark's own inset on the other side -- so the button frames the long label
     evenly instead of jamming it against the mark with air behind it. */
  /* An odd last tile spans the pair, so three services read as a deliberate 2+1
     rather than a 2x2 with a hole punched in the corner. Self-undoing: with four
     tiles nothing matches, so when a store that is still propagating finally
     lands the grid goes back to a square on its own. */
  .sheet li:last-child:nth-child(odd){grid-column:1 / -1}
  .sheet a{position:relative;display:grid;--bias:26px;--clear:32px;
    grid-template-columns:minmax(var(--clear),1fr) auto minmax(0,1fr);align-items:center;
    height:clamp(44px,9.3svh,68px);border-radius:14px;text-decoration:none;
    background:var(--lantern);color:var(--night);font-weight:500;font-size:1.0625rem;
    box-shadow:0 8px 26px -10px rgba(0,0,0,.6);
    transition:transform .22s cubic-bezier(.16,.68,.24,1),background .22s ease}
  /* The hover shadow is a second, deeper shadow on a pseudo-element that fades in,
     rather than the box-shadow itself changing. box-shadow cannot be composited, so
     transitioning it repaints the blur every frame -- over a backdrop-filtered page
     that drops frames and the shadow appears to pop. Opacity is composited. */
  .sheet a::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;
    box-shadow:0 16px 38px -12px rgba(0,0,0,.72);
    opacity:0;transition:opacity .22s ease;pointer-events:none}
  .sheet a svg{position:absolute;left:22px;top:50%;transform:translateY(-50%);
    width:22px;height:22px;display:block}

  .sheet a span{grid-column:2;margin-left:var(--bias);text-align:center;white-space:nowrap}
  /* "SoundCloud" is the longest label left and it runs out of room below ~358px:
     a 320px phone leaves it 92px and it wants 91. One step down fits it there, and
     the bias has to give ground too -- 136px of tile cannot hold a 26px bias and
     still keep the long label off both the mark and the right edge. */
  @media (max-width:359px){.sheet a{font-size:.875rem;--bias:16px;--clear:36px}}
  .sheet a:active{transform:translateY(-1px) scale(.985)}
  @media (hover:hover){
    /* the same lift the bar has, so running down the stack they respond in turn.
       z-index matters: rows are siblings at auto, so each paints over the one
       before it and swallows its neighbour's shadow -- hovering the last row
       showed a shadow while hovering the first did not. Lifting the hovered row
       above the stack makes the shadow read the same wherever you are in it. */
    .sheet a:hover{background:#f6e7b6;transform:translateY(-2px);z-index:2}
    .sheet a:hover::before{opacity:1}
  }
  /* the toggle reads as held-down while its menu is open */
  .alt[aria-expanded="true"]{background:rgba(10,13,8,.1)}
  @media (prefers-reduced-motion:reduce){
    .sheet,.sheet li{transition:none}
  }
  .presave .count{font-size:1rem;opacity:.62}
  a:focus-visible{outline:2px solid var(--paper);outline-offset:3px}
  @media (hover:hover){
    .links a{transition:color .25s ease,transform .25s ease}
    .links a:hover{color:var(--paper);transform:translateY(-2px)}
    .presave{transition:transform .22s cubic-bezier(.16,.68,.24,1),box-shadow .25s ease}
    .presave:has(a:hover){transform:translateY(-2px);box-shadow:0 16px 38px -12px rgba(0,0,0,.72)}
    .alt{transition:background .2s ease}
    .alt:hover{background:rgba(10,13,8,.07)}
  }
  .presave:has(:active){transform:translateY(-1px) scale(.985)}

  /* the header itself doesn't animate. It used to fade and slide 14px while the
     name was being written inside it — two gestures on one thing, and that slide
     under the mask is what made the opening letters feel off. Its contents are
     each hidden on their own (the name by its mask, the icons by their strokes),
     so the box can simply be there. fill-mode below is backwards, not both, so a
     finished frame never outranks .presave:active and kill the press effect. */
  /* One gesture for all five, one continuous change, and deliberately small.
     A heavy blur is what made these pop. Sharpness doesn't track blur radius
     evenly — 17px text at 14px of blur is simply gone, and stays gone for most of
     the animation, so the entire reveal crushes into the last few pixels and
     arrives as a rush. 6px keeps everything legible-but-soft the whole way
     through, which means the fade carries it and the defocus only softens the
     edge. Less effect, more smoothness. */
  @keyframes focusIn{
    from{opacity:0;filter:blur(6px);transform:translateY(6px)}
    to{opacity:1;filter:blur(0px);transform:none}}
  .sq,h1,.sub,.presave,.nav{animation-fill-mode:backwards}
  /* Identical on every one of them — same keyframe, same blur, same lift, same
     rate, same duration. They differ only in when they start. Anything else and
     it stops reading as one movement passing down the page.
     linear on purpose. Easing exists to fake momentum, and a focus pull has none
     — it's a rack, and a good one runs at a constant rate. Every curve tried here
     bunched the blur into the middle and then spent its last half-second crawling
     from 2px to 0, which is below the threshold of visible: a burst, then a stall.
     At a flat rate all five resolve evenly and land together. */
  .sq,h1,.sub,.presave,.nav{
    animation-name:focusIn;
    animation-timing-function:linear}
  /* hold the cascade until the cover has actually decoded, so the page arrives
     composed instead of text landing ahead of the artwork. Scoped to .js so that
     without scripting nothing is ever left holding at opacity 0. */
  /* Two gates, both held until the artwork has decoded: .ready starts the name
     writing, .landed brings the record in. Since 9 Oct 2026 they open together --
     the record used to wait ~1.5s for the intro clip to dissolve into the garden,
     and with the canvas staying behind the record that wait had no reason left.
     A paused animation's delay doesn't elapse, so every record delay is measured
     from the moment of landing. */
  .js .artist,.js .links svg *,
  .js .bg::before,.js .sq,.js h1,.js .sub,.js .presave,.js .nav{animation-play-state:paused}
  /* the name */
  .js.ready .artist{animation-play-state:running}
  /* the garden settles as it comes through, not after. Gated on .landed it was
     fading in frozen at 1.07 and only starting to move once the crossfade had
     finished — the background arrived, stopped, then drifted. */
  .js.handoff .bg::before{animation-play-state:running}
  .js.landed .links svg *,.js.landed .sq,
  .js.landed h1,.js.landed .sub,.js.landed .presave,.js.landed .nav{animation-play-state:running}
  /* Six beats down the page, each starting while the one before is still
     finishing — ordered, but never a moment with nothing moving:
       1 what   the kicker
       2         the cover
       3         the title
       4 where  the icons stroke on, off to the side as the eye travels down
       5 when   the date
       6 the ask the button, landing last */
  /* stage one — over the flight */
  .artist{animation-delay:.15s;animation-duration:3.2s;
    /* no slow start — .45,.12 crawled through the first letter. This leaves at a
       real pace, holds it, and only softens at the very end, like a hand does. */
    animation-timing-function:cubic-bezier(.2,.36,.34,.96)}
  /* runs with the crossfade */
  .bg::before{animation-delay:0s;animation-duration:2.6s}
  /* Not a wave any more — a fan-in. Every element starts at a different moment
     but they all come into focus together at 3.20s, so the durations differ to
     absorb the stagger: the later something starts, the shorter its resolve.
     The kicker is the one exception, sharp at 2.90s, alone ahead of the rest.
     Consequence worth knowing: identical duration was what made the five resolve
     at an identical rate. They no longer do — the cover sheds its 6px of blur
     over 1.55s and the icons over 1.35s, so the later ones resolve slightly
     faster. That is inherent to converging; it cannot be had both ways.
     The kicker is on zero: the background leads during the handover, so a delay
     there would only be dead air on the seam. */
  .sq{animation-delay:0s;animation-duration:1.55s}
  h1{animation-delay:.05s;animation-duration:1.5s}
  .sub{animation-delay:.1s;animation-duration:1.45s}
  .presave{animation-delay:.15s;animation-duration:1.4s}
  .nav{animation-delay:.2s;animation-duration:1.35s}   /* arrives with the garden, not with the name */

  /* icons stroke themselves on, part by part */
  /* opacity in the from-state matters: stroke-linecap is round on the Spotify
     mark, so a zero-length dash still paints a round pip — that's the sliver
     sitting there before the draw begins. fill-mode:both keeps it hidden through
     the delay, and reduced-motion never sets opacity, so it stays visible there. */
  @keyframes draw{from{opacity:0;stroke-dashoffset:1}
    10%{opacity:1}to{opacity:1;stroke-dashoffset:0}}
  @keyframes dot{from{opacity:0}to{opacity:1}}
  .links svg :is(circle,rect,path){
    stroke-dasharray:1;stroke-dashoffset:1;
    animation-name:draw;animation-duration:1.25s;animation-fill-mode:both;
    animation-timing-function:cubic-bezier(.16,.68,.24,1)}
  /* the ring strokes on, then the three arcs build bottom-up like signal bars.
     they're listed top-to-bottom in the SVG, so the delays run in reverse.
     They sit in the wave's fourth slot: clear of the background change, but not
     trailing the whole page — their own stagger then runs out under the button. */
  .links a:nth-child(1) svg>*:nth-child(1){animation-delay:0.2s}
  @keyframes signal{from{opacity:0;transform:translateY(2.5px)}to{opacity:1;transform:none}}
  .links a:nth-child(1) svg>*:nth-child(n+2){
    animation-name:signal;animation-duration:.95s;
    stroke-dasharray:none;stroke-dashoffset:0;
    transform-box:fill-box;transform-origin:center}
  .links a:nth-child(1) svg>*:nth-child(4){animation-delay:0.32s}
  .links a:nth-child(1) svg>*:nth-child(3){animation-delay:0.4s}
  .links a:nth-child(1) svg>*:nth-child(2){animation-delay:0.48s}
  .links a:nth-child(2) svg>*:nth-child(1){animation-delay:0.24s}
  .links a:nth-child(2) svg>*:nth-child(2){animation-delay:0.3s}
  .links a:nth-child(2) svg>*:nth-child(3){
    animation-name:dot;animation-duration:.5s;animation-delay:0.52s;animation-fill-mode:both}

  /* Moving between pages of the site isn't arriving at it, so nothing arrives:
     no writing, no drawing -- the header is just there, the way it was on the
     page you came from. The !important is for the per-part icon rules above,
     which name their own animations at higher specificity. A record opened from
     inside the site is already up too, its art and info sitting over its canvas.
     The whole sequence, canvas and name first and record after, is for arriving.
     On a page with no record (the gallery, the 404) the garden is simply there. */
  .inside .artist{animation:none;-webkit-mask-image:none;mask-image:none}
  .inside .nav,.inside .sq,.inside h1,.inside .sub,.inside .presave{animation:none}
  .inside .links svg *{animation:none!important;stroke-dasharray:none;stroke-dashoffset:0}
  .inside .bg:not(:has(video))::before{animation:none}

  /* Between pages, the new page's content fades up under a header that's already
     there (.fade, set by each page's pre-paint script). This was the browser's own
     cross-page crossfade until Oct 10, 2026: it flashed white returning home from
     Music -- "pretty blinding" -- so it's gone. The fade only touches the content,
     over the page's own dark background, so it can't flash. */
  /* Never from nothing, though. From 0 it left a frame or two of bare garden
     between the old page and the new, which read as a grey blink (Carter, 10 Oct
     2026). From .4 the new page is already there as the old one goes, and settles. */
  @keyframes pageIn{from{opacity:.4}}
  .fade main>:not(header){animation:pageIn .35s cubic-bezier(.2,.6,.3,1) both}

  /* Consent bar. Same dark frosted surface as the menu, same serif, and it waits
     for the entrance to finish rather than sliding in over it. */
  .consent{position:fixed;left:0;right:0;bottom:0;z-index:5;
    display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12px 16px;
    padding:14px var(--pad) calc(14px + env(safe-area-inset-bottom));
    background:rgba(13,17,10,.94);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);
    box-shadow:inset 0 1px 0 rgba(239,233,218,.14);
    transform:translateY(100%);transition:transform .5s cubic-bezier(.16,.68,.24,1)}
  .consent.up{transform:none}
  .consent p{margin:0;flex:1 1 240px;text-align:center;
    font-size:.9375rem;line-height:1.45;color:var(--paper)}
  /* One line, and the button says what it does. Someone has to be told what they
     are agreeing to before they agree to it, but that does not have to be a
     sentence on the bar -- "Allow cookies" carries it, and the link carries the
     rest. The bar asks; the button names the thing; the link explains it. */
  .consent .fine{margin-left:2px;white-space:nowrap;color:var(--muted);
    text-underline-offset:2px}
  .consent .fine:hover{color:var(--paper)}
  .consent-act{display:flex;gap:10px;flex:none}
  /* the two are deliberately identical -- same width, same colour, same weight.
     A cream Allow against a muted No thanks would be steering the answer, and
     min-width keeps the boxes equal even though the labels differ in length. */
  .consent button,.choice button{font:inherit;font-size:.9375rem;cursor:pointer;border:0;
    min-width:138px;padding:9px 18px;border-radius:10px;
    background:rgba(239,233,218,.09);color:var(--paper);
    box-shadow:inset 0 0 0 1px rgba(239,233,218,.22);
    transition:background .18s ease}
  @media (hover:hover){
    .consent button:hover,.choice button:hover{background:rgba(239,233,218,.17)}
  }
  @media (prefers-reduced-motion:reduce){
    .consent,.page{transition:none}
    .glow{animation:none}
    .sq,h1,.sub,.presave,.nav,.bg::before{animation:none}
    .artist{animation:none;-webkit-mask-image:none;mask-image:none}
    /* !important for the same reason as .inside: the per-part icon rules outrank this */
    .links svg *{animation:none!important;stroke-dasharray:none!important;stroke-dashoffset:0!important}
  }

  /* the photo is 1500px wide; on a retina desktop it's drawn at ~2x that, which
     magnifies its compression. More blur hides it, and it's a blurred backdrop
     either way. */
  @media (min-width:900px){
    .bg::before{filter:blur(9px) brightness(.66) saturate(1.12)}
  }

  /* tablets stay a single column, just a wider one */
  @media (min-width:600px) and (max-width:899px){
    .page{max-width:560px}
  }

  /* desktop goes sideways: artwork left, type right. Column one is the artwork
     itself so the gutter is the gap and nothing else, and the 46% cap keeps the
     type column wide enough that the title never clips. */
  @media (min-width:900px){
    .page{max-width:1080px;display:grid;
      grid-template-columns:min(66svh,500px,46%) minmax(0,1fr);
      grid-template-rows:auto 1fr;column-gap:clamp(48px,6vw,80px);align-items:center}
    header{grid-column:1 / -1}
    /* Neither the gallery nor the 404 is the release page's two-column layout,
       and both inherit it here unless told otherwise. Left as a grid, the 404 put
       its message in the artwork column; made a flex column without resetting
       align-items, the gallery kept .page's align-items:center and the header
       shrank to the width of the name -- 112px -- so Music and the icons, pinned
       to its edges, landed on top of it. stretch gives the header the full row.
       Declared after .page so these win on source order, same specificity.
       Same width as the release page, so the header doesn't shift between them --
       the grid keeps its own narrower column. */
    .page--list,.page--lost,.page--doc{display:flex;flex-direction:column;align-items:stretch}
    /* size containment would collapse an auto-height container */
    .cover{margin-top:0;flex:none;height:auto;display:block;container-type:normal}
    /* block container here, so auto margins collapse — give it a real gap */
    .sq{margin:0;width:100%}
    .release{margin-top:0}
    /* the tall bottom padding is for the single column, where it lifts the block
       off the floor; this grid is centred already and would just ride up */
    .page{padding-bottom:calc(clamp(16px,4svh,36px) + env(safe-area-inset-bottom) + var(--consent-h,0px))}
    h1{font-size:5.25rem}
  }
