
    /* --ar-glint / --ar-leadin are the tie-dye at reading volume: they carry
       alpha and fade to nothing at their edges, so a slice painted over the
       ordinary setlist ink blends instead of cutting a hard band out of a word. */
    /* Registered so they can be INTERPOLATED. A plain custom property is an
       untyped token to the engine: it flips from one string to the other with no
       in-between, which is exactly the snap the rail used to do. Declaring the
       type is what buys the tween. */
    @property --rail-w{syntax:"<length>";initial-value:232px;inherits:false}
    @property --rail-gap{syntax:"<length>";initial-value:40px;inherits:false}
    :root{--ar-tie:linear-gradient(100deg,#d4514f 0%,#e0574f 24%,#60a5d2 58%,#c9a35f 88%);
      
      --ar-leadin:linear-gradient(90deg,rgba(212,81,79,.5) 0%,rgba(224,87,79,.34) 55%,rgba(96,165,210,0) 100%)}
    body.listen-archive{background:var(--sl-bg,#090a0d)}
    /* COLLAPSING IS AN ANIMATION OF LAYOUT, NOT A DISPLAY TOGGLE.
       It used to snap: grid-template-columns went to 0 and the rail's children
       went display:none in the same frame, so the canvas jumped sideways into
       the space. Now a REGISTERED custom property carries the width, which is
       what makes it interpolable — an unregistered var would jump between two
       token strings the way the old rule did. The gap rides the same curve, so
       the canvas glides rather than steps. ~240ms on an ease-out, which is the
       length these sidebars run at everywhere they feel good. */
    /* D5 (Alex): the gutter right of the sticky rail was clamp(28,4vw,64) —
       ~58px at 1440, real estate the photograph and the setlist wanted. */
    .ar-shell{display:grid;--rail-w:232px;--rail-full:232px;--rail-gap:clamp(22px,2.2vw,36px);grid-template-columns:var(--rail-w) minmax(0,1fr);gap:var(--rail-gap);width:min(1400px,calc(100% - 56px));margin:0 auto;padding:26px 0 120px;align-items:start;
      transition:--rail-w .24s cubic-bezier(.22,1,.36,1),--rail-gap .24s cubic-bezier(.22,1,.36,1)}
    .ar-main{min-width:0}
    /* D4 (Alex): the canvas is a <main>, so the site's global
       main-element width rule (min 1400px / 100% - 56px, auto margins) was boxing it INSIDE
       its own grid column — 28px of dead margin each side, so the content
       stopped short of the hamburger's gutter. The shell already owns the page
       gutter; the canvas fills its column and the right edge now lines up with
       the header's right edge on every /listen/ page. */
    .ar-canvas{width:100%;max-width:none;margin:0}

    /* ---- the rail ----
       One vertical scroll for the whole rail below the search field — years and
       eras expand INLINE and push the sections below them; nothing nests a
       second scrollbar inside the first. overflow-x is locked to hidden and no
       child uses negative margins, which is what allowed the old rail to pan
       sideways and clip its icons. */
    .ar-cabinet{position:sticky;top:78px;display:flex;flex-direction:column;max-height:calc(100vh - 102px);min-width:0}
    /* The rail column itself never scrolls sideways while it animates — the
       clipping is .ar-cab-body's job, and a scrollbar appearing for one frame
       mid-collapse is exactly the flash we are avoiding. */
    .ar-cabinet{overflow:visible}
    /* SAME FAMILY AS THE RAIL (Alex, 7/28: it "doesn't match the vibe").
       It was the boldest-bordered object in the sidebar — --sl-line-strong at
       rest, which this system reserves for hover and active states — while being
       the one passive thing in it. Now it rests on the ordinary hairline like
       the cards do, takes the strong one on hover, and only goes bright on
       focus. Radius 12 matches .ar-songcard / .ar-placecard; the type matches the
       nav rows it sits above rather than running a size of its own. */
    /* The drawer header exists only when the rail IS a drawer. On the desktop
       rail the collapse handle is the affordance and an X would mean something
       different (close vs. hide), so it is not drawn there at all. */
    .ar-drawer-head{display:none}
    .ar-cab-search{display:flex;align-items:center;gap:9px;flex:none;margin-bottom:8px;padding:0 12px;border:1px solid var(--sl-line);border-radius:12px;background:rgba(255,255,255,.035);transition:border-color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-cab-search:hover{border-color:var(--sl-line-strong);background:rgba(255,255,255,.05)}
    .ar-cab-search:focus-within{border-color:rgba(255,255,255,.42);background:rgba(255,255,255,.06)}
    .ar-cab-search svg{width:15px;height:15px;flex:none;color:var(--sl-faint,#6b6f78);transition:color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-cab-search:focus-within svg{color:var(--sl-muted)}
    /* 16px exactly (M1): anything smaller and iOS zooms the whole page on
       focus — this input rides inside the mobile drawer, so it was zooming the
       menu itself. */
    .ar-cab-search input{flex:1;min-width:0;height:40px;border:0;background:none;color:var(--sl-ink);font:500 16px/1 var(--sl-display)}
    .ar-cab-search input:focus{outline:none}
    .ar-cab-search input::placeholder{color:var(--sl-faint,#6b6f78)}
    .ar-cab-search input::-webkit-search-cancel-button{filter:invert(1);opacity:.4}
    .ar-cab-scroll{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent}
    .ar-cab-scroll::-webkit-scrollbar{width:3px}
    .ar-cab-scroll::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:3px}
    /* Sections separate with quiet rules, not heavy headings. */
    .ar-cab-section{padding:14px 0 12px;border-bottom:1px solid var(--sl-line,rgba(255,255,255,.08))}
    .ar-cab-section:last-of-type{border-bottom:0}
    /* Two section labels remain (Browse, Featured Shows, Resources) and they now
       have to look chosen rather than left over, since the top group has none.
       Slightly larger, slightly less tracked, and more air under them. */
    .ar-cab-label{margin:0 0 10px;color:var(--sl-faint,#6b6f78);font:600 11px/1 var(--sl-mono);letter-spacing:.15em;text-transform:uppercase}
    .ar-cab-group{display:grid;gap:1px}
    /* The primary destinations. No eyebrow above them — they are the first thing
       in the panel, which is the only introduction they need. */
    .ar-cab-top{margin-bottom:4px}
    .ar-cab-primary{gap:2px}
    .ar-cab-onlymobile{display:none}
    /* RAIL TYPE SCALE — one step up across the board (Alex, 7/28: it read like
       fine print beside the canvas). The HIERARCHY is unchanged: nav rows are
       still the largest thing in the rail, the year/era sub-lists sit a step
       under them, Featured Shows a step under that, the footer smallest. Every
       size moved together so the relationships hold. */
    .ar-cab-group a,.ar-cab-group button{display:flex;align-items:center;gap:12px;padding:9px 9px;border:0;border-radius:9px;background:none;color:var(--sl-muted);font:500 16.5px/1.35 var(--sl-display);text-align:left;cursor:pointer;transition:color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-cab-group svg{width:17px;height:17px;flex:none;opacity:.62;transition:opacity .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-cab-group a:hover svg,.ar-cab-group button:hover svg,.ar-cab-group a.is-active svg{opacity:1}
    .ar-cab-group a:hover,.ar-cab-group button:hover,.ar-cab-group a:focus-visible,.ar-cab-group button:focus-visible{color:var(--sl-ink);background:rgba(255,255,255,.05)}
    .ar-cab-group a.is-active{color:var(--sl-ink);background:rgba(255,255,255,.06)}
    .ar-cab-resources a{font-size:15.5px}
    /* The credit used to strand a single word on its own last line. A slightly
       tighter measure plus text-wrap:pretty (which browsers that have it use to
       avoid exactly this orphan) settles it, and the copyright underneath turns a
       floating sentence into a footer that looks intended. */
    .ar-cab-foot{margin:18px 0 4px;padding:0 8px;max-width:24ch;color:var(--sl-faint,#6b6f78);font-size:12.5px;line-height:1.55;text-wrap:pretty}
    /* The "© 2026 Burnthday" line is gone (Alex, 7/28 — he didn't like the
       look). Deleted rather than left describing markup that no longer exists.
       The Internet Archive attribution stays: it is a credit we owe, not chrome. */
    .ar-cab-foot a{color:inherit;text-decoration:underline;text-underline-offset:2px}
    .ar-cab-foot a:hover{color:var(--sl-ink)}

    /* ---- featured shows: city + date, nothing else ---- */
    .ar-featured-list{display:grid;gap:2px}
    .ar-feat{display:flex;align-items:center;gap:10px;padding:5px 8px;border-radius:8px;color:inherit;transition:background .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    /* The rows arrive as blanks and reveal once their photograph has loaded -
       a quiet fade with a whisper of lift, nothing theatrical (Alex: "almost
       not noticeable but elegant"). Rows without a photograph reveal at once. */
    .ar-feat .ar-feat-body{opacity:0;transform:translateY(2px);transition:opacity .3s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),transform .3s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-feat.is-ready .ar-feat-body{opacity:1;transform:none}
    .ar-feat:nth-child(2) .ar-feat-body{transition-delay:.05s}
    .ar-feat:nth-child(3) .ar-feat-body{transition-delay:.1s}
    .ar-feat:nth-child(4) .ar-feat-body{transition-delay:.15s}
    @media (prefers-reduced-motion: reduce){.ar-feat .ar-feat-body{transition:none}}
    .ar-feat:hover,.ar-feat:focus-visible{background:rgba(255,255,255,.05)}
    .ar-feat-art{flex:none;width:31px;height:31px;border-radius:50%;overflow:hidden;border:1px solid var(--sl-line,rgba(255,255,255,.14));background:radial-gradient(120% 130% at 28% 14%,hsl(var(--tone,210) 50% 30%/.9),transparent 62%),linear-gradient(160deg,#161a23,#0b0c10);background-size:cover;background-position:center 30%}
    /* A blank pre-boot row must stand exactly as tall as a filled one (two
       text lines beat the 31px circle by ~9px), or Resources still hops when
       the real rows land. */
    .ar-feat-body:empty{height:40px}
    .ar-feat-body{min-width:0}
    .ar-feat-body strong{display:block;font:600 14.5px/1.25 var(--sl-display);letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ar-feat-body span{display:block;margin-top:2px;color:var(--sl-faint,#6b6f78);font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

    /* ---- collapse: a circle on the boundary, like a drawer handle ----
       Collapsed hides the rail entirely — no icon totem pole — and leaves the
       same circle floating at the page edge to bring it back. */
    /* OFF THE SEARCH FIELD (Alex, 7/28). At top:4px it sat in the same 40px band
       as the input and read as a control belonging to it — two unrelated things
       crowding one corner. It drops below the field, onto the boundary beside
       the first nav group, where "drawer handle" is the only thing it can mean.
       It rides the animating edge rather than jumping to it, and the chevron
       turns to say which way the rail will go. */
    /* THE CONTENTS DO NOT SQUASH. .ar-cab-body is pinned to the rail's FULL
       width and clipped by the shrinking column, so every row keeps its own
       measure the whole way — nothing re-wraps mid-animation, which is the tell
       that gives a cheap sidebar away. The handle is deliberately NOT inside
       this wrapper: it hangs off the rail's edge and must not be clipped with
       the rest.
       The fade runs AHEAD of the width on the way out (120ms, no delay) and
       BEHIND it on the way back (90ms delay), so you never see text sliding
       under its own edge in either direction. */
    /* M7 (Alex): only the left half of the drawer scrolled — the body was
       still pinned to the old rail's 232px (collapse-era shrapnel; the handle
       is deleted). It now fills whatever holds it: the rail column on
       desktop, the full drawer panel on mobile. */
    .ar-cab-body{display:flex;flex-direction:column;min-height:0;flex:1;width:auto;overflow:hidden}
    @media(prefers-reduced-motion:reduce){
      .ar-shell,.ar-cab-body{transition:none}
    }
    /* The handle used to switch to position:fixed at a hard-coded corner the
       moment the class landed, which TELEPORTED it — and its flight path crossed
       the search field's band on the way. It now simply stays where it is: an
       absolute child of the (sticky) rail, riding the animating edge down to
       x=15 on its own. Same resting place, no jump, and it is never anywhere
       near the search box at any point in the animation. */
    /* ---- shared ---- */
    .ar-kicker{margin:0 0 12px;color:var(--sl-muted);font:600 10.5px/1.2 var(--sl-mono);letter-spacing:.2em;text-transform:uppercase}
    .ar-canvas h1{margin:0;font:700 clamp(40px,6.2vw,88px)/.92 var(--sl-display);letter-spacing:-.04em}
    .ar-hero-sub{margin:16px 0 0;color:var(--sl-muted);font-size:15px}
    .ar-head{display:flex;justify-content:space-between;align-items:baseline;gap:18px;margin-bottom:20px;padding-bottom:12px;border-bottom:1px solid var(--sl-line,rgba(255,255,255,.1))}
    .ar-head h2{margin:0;font:650 clamp(21px,2.4vw,29px)/1 var(--sl-display);letter-spacing:-.02em}
    .ar-head span,.ar-head a{color:var(--sl-muted);font:500 11px/1 var(--sl-mono);letter-spacing:.08em;text-transform:uppercase}
    /* The 'More' / 'All' links were an 11px line of type and therefore an 11px
       TAP TARGET — a third of the 44px thumb minimum, on the one control that
       moves you between archive views. Padding gives them a real hit area
       without moving the type: the negative margin keeps the baseline aligned
       with the heading exactly as before. */
    .ar-head a{display:inline-block;padding:12px 0;margin:-12px 0;min-height:0}
    .ar-head a:hover{color:var(--sl-ink)}
    /* PRE-DEPLOY SWEEP (2026-07-28). These section links — "More", "All" — were
       30x11 and 22x11 on a phone: real destinations rendered at a size no thumb
       can reliably hit. An invisible ::after halo gives them a 44px field without
       moving the label a pixel or disturbing the baseline the row is aligned on
       (padding would have pushed the rule below it out of line). */
    .ar-head a{position:relative}
    .ar-head a::after{content:"";position:absolute;z-index:1;left:50%;top:50%;width:max(100%,64px);height:44px;transform:translate(-50%,-50%)}
    /* The canvas IS a <main>, so the site-wide main > section rule (96px)
       landed on its FIRST section too — 96px of nothing above every view, stacked
       on the shell's own padding, once search moved out of the canvas and into the
       rail. The first section starts at the top; the rhythm between sections is
       unchanged. */
    .ar-canvas>section:first-child,body.listen-archive .ar-canvas>section:first-child{margin-top:0}
    /* M2 (Alex): the canvas renders client-side, so for a beat the page was
       header + footer with nothing between — the footer painted first and got
       shoved down when the view arrived, a full-viewport layout shift. An
       empty canvas now reserves a viewport of height, so the footer starts
       below the fold and the arriving view replaces space instead of taking
       it. */
    .ar-canvas[aria-busy="true"]{min-height:100dvh}
    /* THE CRAWLER LINE WAS RENDERING AS AN 88px BILLBOARD. .ar-seo-h1 asks for
       a 20-26px line, but '.ar-canvas h1' — the archive's own view title, the
       one that sets 1997 in 88px display type — is a class AND an element and
       therefore out-ranks a lone class. So the shell's SEO heading has always
       been painted at view-title scale: two enormous lines that fill the screen
       before the app arrives. Naming both wins the cascade honestly, at the
       same weight, instead of reaching for !important. Crawlers and no-script
       clients now get the modest heading the markup always intended. */
    .ar-canvas h1.ar-seo-h1{font:650 clamp(20px,2.2vw,26px)/1.2 var(--sl-display);letter-spacing:-.02em}
    /* AND A VISITOR SHOULD NOT SEE IT AT ALL (Alex, 7/29, twice: pages "say
       'Widespread Panic Live Archive' and then have a bunch of stuff underneath
       them, and then the page loads"). The heading, the summary sentence and
       the forty-year link cloud exist for clients that never run the app. A
       browser that IS about to paint the archive — html.js, stamped inline
       before first paint — hides all three and shows one small mono line in
       their place, so booting reads as the archive opening instead of a
       different page being torn down. The first fix here kept the heading
       visible; at view-title scale that WAS the complaint. If the app never
       boots, aria-busy never clears and the ::before line stands alone — but
       the heading, sentence and year links all still shipped in the document
       for anything that does not run scripts. */
    html.js .ar-canvas[aria-busy="true"] .ar-seo-h1,
    html.js .ar-canvas[aria-busy="true"] .ar-seo-years,
    html.js .ar-canvas[aria-busy="true"] .ar-empty{display:none}
    html.js .ar-canvas[aria-busy="true"]::before{content:"Opening the archive…";display:block;opacity:0;animation-delay:.35s;font:500 11px/1.2 var(--sl-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--sl-faint,#6b6f78);animation:ar-seo-wait 1.2s ease-in-out infinite alternate}
    @keyframes ar-seo-wait{from{opacity:.4}to{opacity:.85}}
    @media(prefers-reduced-motion:reduce){html.js .ar-canvas[aria-busy="true"]::before{animation:none}}
    .ar-canvas>section+section{margin-top:clamp(46px,6vw,86px)}
    .ar-seo-h1{margin:0 0 10px;font:650 clamp(20px,2.2vw,26px)/1.2 var(--sl-display);letter-spacing:-.02em;color:var(--sl-ink)}
    .ar-seo-years{display:flex;flex-wrap:wrap;gap:8px 14px;margin-top:14px}
    .ar-seo-years a{color:var(--sl-muted);font:500 12px/1 var(--sl-mono)}
    .ar-empty{padding:30px 0;color:var(--sl-muted)}
    .ar-linkish{border:0;background:none;padding:0;color:var(--sl-ink);text-decoration:underline;text-underline-offset:3px;cursor:pointer;font:inherit}
    .ar-section-label{display:flex;align-items:baseline;gap:10px;margin:0 0 14px;color:var(--sl-muted);font:600 10.5px/1 var(--sl-mono);letter-spacing:.18em;text-transform:uppercase}
    .ar-section-label+.ar-section-label,.ar-show-side .ar-section-label:not(:first-child),.ar-notes+.ar-section-label{margin-top:34px}
    .ar-provenance{letter-spacing:.04em;text-transform:none;font-weight:500;color:var(--sl-faint,#6b6f78)}
    .ar-chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:14px}
    .ar-chips a{padding:7px 13px;border:1px solid var(--sl-line-strong);border-radius:999px;color:var(--sl-ink);font:600 11px/1 var(--sl-mono);letter-spacing:.05em}
    .ar-chips a:hover{background:var(--sl-ink);color:var(--sl-bg,#090a0d)}

    .ar-invite-label{display:block;color:var(--sl-muted);font:600 10px/1 var(--sl-mono);letter-spacing:.2em;text-transform:uppercase}
    .ar-invite strong{display:block;margin:13px 0 7px;font:650 clamp(19px,2.2vw,25px)/1.1 var(--sl-display);letter-spacing:-.02em}
    .ar-invite-meta{display:block;color:var(--sl-muted);font-size:13px;line-height:1.45}


    /* ---- dense rows: the research surface ---- */
    .ar-rows{border-top:1px solid var(--sl-line,rgba(255,255,255,.1))}
    .ar-row{display:grid;grid-template-columns:92px minmax(120px,.85fr) minmax(160px,1.3fr) 96px;gap:20px;align-items:baseline;padding:13px 10px 13px 4px;border-bottom:1px solid var(--sl-line,rgba(255,255,255,.07));color:inherit;transition:background .14s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),padding-left .14s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-row:hover{background:rgba(255,255,255,.05);padding-left:10px}
    .ar-row:focus-visible{background:rgba(255,255,255,.05);padding-left:10px;outline:2px solid rgba(255,255,255,.8);outline-offset:-2px;border-radius:6px}
    .ar-row time{color:var(--sl-muted);font:600 11.5px/1.3 var(--sl-mono);font-variant-numeric:tabular-nums}
    .ar-row-city{font-weight:640;letter-spacing:-.01em}
    .ar-row-venue{color:var(--sl-muted);font-size:14px}
    .ar-row-count{text-align:right;color:var(--sl-faint,#6b6f78);font:500 10.5px/1.3 var(--sl-mono)}

    /* ---- year chronology ---- */
    .ar-yearhead h1{font-size:clamp(64px,11vw,150px)}
    .ar-yearnote{margin:18px 0 0;color:var(--sl-muted);font-size:14px;line-height:1.6}
    .ar-yearnote a{color:var(--sl-ink);text-decoration:underline;text-decoration-color:rgba(255,255,255,.28);text-underline-offset:3px}
    .ar-yearnav{display:flex;justify-content:space-between;margin-top:26px;padding-top:16px;border-top:1px solid var(--sl-line,rgba(255,255,255,.1));color:var(--sl-muted);font:600 11px/1 var(--sl-mono);letter-spacing:.08em;text-transform:uppercase}
    .ar-yearnav a:hover{color:var(--sl-ink)}
    .ar-chronology{margin-top:clamp(40px,5vw,72px);display:grid;gap:42px}
    .ar-month{display:grid;grid-template-columns:132px minmax(0,1fr);gap:26px;align-items:start}
    .ar-month h3{position:sticky;top:96px;margin:0;color:var(--sl-faint,#6b6f78);font:600 11px/1 var(--sl-mono);letter-spacing:.18em;text-transform:uppercase}
    .ar-month-blocks{display:grid;gap:11px;min-width:0}
    /* M5 (Alex): the run bar's 17px indent pushed grouped rows right of the
       ungrouped ones — date and city columns drifted. EVERY block reserves
       the bar's lane; only runs paint it. Columns align regardless. */
    .ar-block{position:relative;display:grid;gap:1px;padding-left:17px}
    .ar-block.is-run::before{content:"";position:absolute;left:0;top:22px;bottom:9px;width:2px;border-radius:2px;background:var(--ar-tie);background-size:100% 220%;opacity:.62}
    .ar-run-label{display:block;margin-bottom:7px;color:var(--sl-faint,#6b6f78);font:600 9.5px/1 var(--sl-mono);letter-spacing:.16em;text-transform:uppercase}
    .ar-block-night{display:grid;grid-template-columns:34px minmax(90px,.8fr) minmax(0,1.4fr) auto;gap:16px;align-items:baseline;padding:9px 10px 9px 4px;border-radius:6px;color:inherit;transition:background .14s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-block-night:hover{background:rgba(255,255,255,.06)}
    .ar-block-night:focus-visible{background:rgba(255,255,255,.06);outline:2px solid rgba(255,255,255,.8);outline-offset:-2px}
    .ar-block-night time{color:var(--sl-ink);font:600 15px/1 var(--sl-mono);font-variant-numeric:tabular-nums}
    .ar-block-city{font-weight:620}
    .ar-block-venue{color:var(--sl-muted);font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ar-block-count{color:var(--sl-faint,#6b6f78);font:500 10px/1 var(--sl-mono)}

    /* ---- the stage ---- */
    /* max-width is load-bearing, not belt-and-braces: aspect-ratio + min-height
       resolve the WIDTH from the height when nothing pins it, so the stage grew
       past its column and pushed a horizontal scrollbar onto the front door
       (min-height 32vw x 21/9 = wider than the canvas). Pinning the width lets
       min-height do only what it is for — keep the stage from collapsing on a
       narrow screen — while the ratio governs the height. */
    .ar-show-stage{position:relative;isolation:isolate;overflow:hidden;width:100%;max-width:100%;border:1px solid var(--sl-line,rgba(255,255,255,.14));border-radius:18px}
    /* Only the image-less stage keeps a fixed shape — there is no photograph to
       take a ratio from, so the tie-dye needs one of its own. */
    .ar-show-stage:not(:has(.ar-stage img)){aspect-ratio:var(--ar,21/9);min-height:0}
    /* The front door's lead stage paints its photo as a BACKGROUND (the index
       has no loaded show doc), so no <img> props it open — without its own
       ratio it collapsed to 0px tall and the lead card rendered as an empty
       black rectangle (Alex's punch list #1, 2026-07-28). */
    .ar-stage-art{aspect-ratio:var(--ar,21/9);min-height:0}
    /* A PHONE GETS THE PHOTOGRAPH'S OWN PROPORTION, NOT A LETTERBOX. At 390px
       the 21:9 window is 167px tall - the picture reads as a slot, and the
       identity text laid over it rode up to y=60, UNDER the 66px sticky header,
       which is Alex's "top bar... cutting off Saturday" and "the hero is still
       not tall enough" (2026-08-13, measured before and after). 3:2 is what
       these photographs actually are. Desktop keeps the cinematic crop. */
    @media(max-width:700px){
      .ar-show-stage:not(:has(.ar-stage img)),
      .ar-stage-art{aspect-ratio:3/2}
    }
    /* Faces ride the upper frame of concert photography; 30% cropped brows on
       the lead (Alex: "Nick's face"). 22% keeps heads whole in the 21:9 cut —
       a heuristic until per-photo focal data exists. */
    /* D3 (Alex): faces were landing UNDER the identity text. Measured, not
       guessed: the 21:9 window shows 64% of a 3:2 photo, the text band starts
       57% down the frame, and stage performers sit ~46% down the source — at
       the old 22% that put their heads at 60% of the frame, i.e. behind the
       type. 58% lifts them to ~40%, clear of the text, and drops the dead
       lighting rig off the top. Concert photography puts people in that same
       middle band, so this reads as a general rule rather than one photo's
       fix; a per-photo focal point would still beat it. */
    .is-lead .ar-stage-art{background-position:center 58%}
    .ar-lead-link{position:absolute;inset:0;z-index:1}
    .is-lead .ar-show-id{z-index:2}
    .is-lead .ar-show-actions .sc-chip{position:relative;z-index:3}
    /* THE PHOTOGRAPH IS NEVER CROPPED (DESIGN-SYSTEM.md: setlist photography
       renders at its natural ratio; object-fit:cover is not used to force one).
       The stage was a 21:9 window with object-fit:cover behind it, which threw
       away most of a portrait frame — 759x1024 rendered as 1.78. The image now
       sets the stage's height, and the identity block still lays over its lower
       edge because the stage remains its positioning context. */
    .ar-stage{position:relative;display:block;width:100%;margin:0}
    /* RESERVE THE BOX, NEVER A VOID (Alex, 8/4: "i don't love how the page loads and then the image loads which pushes the content down"; and, on 1995-03-22, "here it is on a listen page with no image loaded"). height:auto with no dimensions on the tag means the frame is ZERO PIXELS TALL until Archive.org delivers — so the page shows a void, then jolts when the picture claims its height. Both complaints are that one fact. The frame now holds the same 21/9 the empty state already reserves, wears the night's own tone while it waits, and the picture fades up when it arrives instead of popping. Nothing moves, and there is something to look at from the first paint. */
.ar-stage{aspect-ratio:21/9;min-height:340px;overflow:hidden;background:radial-gradient(120% 150% at 18% 8%,hsl(var(--tone) 52% 26%/.85),transparent 62%),radial-gradient(110% 130% at 88% 12%,hsl(var(--tone) 46% 20%/.7),transparent 58%),#0b0b0d}
    /* A frame that HOLDS a photograph loads against neutral dark, not the night's
       tone — 7/25's red tone painted twice under the arriving image and read as
       a double red flash (Alex, 2026-08-11). Tone stays for art-less stages. */
    /* THE HOMEPAGE HERO'S OWN BLUR DERIVATIVE, PAINTED BEHIND THE PHOTOGRAPH
       (Alex, 2026-08-12: "make it behave exactly like the homepage hero, which
       already does not flash"). --stage-blur carries the same 400px file
       blurSourceFor() feeds the hero backdrop; unset, it resolves to none and
       this is the flat colour it has always been. No second pipeline, no extra
       element, no change to the ::after scrim's stacking. */
    .ar-stage:has(img){background-color:#0b0b0d;background-image:var(--stage-blur,none);background-size:cover;background-position:center 30%}
    /* A MEASURED PHOTOGRAPH KEEPS ITS OWN SHAPE — EVERYWHERE, not just on a
       phone. This is the fifth attempt at the hero and the first to find the
       cause, because the measurement was never what was broken: the build
       emits the real ratio and the element carries it (--ar:1800/1198 on
       2026-07-24). The rule that CONSUMES it — .ar-stage[style*="--ar"] with
       min-height:0 — lived inside @media(max-width:820px), so it had never run
       on a desktop at all. Above 820px the line directly above kept winning:
       21/9 with a 340px floor, which at 430 wide computes 184px, floors to
       340, and leaves object-fit:cover to crop the sides off a 3:2 photograph
       to fill a box 54px too tall. That 340 in the render was the floor itself,
       sitting in plain sight.
       So the override moves out here, where the frame takes the photograph's
       shape at every width. Nothing else changes: a stage with NO measured
       photo still gets 21/9 and the floor, which is what the gradient
       placeholder wants. */
    /* THE COLON IS LOAD-BEARING. Matching "--ar" as a substring also matches
       --art, which is the CSS background image the /listen/ hero carries, as in
       "--tone: 354; --art: url(...)". That stage has no --ar at all, so
       aspect-ratio:var(--ar) resolved to nothing while min-height:0 took away
       its 340px floor, and it collapsed to 1118x0 with the photograph gone.
       Alex caught it on production within the hour: "on the main listen page
       the hero has no image". "--ar:" cannot match "--art:". */
    .ar-stage[style*="--ar:"]{aspect-ratio:var(--ar);min-height:0;height:auto}
/* THE FADE NOW WAITS FOR THE PHOTOGRAPH. It used to be an animation that began
   the moment the frame was drawn, so on a slow night's photo the whole .32s was
   spent on an empty black box and the picture then hard-cut in at full opacity.
   That is the flash. MEASURED on the preview 2026-08-12, /listen/1987-04-09/:
   the photo's bytes landed 213ms (390px) to 1,528ms (900px) AFTER the fade had
   already finished — at every width tested. data-loaded is set by the image's
   own onload in stageMarkup(), so the fade runs when there is finally something
   to fade in. The keyframe is the no-JS failsafe, the same idiom [data-autofit]
   already uses here: if onload never runs, the picture simply appears. */
.ar-stage img{display:block;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .32s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));animation:ar-stage-failsafe 0s 3s forwards}
.ar-stage img[data-loaded]{opacity:1;animation:none}
@keyframes ar-stage-failsafe{to{opacity:1}}
@media (prefers-reduced-motion:reduce){.ar-stage img{transition:none;opacity:1}}
    .ar-stage::after{content:"";position:absolute;inset:0;background:linear-gradient(175deg,rgba(4,4,6,.34) 0%,rgba(4,4,6,.1) 34%,rgba(4,4,6,.9) 100%)}
    /* A PHONE NEEDS A DEEPER FLOOR (Alex, 2026-08-13: "the listen hero is not
       legible on mobile... can we just really darken the background"). The
       desktop scrim above reaches .9 only at the very bottom edge; at 390px
       the date, city and venue sit over the lower THIRD, which on a bright
       stage photograph is still washed out. Anchored to the bottom rather
       than flat across the frame, so the top half of the picture - lights,
       band, the part worth looking at - keeps its full strength. */
    /* EVERY stage on a phone, not just the lead (Alex: "it's still not fully
       visible on the other show images"). Identical curve, so the front door
       and the night cards read the same. */
    @media(max-width:700px){
      .ar-stage::after,
      .ar-stage-empty::after{background:linear-gradient(180deg,rgba(4,4,6,.12) 0%,rgba(4,4,6,.55) 26%,rgba(4,4,6,.86) 46%,rgba(4,4,6,.95) 66%,rgba(4,4,6,.97) 100%)}
    }
    .ar-stage figcaption{position:absolute;z-index:3;top:14px;right:14px;max-width:60%;padding:6px 11px;border:1px solid rgba(255,255,255,.16);border-radius:999px;background:rgba(8,8,10,.5);color:rgba(255,255,255,.78);font:500 10px/1.3 var(--sl-mono);-webkit-backdrop-filter:blur(9px);backdrop-filter:blur(9px)}
    .ar-stage figcaption a{color:inherit;text-decoration:underline;text-underline-offset:2px}
    /* No image is a designed state, not a hole: the night's own hue, a tie-dye
       wash and the year set at architectural scale. */
    .ar-stage-empty{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden;background:
      radial-gradient(120% 150% at 18% 8%,hsl(var(--tone) 52% 26%/.85),transparent 62%),
      radial-gradient(110% 130% at 88% 22%,hsl(calc(var(--tone) + 58) 46% 24%/.7),transparent 58%),
      linear-gradient(168deg,#111318,#08090c)}
    .ar-stage-empty::after{content:"";position:absolute;inset:0;background:linear-gradient(178deg,rgba(4,4,6,.2),rgba(4,4,6,.88))}
    .ar-stage-year{font:700 clamp(160px,26vw,360px)/.8 var(--sl-display);letter-spacing:-.06em;color:rgba(255,255,255,.055)}
    .ar-show-id{position:absolute;z-index:2;left:clamp(20px,3.4vw,44px);right:clamp(20px,3.4vw,44px);bottom:clamp(20px,3vw,38px)}
    .ar-show-id time{display:block;color:rgba(242,242,240,.64);font:400 12.5px/1.2 var(--sl-mono);letter-spacing:.14em;text-transform:uppercase}
    .ar-show-id h1{margin:12px 0 0;font-size:clamp(38px,6.4vw,84px);font-weight:680;letter-spacing:-.02em;color:#f2f2f0}
    .ar-show-id>p{margin:10px 0 0;color:rgba(242,242,240,.64);font-weight:440;font-size:clamp(16px,1.6vw,19px)}
    .ar-show-actions{display:flex;flex-wrap:wrap;align-items:center;gap:11px;margin-top:22px}
    /* The show page's play control is now the site's own .sc-chip pair (styled in
       stagelight.css, which this page loads). The all-caps mono .ar-play-show
       pill that used to live here is gone rather than left behind — one chip
       system, one place it is defined. */
    /* .ar-show-meta / .ar-dot / .ar-lineup are gone with the show hero's meta
       line (Alex, 7/28) — the hero carries date, city, venue and the play action
       only, and the lineup fact reads as a sentence in Show Notes. Deleted rather
       than left orphaned, so nothing here describes markup that no longer exists. */
    a.ar-show-era:hover{color:#fff;text-decoration:underline;text-underline-offset:3px}
    .ar-show-body{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(240px,.75fr);gap:clamp(30px,4vw,70px);margin-top:clamp(30px,4vw,52px)}
    /* IMAGE RIGHT, SETLIST LEFT (Alex's pick of three; matches burnthday.com's
       own show layout). The split show page is a two-column grid: identity and
       setlist read down the LEFT where reading starts, the photograph stands
       as a column asset on the RIGHT with the recordings under it. A portrait
       photo simply runs taller in its column instead of owning the screen.
       The rail narrows one step at tighter widths to make room — a trade Alex
       approved explicitly. */
    /* ROWS: auto auto 1fr (Alex, 7/29: "gigantic gap between [the photo] and
       recordings"). Without an explicit row plan, the setlist — spanning rows
       2-3 — helped size row 2, so the side column could start ~300px below
       where the photo actually ended. With row 3 flexible, the setlist's
       height pours into row 3 instead, and row 2 ends where the photo does:
       Recordings now starts directly under the image. */
    .ar-show.is-split{display:grid;grid-template-columns:minmax(0,1.55fr) clamp(320px,30vw,470px);grid-template-rows:auto auto 1fr;grid-template-areas:"head visual" "main visual" "main side";column-gap:clamp(30px,4vw,64px);row-gap:0;align-items:start}
    .ar-show.is-split .ar-show-head{grid-area:head;padding-bottom:clamp(22px,3vw,34px)}
    .ar-show.is-split .ar-show-head time{display:block;color:var(--sl-muted);font:600 11px/1.2 var(--sl-mono);letter-spacing:.12em;text-transform:uppercase}
    .ar-show.is-split .ar-show-head h1{margin:12px 0 6px;font:700 clamp(34px,4.2vw,62px)/.95 var(--sl-display);letter-spacing:-.035em;color:#fff}
    .ar-show.is-split .ar-show-head>p{margin:0;color:rgba(255,255,255,.8);font-size:clamp(15px,1.5vw,18px)}
    .ar-show.is-split .ar-show-head .ar-show-actions{margin-top:18px}
    /* The night's own facts, one line, mono like the date above it — a night
       with no notes and no photograph still says something true about itself. */
    /* Plain chronological navigation, distinct from the discovery rails below:
       two quiet slabs, previous on the left, next on the right. The empty span
       holds the grid column when a night has no neighbour on that side, so the
       first and last shows in the archive do not shift their sole button over. */
    /* BOTH BOTTOM RAILS SPAN THE WHOLE ARTICLE. They carry no grid-area, so
       before the rows were declared they auto-placed into an implicit row; once
       grid-template-rows named three, auto-placement dropped them into the
       narrow right column and the show cards crammed to 224px each against the
       right edge. Saying 1 / -1 makes the intent explicit instead of a
       side-effect of how many rows happen to be declared above. */
    .ar-show.is-split .ar-showstats,
    .ar-show.is-split .ar-steps,
    .ar-show.is-split .ar-onward{grid-column:1 / -1}
    .ar-steps{display:grid;grid-template-columns:1fr 1fr;gap:clamp(14px,1.8vw,26px);margin:clamp(30px,4vw,48px) 0 0}
    /* The cards ARE .ar-night now, so they inherit the archive's card language
       — framed art, radius, border, shadow, hover lift — and only the eyebrow
       and the right-alignment of the next card are added here. */
    .ar-steps .ar-step .ar-step-dir{position:absolute;top:14px;left:16px;z-index:2;font:600 10.5px/1.2 var(--sl-mono);letter-spacing:.12em;text-transform:uppercase;color:rgba(255,255,255,.85);text-shadow:0 1px 8px rgba(0,0,0,.7)}
    .ar-steps .ar-step-next .ar-step-dir{left:auto;right:16px}
    .ar-steps .ar-step.is-empty{border:0;background:none;box-shadow:none}
    .ar-steps .ar-step-next{text-align:right}
    /* The night's own facts sit a step below the venue, not level with it.
       Inheriting .ar-show-head>p made this read as important as the venue and
       shoved the play chip down the page. */
    .ar-show.is-split .ar-show-head .ar-show-summary{margin:8px 0 0;color:var(--sl-muted);font:500 12.5px/1.5 var(--sl-mono);letter-spacing:.03em}
    @media(max-width:560px){.ar-steps{grid-template-columns:1fr}.ar-steps .ar-step.is-empty{display:none}.ar-step-next{text-align:left}}
    /* NOT STICKY (Alex, 7/29). The photo used to follow the scroll, so the
       recordings and the night cards slid up BEHIND it — their outlines peeking
       out under the credit pill read as overlap on every report. It scrolls
       away with the page now, like everything else. */
    .ar-show.is-split .ar-show-visual{grid-area:visual}
    .ar-show.is-split .ar-show-visual .ar-show-stage{aspect-ratio:auto;min-height:0}
    /* The night's numbers: one scrolling row under the photograph, the
       homepage's rarity symbols at archive scale. Scrollbar hidden the same
       way the year strip hides it — the row invites a swipe, not chrome. */
    /* EC-homage Song Stats: the REFERENCE lives under the setlist as a quiet
       mono table — every song, every number, uncurated. The strip above the
       fold is now the STORY: it speaks only when a night has a debut or a
       bustout, per Alex ('curate them when it matters'). */
    .ar-showstats{margin:26px 0 0;border-top:1px solid var(--sl-line,rgba(255,255,255,.12));padding-top:14px}
    .ar-showstats summary{cursor:pointer;list-style:none;display:flex;align-items:baseline;gap:10px;font:600 11px/1.2 var(--sl-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted)}
    .ar-showstats summary::-webkit-details-marker{display:none}
    .ar-showstats summary span{letter-spacing:0;text-transform:none;font-weight:400;font-size:11.5px}
    /* The table IS the house .data-table from stagelight.css — the site has
       one table language and this page had no business inventing a second
       (Alex: "looks nothing like the other tables... it is ugly"). Only the
       numeric right-alignment and the summary line are scoped here. */
    .ar-showstats .data-table{table-layout:auto}
    .ar-showstats .data-table th:nth-child(n+3),.ar-showstats .data-table td:nth-child(n+3){text-align:right;font-family:var(--sl-mono)}
    .ar-showstats .data-table td:first-child{font-weight:650}
    .ar-showstats .data-table a{color:inherit;text-decoration:underline;text-underline-offset:2px;text-decoration-color:color-mix(in oklab,currentColor,transparent 65%)}
    /* THE TRENDING COLUMN. It takes the room the full-width table freed up —
       the line needs horizontal distance to read as a slope at all. Its
       subtitle says what the line counts, because a chart nobody can name the
       units of is decoration. The Gap wedge gets the other wide share. */
    .ar-showstats .data-table th.ar-trend-col{width:24%;text-align:left}
    .ar-showstats .data-table th.ar-trend-col span,.ar-showstats .data-table th.ar-gap-col span{display:block;margin-top:3px;font:400 10px/1.3 var(--sl-mono);letter-spacing:.02em;text-transform:none;color:var(--sl-faint,#6b6f78)}
    .ar-showstats .data-table td.ar-trend-col{text-align:left;padding-top:6px;padding-bottom:6px}
    /* The table scrolls sideways on a phone rather than crushing six columns
       into 375px; on wide screens the wrapper is inert. */
    .ar-showstats-scroll{overflow-x:auto;scrollbar-width:thin}
    .ar-showstats-scroll .data-table{min-width:640px}
    /* Rarity beside the name: the strip's own bursts at table scale. The slot
       is on EVERY row (empty on the routine ones) so the song names stay on
       one left edge — a burst is a flag, not an indent. */
    .ar-showstats td.ar-ss-song .rarity-symbol{display:inline-block;width:34px;min-width:34px;vertical-align:-2px}
    .ar-showstats td.ar-ss-song .rarity-symbol svg{height:12px;width:auto;display:inline-block}
    /* MOTION IS ENTRANCE-ONLY (Alex, 8/7: the animation "look[s] like ass" and
       the bars are "menacing on the brain" while scrolling). It was 126 layers
       drifting forever behind blur and glow, repainting on every scroll frame.
       The tie-dye is now a still gradient — it never needed to move to read —
       and the only motion left is the one that carries data: the span crossing
       once, on arrival. Compositor-only properties, nothing perpetual.

       ── THE GAP IS A SPAN, NOT A BAR ──────────────────────────────────────
       A wedge draws magnitude; a gap is an ABSENCE (Alex, 8/6). Left cap is the
       last time they played it, right cap is tonight, and the emptiness between
       them is what the number counts. The vessel is the SAME shape Last 100
       uses — one family holding different contents — and it holds slow weather
       rather than sitting flat, so a long absence has something moving in it.
       THE MOTION IS THE ENCODING: the span travels, and its duration is set by
       the gap itself, so a 1,212-show bustout takes visibly longer to cross
       than a four-show wait. You feel the distance before you read the digit. */

    /* NAMED AND NOT LINKED was the state of this page: seven organisations
       thanked by name, ten mentions, zero links, while 31 links existed
       elsewhere on it. A thank-you that names someone and does not point at
       them is a hollow thank-you. But the prose IS the page here, so the links
       stay invisible at rest and only the first mention of each name is wired —
       four Everyday Companions underlined would read as a directory, not a
       thank-you. On hover the tie-dye the site already uses for song titles
       draws in, so this borrows a link language rather than inventing one. */
    body.stagelight .thanks-link{color:inherit;text-decoration:none;position:relative}
    body.stagelight .thanks-link::after{content:"";position:absolute;left:0;right:0;bottom:-2px;height:1.5px;
      background:linear-gradient(90deg,#d4514f,#60a5d2 58%,#c9a35f);
      background-size:180% 100%;border-radius:1px;
      transform:scaleX(0);transform-origin:left center;opacity:0;
      transition:transform .28s cubic-bezier(.2,.7,.3,1),opacity .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    body.stagelight .thanks-link:hover::after,
    body.stagelight .thanks-link:focus-visible::after{transform:scaleX(1);opacity:.95}
    body.stagelight .thanks-link:focus-visible{outline:none}
    @media (prefers-reduced-motion:reduce){
      body.stagelight .thanks-link::after{transition:none}
    }
        /* THE TAPER'S OWN TRACK LABELS OPEN FROM A BUTTON THAT LOOKS LIKE ONE.
       It read "As labelled on the tape" in flat small caps with nothing to say
       it could be opened, so nobody opened it (Alex, 8/7). Named for what it is
       and given a chip's affordance — border, hover, and a caret that turns. */
    .ar-tapelist>summary{list-style:none;cursor:pointer;display:block;padding:0;margin:0}
    .ar-tapelist>summary::-webkit-details-marker{display:none}
    .ar-tapelist-btn{display:flex;width:100%;align-items:center;justify-content:space-between;gap:8px;
      padding:12px 16px;border-radius:10px;
      border:1px solid var(--sl-line,rgba(255,255,255,.16));background:rgba(255,255,255,.035);
      font:600 10.5px/1 var(--sl-mono);letter-spacing:.13em;text-transform:uppercase;
      color:var(--sl-muted);transition:color .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),border-color .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-tapelist-btn::after{content:"";width:6px;height:6px;border-right:1.5px solid currentColor;
      border-bottom:1.5px solid currentColor;transform:rotate(45deg) translateY(-1px);
      transition:transform .2s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-tapelist>summary:hover .ar-tapelist-btn{color:var(--sl-ink,#f2f2f0);
      border-color:rgba(255,255,255,.34);background:rgba(255,255,255,.06)}
    .ar-tapelist[open] .ar-tapelist-btn::after{transform:rotate(-135deg) translateY(-1px)}
    .ar-tapelist>summary:focus-visible .ar-tapelist-btn{outline:2px solid var(--sl-line,rgba(255,255,255,.4));outline-offset:2px}
.ar-showstats th.ar-gap-col{width:23%}
    .ar-showstats th.ar-meter-col{width:172px}
    .ar-showstats th.ar-ord-col{width:84px;text-align:right}
    /* THE PHONE. Measured at 390px on 8/7, not eyeballed: the 640px min-width
       put the table 266px past the right edge, so Last 100 and # were off the
       screen entirely and Gap was clipped mid-barbell. The sideways scroll that
       was meant to save them is the wrong instrument for a data table — the
       moment you swipe to a number, the song name it belongs to has scrolled
       away, which is Alex's "the song info looks off to the right" and "reads
       as two unrelated columns" in one. Five facts, one screen, no swipe.
       The design is untouched: same columns, same barbell, same marks.
       THIS BLOCK MUST STAY BELOW THE THREE WIDTH RULES ABOVE. A media query
       adds no specificity, so written any earlier the 172px simply wins and
       the table stays 567px wide — which is exactly what the first attempt
       measured.
       NARROWING THE COLUMNS WAS TRIED FIRST AND MEASURED FAILING. Under
       table-layout:auto a width is a suggestion, and the meter cell's own
       content refuses to go below ~168px, so the table stayed 567px and the
       only thing the attempt achieved was moving the overflow from the
       wrapper up to the whole page (documentElement 583 against a 390
       viewport). Five columns with two drawn meters do not fit 358px. So the
       row stops being a row: on a phone each song is one block, two lines.

           * Down                          149th
             07/10/26    |--| 7      ||... 11

       Line one names the song and says which performance it was. Line two
       carries when, how long since, and how often — the three facts that
       only mean anything next to each other. Nothing is dropped, nothing
       needs a sideways swipe, and both edges sit on the page's own gutter. */
    @media(max-width:640px){
      .ar-showstats-scroll{overflow-x:visible}
      .ar-showstats-scroll .data-table{min-width:0;width:100%;display:block}
      /* A header row cannot label a stacked block — it would sit above the
         first song describing columns that no longer exist. The meters keep
         their aria-labels, so nothing is lost to a screen reader. */
      .ar-showstats .data-table thead{display:none}
      .ar-showstats .data-table tbody{display:block}
      .ar-showstats .data-table tbody tr{display:grid;
        grid-template-columns:max-content minmax(0,1fr) minmax(0,1fr);
        grid-template-areas:"song song ord" "date gap meter";
        align-items:center;column-gap:12px;row-gap:7px;padding:13px 0;
        border-bottom:1px solid var(--sl-line,rgba(255,255,255,.12))}
      .ar-showstats .data-table tbody td{display:block;padding:0;border:0;text-align:left}
      .ar-showstats .data-table tbody td.ar-ss-song{grid-area:song;line-height:1.25}
      .ar-showstats .data-table tbody td:nth-child(2){grid-area:date}
      .ar-showstats .data-table tbody td.ar-gap-col{grid-area:gap}
      .ar-showstats .data-table tbody td.ar-meter-col{grid-area:meter}
      .ar-showstats .data-table tbody td.ar-ord-col{grid-area:ord;text-align:right;align-self:start}
      /* The ordinal's suffix is width:0 by design, so the DIGITS line up down
         the desktop column and the "th" hangs into the cell's right padding.
         Stacked, that padding is the page's right gutter, so the suffix hung
         off the screen edge and was clipped — "307th" read as "307t". There is
         no column to align to here, so it takes its real width back. */
      .ar-showstats .data-table tbody td.ar-ord-col i{width:auto}
      /* The 34px slot existed to hold the names on one left edge in a column.
         The name now owns the full width, so the mark sits tight against it
         instead of pushing the first word up onto its own line
         ("* Big / Wooly Mammoth"). */
      .ar-showstats td.ar-ss-song .rarity-symbol{width:auto;min-width:0;margin-right:8px}
      /* Both vessels share one line now, so they have to be allowed to shrink;
         a flex item's default min-width:auto is what would stop them. */
      .ar-gap,.ar-meter{gap:7px;min-width:0}
      .ar-gap-vessel{min-width:0}
    }
    .ar-gap,.ar-meter{display:flex;align-items:center;gap:11px}
    .ar-gap-vessel,.ar-meter{position:relative;height:18px;border-radius:9px}
    .ar-gap-vessel{flex:1 1 auto;display:flex;align-items:center;padding:0 3px;overflow:hidden;
      background:rgba(255,255,255,.055);box-shadow:inset 0 0 0 1px rgba(255,255,255,.045)}
    .ar-gap-air{position:absolute;inset:0;opacity:.42;pointer-events:none;
      background:
        radial-gradient(60% 140% at 20% 50%,rgba(212,81,79,.5),transparent 70%),
        radial-gradient(60% 140% at 55% 50%,rgba(96,165,210,.5),transparent 70%),
        radial-gradient(60% 140% at 88% 50%,rgba(201,163,95,.45),transparent 70%);
      background-size:200% 100%,240% 100%,180% 100%}
    .ar-cap{position:relative;flex:none;width:7px;height:18px;border-radius:9px;overflow:hidden}
    .ar-showstats tr:not(.is-seen) .ar-gap-span{width:var(--gw)}
    .ar-showstats tr:not(.is-seen) .ar-cap-from,.ar-showstats tr:not(.is-seen) .ar-cap-to{opacity:1;animation:none}
    .ar-showstats tr:not(.is-seen) .ar-meter-on{clip-path:none;animation:none}
    /* ARMED — the state between "the script is running" and "you have reached
       the table". Alex, 8/7: the animation "loaded out of order" and "the bars
       did not animate at all". Both were one fault, measured on the preview:
       the rules directly above are the NO-SCRIPT state, and they draw every
       bar at its FINAL width. So the bars painted full, and then scrolling to
       them added .is-seen, which starts the animation at zero — a visible jump
       BACKWARDS before the growth. Where the table was already on screen the
       reset and the refill fell in the same frame and there was no animation
       to see at all.
       Keeping the finished state for a reader with no JavaScript is right, so
       it stays. But once observeShowStats has wired itself the script owns the
       entrance, and it says so by adding .is-armed on the same line — which
       holds the bars empty from the first frame instead of filling them and
       then taking it back. */
    .ar-showstats.is-armed tr:not(.is-seen) .ar-gap-span{width:0}
    .ar-showstats.is-armed tr:not(.is-seen) .ar-cap-from,.ar-showstats.is-armed tr:not(.is-seen) .ar-cap-to{opacity:0}
    .ar-showstats.is-armed tr:not(.is-seen) .ar-meter-on{clip-path:inset(0 100% 0 0)}
    .ar-showstats tr.is-seen .ar-gap-span{flex:none;width:0;height:2px;
      background:repeating-linear-gradient(90deg,rgba(255,255,255,.2) 0 4px,transparent 4px 9px);
      animation:ar-cross var(--gd,900ms) cubic-bezier(.22,.61,.36,1) forwards;
      animation-delay:calc(var(--in,0)*26ms + 120ms)}
    .ar-gap-span{flex:none;height:2px;background:repeating-linear-gradient(90deg,rgba(255,255,255,.2) 0 4px,transparent 4px 9px)}
    @keyframes ar-cross{to{width:var(--gw)}}
    /* THE STAGGER COUNTS ARRIVALS, NOT ROWS. --in is the row's place in the batch
       that revealed together, set by observeShowStats at reveal time; --row is its
       fixed place in the table and is still in the markup for everything else.
       Keyed to --row, row 20 carried 520ms of delay it never earned — and on a
       phone, where rows arrive one at a time, that is 520ms of nothing. */
    .ar-cap-from{opacity:0;animation:ar-land .32s cubic-bezier(.2,.8,.3,1) forwards;animation-delay:calc(var(--in,0)*26ms)}
    .ar-cap-to{opacity:0;animation:ar-land .42s cubic-bezier(.16,1.1,.3,1) forwards;
      animation-delay:calc(var(--in,0)*26ms + var(--gd,900ms) + 60ms)}
    @keyframes ar-land{from{opacity:0;transform:scaleY(.3)}to{opacity:1;transform:scaleY(1)}}
    .ar-gap>b,.ar-meter>b{flex:none;font:500 12px/1 var(--sl-mono);font-variant-numeric:tabular-nums;color:var(--sl-muted)}
    .ar-gap.is-deep .ar-cap{box-shadow:0 0 0 1px rgba(232,199,102,.5)}

    /* ── THE LIVING SURFACE ── one white body carrying three tie-dye tints that
       wander on coprime clocks, so nothing ever sweeps and the pattern never
       repeats. Every lit part of every mark is a window onto it. */
    .ar-liv{position:absolute;inset:0;overflow:hidden;background:rgba(255,255,255,.3)}
    .ar-liv s{position:absolute;inset:-70%;display:block;mix-blend-mode:screen;opacity:.9}
    .ar-t1{background:radial-gradient(closest-side at 30% 50%,rgba(212,81,79,.95),rgba(212,81,79,0) 72%)}
    .ar-t2{background:radial-gradient(closest-side at 62% 50%,rgba(96,165,210,.95),rgba(96,165,210,0) 72%)}
    .ar-t3{background:radial-gradient(closest-side at 88% 50%,rgba(201,163,95,.9),rgba(201,163,95,0) 74%)}

    /* ── LAST 100 ── five bars in the same vessel, square root and snapped to
       half a bar: three quarters of all songs sit at 25 or under, so a linear
       scale crushed them into the first bar and drew the rest as a sliver. */
    .ar-meter{flex:none;width:132px;overflow:hidden;
      background:rgba(255,255,255,.055);box-shadow:inset 0 0 0 1px rgba(255,255,255,.045);
      --mask:repeating-linear-gradient(90deg,#000 0 22px,transparent 22px 26.4px)}
    .ar-meter-on{position:absolute;left:2px;top:2px;bottom:2px;width:var(--mw);border-radius:7px;
      overflow:hidden;-webkit-mask:var(--mask);mask:var(--mask);
      clip-path:inset(0 100% 0 0);animation:ar-fill .5s cubic-bezier(.3,.7,.3,1) forwards;
      animation-delay:calc(var(--in,0)*26ms + 200ms)}
    @keyframes ar-fill{to{clip-path:inset(0 0 0 0)}}
    .ar-meter>b{position:absolute;right:9px;top:50%;transform:translateY(-50%);z-index:2}
    .ar-ord-col{text-align:right;font-family:var(--sl-mono);font-variant-numeric:tabular-nums;color:var(--sl-muted)}
    .ar-ord-col i{font-style:normal;font-size:.86em;color:var(--sl-faint,#6b6f78);display:inline-block;width:0}
    @media (prefers-reduced-motion:reduce){
      .ar-gap-air,.ar-liv s{animation:none}
      .ar-gap-span{animation:none;width:var(--gw)}
      .ar-cap-from,.ar-cap-to{animation:none;opacity:1}
      .ar-meter-on{animation:none;clip-path:none}
    }}
    @media(prefers-reduced-motion:reduce){.ar-gapbar i{animation:none}}
    /* TEN TICKS. The filled ones are the /10 count — countable, so the mark
       is the number rather than standing in front of it. Filled ticks borrow
       the heat red the trend line already speaks. */
    .ar-showstats th.ar-ticks-col{text-align:left}
    .ar-showstats td.ar-ticks-col{text-align:left}
    .ar-ticks{display:inline-flex;gap:3px}
    .ar-ticks i{width:4px;height:12px;border-radius:1px;background:rgba(255,255,255,.13)}
    .ar-ticks i.is-on{background:#e5726f}
    /* The colours are Tonight's Odds' own heat tiers (.tn-hot / .tn-warm), so a
       song in heavy rotation is the same red here as it is on the homepage.
       currentColor carries it into the stroke. */
    .ar-trend{display:block;width:100%;height:34px;overflow:visible}
    .ar-trend.is-hot{color:#e5726f}
    .ar-trend.is-warm{color:#e5b3b1}
    .ar-trend.is-cold{color:var(--sl-faint,#6b6f78)}
    /* Drawn in, not blinked in — the year ribbon's gesture, arrived at a
       different way. A stroke-dashoffset draw cannot work here: the viewBox is
       stretched horizontally to fill the column, the dash pattern is measured
       after that stretch, and the line comes out full of holes. This wipes a
       clip-path across the SVG's own box instead, which does not care what the
       geometry inside it is doing. The stagger walks DOWN the table. */
    @keyframes ar-trend-draw{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
    .ar-trend{clip-path:inset(0 100% 0 0);animation:ar-trend-draw .5s cubic-bezier(.2,.7,.3,1) forwards;animation-delay:calc(var(--row,0) * 22ms)}
    @media(prefers-reduced-motion:reduce){.ar-trend{clip-path:none;animation:none}}
    .ar-fact-strip{display:flex;gap:8px;margin:14px 0 0;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:2px}
    .ar-fact-strip::-webkit-scrollbar{display:none}
    .ar-fact{flex:none;display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border:1px solid var(--sl-line,rgba(255,255,255,.12));border-radius:10px;background:rgba(255,255,255,.03)}
    .ar-fact .rarity-symbol{display:inline-flex;width:20px;flex:none}
    .ar-fact .rarity-symbol svg{width:100%;height:auto;display:block}
    .ar-fact-body{display:grid;gap:1px}
    .ar-fact-body strong{font:650 12.5px/1.2 var(--sl-display);color:rgba(255,255,255,.94);white-space:nowrap}
    .ar-fact-body small{font:500 10px/1.3 var(--sl-mono);letter-spacing:.05em;color:var(--sl-muted);white-space:nowrap}
    .ar-fact-body a{color:inherit;text-decoration:underline;text-underline-offset:2px}
    /* Type is off the photo, so the heavy identity scrim would be mud on the
       picture; a whisper stays for the figcaption pill. */
    .ar-show.is-split .ar-stage::after{background:linear-gradient(180deg,rgba(4,4,6,.22) 0%,rgba(4,4,6,0) 26%,rgba(4,4,6,.14) 100%)}
    .ar-show.is-split>.ar-show-body{display:contents}
    .ar-show.is-split .ar-show-main{grid-area:main;min-width:0}
    .ar-show.is-split .ar-show-side{grid-area:side;margin-top:clamp(26px,3vw,40px)}
    @media(max-width:1400px){.ar-shell{--rail-w:196px;--rail-full:196px}}
    .ar-show-main{min-width:0}

    /* ---- setlist: the best thing on the page ---- */
    .ar-setlist{display:grid;gap:19px}
    /* THE LABEL SITS ON THE FIRST SONG LINE'S BASELINE (Alex, 7/28).
       It used to be nudged with a 6px padding, which cannot hold: the songs are
       clamp(17px,1.9vw,21px) on a 2.05 line-height, so their first baseline moves
       with the viewport while a fixed padding does not — the label floated high
       on wide screens and low on narrow ones. Grid baseline alignment aligns the
       two items' FIRST baselines by definition, so it stays correct at every size
       and for one-line and many-line sets alike. */
    /* THE LABEL COLUMN IS THE WIDEST LABEL, NOT A ROUND NUMBER, AND THE LABELS
       ARE RIGHT-ALIGNED IN IT. Alex, 2026-08-12: "there's just a gigantic gap
       ... typically it's just one colon space." MEASURED at 1280px: the column
       was 66px, "Set 1" is 37.8px of ink and "Encore" — the widest label this
       page can print — is 45.4px, so every row carried 20-28px of dead space
       before the 14px gap even started. That is the 42px empty run he saw.
       48px fits Encore with room to spare, and right-aligning means EVERY
       label ends on the same line a fixed 12px from the songs, whatever its
       length — so the gap reads as deliberate spacing instead of ragged
       leftovers, and the songs gain 20px of measure.
       align-items:start, not baseline: the label is 10.5px and the songs are
       19px on a 28.5px line, so a shared baseline dropped the small label to
       the bottom of the big line box and it read as sitting low (his "should
       they be centre aligned?"). Giving the label the songs' own line height
       instead makes its box the same height as the first song line, so it
       centres against that line — the same idiom .home-hero .sc-label already
       uses. NOT align-items:center, which would centre it against the whole
       multi-line block and leave it floating beside nothing. */
    .ar-set{display:grid;grid-template-columns:48px minmax(0,1fr);gap:12px;align-items:start}
    .ar-set-label{padding-top:0;text-align:right;color:var(--sl-faint,#6b6f78);font:600 10.5px/28.5px var(--sl-mono);letter-spacing:.12em;text-transform:uppercase}
    /* 1.8 on desktop (Alex, 8/7). 2.05 was generous for a list and made the
       setlist read as separate items rather than a set played in one breath.
       The MOBILE rule stays at 2.4 and is load-bearing: the song buttons carry
       10px padding for a 41px tap target, and tighter leading lets two padded
       songs on consecutive lines overlap. */
    /* 19px / 1.5, Alex's call 8/7 — "the font size on the listen show pages is
       too large so only a couple songs fit on each line". It was 21px / 1.6
       (and 1.8 before that in the same conversation).
       THE GAIN PLATEAUS AT 19, WHICH IS WHY IT STOPS THERE. Measured across the
       whole setlist at 1440: 21px gives 2.63 songs a line, and 19, 18, 17 and
       16 ALL give 3.50 — the limit is how long the song names are, not the type
       size. Below 19 the only thing that changes is the setlist getting shorter
       (171px at 19, down to 134 at 16) while the type gets smaller for nothing.
       So this takes the entire line-length gain at the largest size that offers
       it. The setlist is the shape of the night and has to keep its weight;
       shrinking further would make it read as fine print and buy no songs.
       The phone is untouched — the media query below sets its own 18px/2.4, and
       that leading is load-bearing for the 41px tap targets. */
    .ar-set-songs{margin:0;font-size:clamp(16px,1.6vw,19px);line-height:1.5;letter-spacing:-.01em}
    /* No pill. The title itself is the target: weight comes up and a tie-dye
       underline is drawn tight under the type.
       The underline MUST hang off the song's own text box, not its line box —
       that distinction is the entire bug Alex reported. An inline button's box
       is as tall as the paragraph's 2-ish line-height, so an underline anchored
       to its bottom lands on top of the next line. inline-block + a tight
       internal line-height shrinks the box back to the glyphs, so "bottom" means
       just under the descenders and nothing below can be touched. */
    /* A TITLE THAT CANNOT FIT BREAKS AT A SPACE, rather than painting over the
       page. .ar-song inherits nowrap from .ar-nb — which is right for the WRAPPER,
       whose job is to keep a song welded to its "," or ">" — but wrong for the
       title itself. With nowrap inherited and max-width:100%, the BOX was clamped
       to the column while the TEXT kept going: on 1996-01-27, "I'm Just an Old
       Chunk of Coal (But I'm Gonna Be a Diamond Someday)" measured 677px of ink in
       a 522px box and painted 91px into the recording card beside it (Alex, 08-02:
       "why is the setlist spilling into the card on the right?").
       Measured, not guessed — and the reason it hid so long is that
       getBoundingClientRect() reports the CLAMPED box, so the overlap is invisible
       to any check that measures boxes. scrollWidth vs clientWidth is what shows it.
       Only titles too long for the column wrap; on that page exactly one does. */
    .ar-song{position:relative;display:inline-block;max-width:calc(100% - 1.25em);white-space:normal;line-height:1.15;vertical-align:baseline;padding:0;border:0;background:none;color:inherit;font-family:inherit;font-size:inherit;text-align:left;cursor:pointer;transition:color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),font-weight .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));font-weight:450}
    .ar-song:not(.is-static):is(:hover,:focus-visible,.is-playing,.is-current)::before{content:"";position:absolute;left:0;right:0;bottom:-.02em;height:1.5px;border-radius:2px;background:rgba(255,255,255,.22);pointer-events:none}
    .ar-song::after{content:"";position:absolute;left:0;right:0;bottom:-.16em;height:1.5px;border-radius:2px;background:var(--ar-tie);background-size:240% 100%;opacity:0;transform:scaleX(.35);transform-origin:left;transition:opacity .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),transform .22s cubic-bezier(.2,.7,.3,1)}
    /* Hover must NOT change weight. A weight shift changes the text's width, so
       the setlist reflows under the pointer and the whole surface feels unstable.
       Geometry stays fixed: only colour, the underline and brightness respond. */
    .ar-song:hover,.ar-song:focus-visible{color:#fff}
    .ar-song:hover{outline:none}
    /* Hover cannot be the only signal: a keyboard user gets the same weight and
       underline PLUS a real ring, so focus is never ambiguous. */
    .ar-song:focus-visible{outline:2px solid rgba(255,255,255,.85);outline-offset:3px;border-radius:3px}
    /* Hover raises the quiet gray line only (::before). The tie-dye ::after is
       playback's alone — the fill that crosses the song as the tape runs. */
    .ar-song.is-current{--song-progress:0%;color:transparent;background:var(--ar-tie);background-size:220% 100%;-webkit-background-clip:text;background-clip:text;animation:ar-tiedye 8s ease-in-out infinite alternate}
    /* The played-through indicator is the same tight underline, filled to the
       song's own progress. */
    .ar-song.is-current::after{opacity:1;transform:none;right:auto;width:var(--song-progress);max-width:100%;transition:width .18s linear}
    .ar-song.is-static{cursor:default}
    .ar-song.is-static::after{display:none}
    /* LEADING: the tape is on a track no setlist song owns — an Intro, tuning,
       the walk back on for an encore — and the NEXT mapped song is coming. Its
       letters fill with tie-dye from the left as that in-between track runs out,
       so you can watch the night head into Fishwater before Fishwater starts.
       Two layers clipped to the text: the gradient sized to var(--lead), solid
       white behind it. archive-app.mjs sets --lead; home-player.mjs paints the
       identical state on the main site. */
    /* ---- POSITIONAL HINTS: where a thing sits inside a song ----
       The setlist is one line of prose per set, but the tape knows more than the
       line does: a tease happens somewhere in the MIDDLE of a song, and a
       standalone Jam runs INTO the song that follows it. Both facts are already
       in the data (units.teases / units.raps carry the song they happened
       during; a standalone unit carries its segue), so they are drawn rather
       than written out a second time.

       NO TEASE GLINT (Alex, 7/29: "There should be no tease indicator with the
       tie-die on the setlists" — the Porch Song glow. The mid-song --ar-glint
       wash is deleted on BOTH surfaces, not restyled, and .has-unit still rides
       in the markup for the hover/aria hint). What remains is the lead-in: the
       first-letters sliver for a song a standalone Jam segued into, which he
       has never flagged. A lead-in = the first sliver only, static and quiet.

       :not(.is-current):not(.is-leading) because live playback outranks a
       resting hint every time. */
    .ar-song.has-lead-in:not(.is-current):not(.is-leading){color:transparent;background-repeat:no-repeat,no-repeat;background-clip:text;-webkit-background-clip:text;background-image:var(--ar-leadin),linear-gradient(var(--sl-ink),var(--sl-ink));background-size:13% 100%,100% 100%;background-position:left center,left center}
    /* Hover brightens the base layer instead of the colour, since the colour is
       transparent while a hint is painted. Same brightening, same geometry. */
    .ar-song.has-lead-in:not(.is-current):not(.is-leading):hover,.ar-song.has-lead-in:not(.is-current):not(.is-leading):focus-visible{background-image:var(--ar-leadin),linear-gradient(#fff,#fff)}
    .ar-song.is-leading{--lead:0%;color:transparent;background-image:var(--ar-tie),linear-gradient(#fff,#fff);background-size:var(--lead) 100%,100% 100%;background-repeat:no-repeat,no-repeat;background-position:left center,left center;-webkit-background-clip:text;background-clip:text;transition:background-size .2s linear}
    .ar-song.is-leading::after{display:none}
    @keyframes ar-tiedye{from{background-position:0% 50%}to{background-position:100% 50%}}
    .ar-segue{color:var(--sl-muted);font-weight:400;padding:0 .12em}
    /* A SEPARATOR BELONGS TO THE SONG BEFORE IT (Alex, 7/28: commas and ">" were
       stranded at the start of wrapped lines). The mark is an inline span after a
       song button, and the break opportunity sits BETWEEN them — so the fix is a
       word joiner at the head of each mark (see setlistMarkup), which forbids a
       break at exactly that point.
       Deliberately NOT white-space:nowrap on these spans: that also makes the
       mark's TRAILING space unbreakable, so the whole ", " had to travel to the
       next line with the song that followed it — which put the comma back at a
       line start, the very thing being fixed. The trailing space must stay a
       break opportunity; only the joint before the mark must not be one. */
    .ar-set-songs{text-wrap:pretty}
    .ar-sep{color:var(--sl-muted)}
    .ar-set-songs>:last-child.ar-sep{display:none}
    /* The comma-glue wrappers (.ar-nb) made the old last-child rule blind — the
       final separator now lives INSIDE the last wrapper, so the set printed a
       trailing comma. Hide it wherever a glued unit closes its container. */
    .ar-set-songs>.ar-nb:last-child .ar-sep,p>.ar-nb:last-child .ar-sep{display:none}
    .ar-tracks{margin:0;padding:0;list-style:none;border-top:1px solid var(--sl-line,rgba(255,255,255,.1))}
    .ar-track{display:flex;justify-content:space-between;align-items:baseline;gap:16px;width:100%;padding:11px 6px;border:0;border-bottom:1px solid var(--sl-line,rgba(255,255,255,.07));background:none;color:inherit;font:inherit;text-align:left;cursor:pointer}
    .ar-track:hover{background:rgba(255,255,255,.05)}
    .ar-track.is-current{font-weight:660}
    .ar-track-time{flex:none;color:var(--sl-muted);font:11px var(--sl-mono);font-variant-numeric:tabular-nums}

    /* ---- sources ---- */
    .ar-show-side{min-width:0}
    .ar-sources{display:grid;gap:7px}
    .ar-source{display:block;width:100%;padding:13px 15px;border:1px solid var(--sl-line);border-radius:10px;background:none;color:inherit;text-align:left;cursor:pointer;transition:border-color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-source:hover{border-color:var(--sl-line-strong);background:rgba(255,255,255,.04)}
    .ar-source.is-active{border-color:rgba(255,255,255,.4);background:rgba(255,255,255,.06)}
    .ar-source strong{display:block;font-size:14px;font-weight:620}
    .ar-source small{display:block;margin-top:4px;color:var(--sl-muted);font-size:11.5px;line-height:1.45;overflow-wrap:anywhere}
    .ar-source-len{font-family:var(--sl-mono);font-size:10.5px !important;letter-spacing:.04em}
    .ar-archive-link{display:inline-block;margin-top:14px;color:var(--sl-muted);font-size:12px;text-decoration:underline;text-underline-offset:3px}
    .ar-archive-link:hover{color:var(--sl-ink)}


    /* ---- atmosphere: the homepage mechanism, reused ---- */
    /* Scoped to body.listen-archive for the same reason the dock is: the site's
       body.stagelight > * { position: relative } out-specifies a bare class and
       would drop this back into document flow as a 520px hole. */
    body.listen-archive > .ar-echo{position:fixed;z-index:0;top:0;left:0;right:0;height:520px;overflow:hidden;pointer-events:none;opacity:0;transition:opacity .6s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-echo.is-on{opacity:1}
    .ar-echo::before{content:"";position:absolute;inset:0;background-image:var(--echo);background-size:cover;background-position:center 28%;transform:scale(1.35);filter:blur(30px) saturate(1.12);opacity:.34}
    .ar-echo::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(9,10,13,.72) 0%,rgba(9,10,13,.5) 22%,rgba(9,10,13,.86) 62%,var(--sl-bg,#090a0d) 100%)}
    /* The glow the tint publishes is the same custom property the homepage hero
       drives, so the archive inherits that behaviour instead of re-deriving it. */
    body.listen-archive{background:
      radial-gradient(62% 40% at 74% 2%,var(--hero-glow,transparent),transparent 72%),
      radial-gradient(52% 34% at 10% 0%,var(--hero-glow,transparent),transparent 70%),
      var(--sl-bg,#090a0d);
      background-attachment:fixed}
    .ar-shell,.ar-dock{position:relative;z-index:1}
    .ar-show-stage{box-shadow:0 40px 90px -40px var(--hero-glow-strong,rgba(0,0,0,.8)),0 30px 70px -30px rgba(0,0,0,.75)}

    /* ---- cabinet icons + years folder ---- */
    .ar-cab-group a,.ar-cab-group button{display:flex;align-items:center;gap:10px;padding:7px 0 7px 2px}
    .ar-cab-group svg{width:17px;height:17px;flex:none;opacity:.62;transition:opacity .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-cab-group a:hover svg,.ar-cab-group button:hover svg,.ar-cab-group a.is-active svg{opacity:1}
    .ar-cab-group a::before,.ar-cab-group button::before{display:none}
    .ar-cab-group a.is-active{color:var(--sl-ink)}
    /* Years is a contained drawer, not an accordion that shoves Songs/Places/Eras
       down the page. The cabinet's height never changes: all 42 years live in a
       fixed-height scroller, and the chevron sits directly beside the label. */
    .ar-years-folder{display:grid}
    .ar-years-toggle{width:100%;gap:10px !important}
    .ar-years-chev{width:12px !important;height:12px !important;margin-left:2px;opacity:.5 !important;transition:transform .22s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),opacity .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-years-toggle:hover .ar-years-chev{opacity:.9 !important}
    .ar-years-toggle.is-open .ar-years-chev{transform:rotate(180deg);opacity:1 !important}
    .ar-years-list[hidden]{display:none}
    /* Inline expansion: the list pushes Songs/Places/Eras down inside the rail's
       single scroll. No inner scrollbar, no "show more". */
    /* No indent rule. The step in from the toggle is what says these belong to it;
       a hairline down the left edge was one more line on a page already made of
       them (Alex). The total left offset is unchanged, so nothing shifts. */
    .ar-years-list{display:grid;gap:0;margin:2px 0 4px 37px;padding:2px 0}
    .ar-years-list a{display:flex;align-items:baseline;gap:8px;padding:7px 4px 7px 0;color:var(--sl-muted);font:500 15px/1.2 var(--sl-mono);letter-spacing:.04em}
    .ar-years-list a em{font-style:normal;font-size:12px;color:var(--sl-faint,#6b6f78);opacity:.8}
    .ar-eras-list a{font-family:var(--sl-display);font-size:15.5px;letter-spacing:0}
    .ar-years-list a:hover{color:var(--sl-ink)}
    .ar-years-list a.is-active{color:transparent;background:var(--ar-tie);background-size:300% 100%;-webkit-background-clip:text;background-clip:text;font-weight:700}
    .ar-years-list a.is-active em{color:var(--sl-ink);opacity:1}

    .ar-latest-lead .ar-nights.is-lead{grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}
    .ar-nights.is-lead .ar-night{aspect-ratio:4/3}
    .ar-nights.is-lead .ar-night-city{font-size:clamp(22px,2.4vw,28px)}



    /* ---- year ribbon: a tour path, not a chart ---- */
    /* NO SCROLL CONTAINER, EVER (Alex, 7/28: the scrollbar sat across the month
       labels and read as broken). The strip FITS its column at every desktop
       width — the year is distributed across whatever room there is, and the
       separation a squeeze was trying to buy is bought by the stair instead.
       overflow-x:clip rather than auto: if anything ever does exceed the box it
       is trimmed silently instead of growing a scrollbar over the months. */
    .ar-ribbon{position:relative;margin-top:26px;padding-bottom:132px;overflow-x:clip;overflow-y:visible}
    .ar-ribbon-track{position:relative;height:96px;border-bottom:1px solid var(--sl-line,rgba(255,255,255,.14))}
    .ar-ribbon-month{position:absolute;bottom:-22px;left:0;padding-left:6px;border-left:1px solid var(--sl-line,rgba(255,255,255,.09));height:calc(100% + 14px);top:0}
    .ar-ribbon-month em{position:absolute;bottom:-20px;left:6px;font-style:normal;color:var(--sl-faint,#6b6f78);font:500 9.5px/1 var(--sl-mono);letter-spacing:.12em;text-transform:uppercase}
    /* A run of consecutive nights reads as one move down the road. */
    /* THE STAIR. Consecutive nights sit a day apart, which on a year-wide axis
       is under half a percent — three nights used to be three dots on top of one
       another with no way to aim at the middle one. Each night of a run now
       climbs one --step, so the run reads as a diagonal and every dot has its own
       air. Consecutive days are only ~3px apart on a year-wide axis, so the CLIMB
       is what makes each night its own target — 18px a step against a 26px-tall
       box. The box is narrow (16px, barely wider than the 12px dot) on purpose:
       a wide one would swallow the neighbours it is only 3px away from, and the
       dot is the thing being aimed at anyway.
       Later nights are later in the DOM and therefore paint on top, so the small
       remaining overlap resolves to the night you can actually see. The stair restarts
       every four (set in ribbonMarkup) so a long run cannot climb off the track.
       Horizontal position is still the real date — only the height is invented,
       and it only ever means "the next night of this run". */
    .ar-node{position:absolute;bottom:calc(8px + var(--step,0) * 18px);width:16px;height:26px;margin-left:-8px;padding:0;border:0;background:none;display:grid;place-items:center;cursor:pointer;z-index:2}
    .ar-node-dot{width:12px;height:12px;border-radius:50%;background:rgba(255,255,255,.55);box-shadow:0 0 0 3px rgba(255,255,255,.07);transition:transform .18s cubic-bezier(.2,.7,.3,1),background .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),box-shadow .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-node:hover .ar-node-dot,.ar-node:focus-visible .ar-node-dot{background:var(--ar-tie);background-size:200% 200%;transform:scale(1.5);box-shadow:0 0 0 4px rgba(255,255,255,.1)}
    .ar-node:focus-visible{outline:none}
    .ar-node:focus-visible .ar-node-dot{box-shadow:0 0 0 3px rgba(255,255,255,.85)}
    /* The connector climbs WITH the nights instead of lying flat beneath them, so
       a run looks like one thing going up rather than a bar with dots near it. */
    /* The connectors are stroked with the tie-dye ramp itself (the gradient def
       lives once in the ribbon markup), so a run climbing the year is drawn in
       the same colour the setlist uses for a song that is playing. */
    .ar-runline{position:absolute;bottom:13px;height:72px;overflow:visible;opacity:.62;pointer-events:none;z-index:1}
    .ar-tie-defs{position:absolute;width:0;height:0;pointer-events:none}
    /* THE LAMP. A wide, soft tie-dye light parked at the cursor's position along
       the year (--ar-cursor, written once a frame by wireRibbon). screen blending
       means it BRIGHTENS what is under it — the dots and the connectors near the
       pointer light up and fall away with distance — rather than washing a panel
       over the top of them. Deliberately weak: the hovered node's own scale and
       its preview card have to stay obviously stronger, because those are the
       affordance and this is only the room's light. Off until the pointer is
       actually over the strip (.is-lit), so it never sits there glowing at
       nobody, and absent entirely on touch and under reduced motion. */
    .ar-lamp{position:absolute;top:-24px;bottom:-24px;left:var(--ar-cursor,50%);width:min(420px,46%);margin-left:calc(min(420px,46%) / -2);border-radius:50%;opacity:0;pointer-events:none;z-index:0;mix-blend-mode:screen;transition:opacity .35s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));
      background:radial-gradient(closest-side,rgba(224,87,79,.30),rgba(96,165,210,.16) 46%,transparent 78%)}
    .ar-ribbon.is-lit .ar-lamp{opacity:1}
    @media(prefers-reduced-motion:reduce){.ar-lamp{display:none}}
    @media(hover:none),(pointer:coarse){.ar-lamp{display:none}}
    /* ARRIVAL, ONCE. The connectors draw themselves and the nights settle up in
       date order with a small stagger — the year assembling rather than appearing
       fully formed. Roughly 500ms end to end, transform/opacity only, and nothing
       loops: it happens on the render of a year view and then the strip is still.
       --i is the show's index in the year, set in ribbonMarkup, so the stagger
       follows the calendar rather than the DOM by accident. */
    @keyframes ar-node-settle{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}
    @keyframes ar-line-draw{from{stroke-dashoffset:var(--len,240)}to{stroke-dashoffset:0}}
    .ar-ribbon .ar-node{opacity:0;animation:ar-node-settle .34s cubic-bezier(.2,.7,.3,1) forwards;animation-delay:calc(var(--i,0) * 3.4ms)}
    .ar-ribbon .ar-runline polyline{stroke-dasharray:var(--len,240);animation:ar-line-draw .5s cubic-bezier(.2,.7,.3,1) forwards;animation-delay:calc(var(--i,0) * 3.4ms)}
    @media(prefers-reduced-motion:reduce){
      .ar-ribbon .ar-node{opacity:1;animation:none}
      .ar-ribbon .ar-runline polyline{stroke-dasharray:none;animation:none}
    }
    /* Below the track, not above it — above collides with the year heading. */
    /* Below the track AND below the month labels. The strip grew taller when the
       stair went in, and a preview measured from the top of it landed across the
       months. Anchored to the bottom of the ribbon's own reserved space instead,
       so it cannot collide with them whatever height the stair reaches. */
    .ar-preview{position:absolute;z-index:6;top:auto;bottom:0;left:var(--x,50%);transform:translateX(-50%);display:flex;gap:12px;width:min(320px,86vw);padding:11px;border:1px solid var(--sl-line-strong);border-radius:13px;background:rgba(13,14,19,.95);box-shadow:0 26px 60px -26px rgba(0,0,0,.9);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);pointer-events:none}
    /* THE MARKUP ALREADY SAID hidden. The rule above says display:flex, and a
       class selector beats the browser's own [hidden] rule, so the element was
       told to hide and never did — an empty bordered box with a backdrop blur
       parked under the month scale on every year page, which reads as a
       collapsed bento nobody asked for (Alex, 08-02: "this card being collapsed
       as a bento when you are not hovering on the date list... is a bug too").
       It is the preview that appears when you hover a night; with nothing
       hovered it has no content, and something with no content should occupy
       no space. Any rule that sets display on an element that is sometimes
       [hidden] has to say what hidden means, or hidden means nothing. */
    .ar-preview[hidden]{display:none}
    .ar-preview-media{flex:none;width:74px;height:56px;border-radius:8px;background:radial-gradient(120% 130% at 26% 12%,hsl(var(--tone) 50% 30%/.9),transparent 62%),linear-gradient(160deg,#161a23,#0b0c10);background-size:cover;background-position:center 30%}
    .ar-preview-media.has-art{background-blend-mode:normal}
    .ar-preview-body{min-width:0;display:grid;gap:2px}
    .ar-preview-body time{color:var(--sl-muted);font:600 10px/1.2 var(--sl-mono);letter-spacing:.08em;text-transform:uppercase}
    .ar-preview-body strong{font:650 15px/1.2 var(--sl-display);letter-spacing:-.015em}
    .ar-preview-body span{color:var(--sl-muted);font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ar-preview-body em{margin-top:3px;font-style:normal;color:var(--sl-faint,#6b6f78);font:500 9.5px/1 var(--sl-mono);letter-spacing:.1em;text-transform:uppercase}

    /* ---- archive-sourced notes + performance units ---- */
    .ar-notelist.is-archive li::before{opacity:.4}
    .ar-notes-prov{margin:8px 0 0;color:var(--sl-faint,#6b6f78);font:500 9.5px/1.4 var(--sl-mono);letter-spacing:.1em;text-transform:uppercase}
    .ar-units{display:flex;flex-wrap:wrap;gap:7px;margin-top:14px}
    .ar-unit{display:inline-flex;align-items:baseline;gap:7px;padding:6px 11px;border:1px solid var(--sl-line);border-radius:999px;font-size:12.5px;color:var(--sl-ink)}
    .ar-unit em{font-style:normal;color:var(--sl-faint,#6b6f78);font:600 9px/1 var(--sl-mono);letter-spacing:.14em;text-transform:uppercase}
    .ar-tapenotes{margin:12px 0 0;padding:0;list-style:none;display:grid;gap:5px}
    .ar-tapenotes li{color:var(--sl-faint,#6b6f78);font-size:12px;line-height:1.5}

    /* ---- the front door's lead show ---- */
    /* THE FRONT DOOR'S LEAD IS THE SHOW HERO, SCALED. It used to be its own
       two-column composition — a 16:10 thumbnail beside a text column — so the
       same night looked like two different objects depending which page you were
       on. It now renders the show page's own .ar-show-stage / .ar-stage /
       .ar-show-id markup, and this modifier is the entire difference: a shorter
       minimum height and a type scale that suits a card rather than a page. Same
       ratio, same crop, same scrim, same inset, same chip. */
    .ar-show-stage.is-lead{min-height:clamp(240px,26vw,340px)}
    .ar-show-stage.is-lead .ar-show-id h1{font-size:clamp(32px,4.6vw,58px)}
    .ar-show-stage.is-lead .ar-show-id>p{font-size:clamp(14px,1.4vw,17px)}
    .ar-show-stage.is-lead .ar-show-id h1 a:hover{color:#fff}
    /* The front door has only the index in memory, so the lead's photograph
       arrives through the same lazy [data-card-art] fill the night cards use —
       a background image rather than an <img>. Everything else about the stage,
       including its scrim (.ar-stage::after), is inherited untouched. */
    .ar-stage-art{background-size:cover;background-position:center 30%;background-image:linear-gradient(150deg,hsl(var(--tone,210) 48% 26%),#0b0c10)}
    .ar-stage-art.has-art{background-image:var(--art)}
    .ar-lead-open{color:rgba(255,255,255,.8);font-size:13.5px}
    .ar-lead-open:hover{color:#fff}

    /* ---- taper track list as provenance under Recordings ---- */
    .ar-tapelist{margin-top:16px}
    .ar-tapelist summary{cursor:pointer;color:var(--sl-muted);font:600 10.5px/1.4 var(--sl-mono);letter-spacing:.14em;text-transform:uppercase}
    .ar-tapelist summary:hover{color:var(--sl-ink)}
    .ar-tapelist[open] summary{margin-bottom:8px}

    /* ---- related nights: the site's own show-card language ----
       Built from .sc-date / .sc-city / .sc-venue and the framed .sc-photo
       treatment used by the homepage setlist cards, so these belong to the same
       family instead of being a second card system. No gray slab on hover: the
       image responds, the type sharpens. */
    .ar-onward{display:grid;gap:clamp(38px,5vw,64px);margin-top:clamp(52px,7vw,96px);padding-top:clamp(30px,4vw,48px);border-top:1px solid var(--sl-line,rgba(255,255,255,.1))}
    .ar-onward .ar-head h2{font-size:clamp(17px,1.7vw,21px)}
    .ar-nights{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(210px,100%),1fr));gap:clamp(14px,1.6vw,22px)}
    /* Latest Shows holds exactly six cards (Alex's 08-10 spec: bigger cards,
       no third row). The shared auto-fill grid reaches four columns from
       ~882px up, which strands two empty cells in the second row — his
       "6 with two empty slots". Three columns is the balanced shape for six.
       Scoped to this one grid; On This Day and the year pages keep auto-fill. */
    @media (min-width:882px){.ar-nights-latest{grid-template-columns:repeat(3,1fr)}}
    /* TEXT OVER THE IMAGE (Alex, 7/29: the homepage's elegant cards, 'darkened
       fade', not text under a thumbnail). Same markup — the media block becomes
       the card's whole surface and the type sits on a bottom scrim whose stops
       are lifted from the homepage sc-bg treatment, flipped to fade upward. */
    .ar-night{position:relative;display:block;color:inherit;overflow:hidden;aspect-ratio:4/3;border-radius:var(--sl-r-md,12px);border:1px solid var(--sl-line,rgba(255,255,255,.12));box-shadow:0 30px 60px -24px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.1)}
    /* THE ART LIVES ON ::before AS var(--art) — that is the contract the app
       paints to (archive-app.mjs sets --art and .has-art on [data-card-art] as
       each card scrolls into view). Rewriting this block for the overlay look
       dropped that layer and every card went blank. The scrim is ::after so it
       sits ABOVE the art and below the type. */
    .ar-night-media{display:block;position:absolute;inset:0;overflow:hidden;border-radius:0;border:0;box-shadow:none}
    .ar-night-media::before{content:"";position:absolute;inset:0;background-image:var(--art);background-size:cover;background-position:center 30%;transform:scale(1);transition:transform .5s cubic-bezier(.2,.7,.3,1);opacity:0}
    .ar-night-media.has-art::before{opacity:1}
    .ar-night:hover .ar-night-media.has-art::before{transform:scale(1.04)}
    /* THE WASH NOW REACHES THE TEXT (Alex, 8/10 — reverses the plan to move
       text off the photo: "I like that more, maybe just darken the bottom a
       little bit where those three lines are going to go"). Measured, not
       guessed: on the real Latest Shows cards the date sat as high as 48% up
       from the card's bottom edge on desktop and 60% on mobile — past where
       the old stops (52%→30%, 74%→0) had already faded to nothing, which is
       the exact bug the old off-photo spec was written to route around. Same
       three-line stack, same photograph; the gradient's reach is the fix. */
    .ar-night-media::after{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(0deg, rgba(9,9,11,0.96) 0%, rgba(9,9,11,0.84) 28%, rgba(9,9,11,0.54) 56%, rgba(9,9,11,0.16) 80%, rgba(9,9,11,0) 100%)}
    .ar-night-media.has-art .ar-night-fallback{display:none}
    .ar-night-fallback{position:absolute;inset:0;display:grid;place-items:center;font:700 clamp(38px,5vw,58px)/1 var(--sl-display);letter-spacing:-.04em;color:rgba(255,255,255,.09)}
    .ar-night-date{position:relative;z-index:2;display:block;margin:0;padding:0 16px;font-family:var(--sl-mono);font-size:11px;letter-spacing:.08em;color:rgba(255,255,255,.78);font-variant-numeric:tabular-nums}
    .ar-night-city{position:relative;z-index:2;display:block;margin-top:5px;padding:0 16px;font-family:var(--sl-display);font-weight:720;font-size:clamp(18px,1.9vw,22px);letter-spacing:-.015em;line-height:1.1;color:#fff;transition:color .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-night-venue{position:relative;z-index:2;display:block;margin-top:4px;padding:0 16px 16px;font-size:13px;font-weight:440;color:rgba(255,255,255,.66);line-height:1.35}
    /* The type stack rides the card's bottom, over the scrim. */
    .ar-night{display:flex;flex-direction:column;justify-content:flex-end}
    .ar-night-meta{display:block;margin-top:8px;font:500 10px/1 var(--sl-mono);letter-spacing:.1em;text-transform:uppercase;color:var(--sl-faint,#6b6f78)}
    /* ---- THE YEAR STRIP (Alex, 2026-07-28) ----
       The Codex Atlanticus gesture wearing Burnthday's clothes: a count and a
       long arrow on the left, a free-scrolling filmstrip of the whole catalog
       on the right. One photograph per year; the frames AGE with the band —
       Polaroid white borders and warm fade in the club years, dark Super-8
       card through the 90s, thin print border in the 2000s, clean digital
       after. The year numerals are the hand (Panic-Hand), because the years
       belong to Garrie's world, not the UI's. */
    .ar-yearstrip{display:grid;grid-template-columns:132px minmax(0,1fr);gap:26px;align-items:center;padding:0 0 clamp(4px,.6vw,8px)}
    /* THE SPACING NEEDS THE SPECIFICITY (the hazard docs/HANDOFF names): the
       sitewide rhythm rule is body.stagelight main > section at margin-top 96px,
       at 0,1,3 — a bare .ar-yearstrip at 0,1,0 loses silently, which is why the
       strip still sat 96px below the featured image and threw 84px at Latest
       Shows after the margin was "reduced". Alex, 7/29: "why is there so much
       white space?" — 180px of air around a 188px strip. */
    body.stagelight .ar-yearstrip{margin-top:clamp(16px,2vw,28px)}
    body.stagelight .ar-yearstrip + section{margin-top:clamp(24px,3vw,44px)}
    .ar-ys-side{display:block}
    .ar-ys-head{margin:0;font:700 clamp(19px,1.8vw,24px)/1.2 var(--sl-display);letter-spacing:-.02em;color:#fff}
    .ar-ys-viewport{overflow:hidden;cursor:grab;touch-action:pan-y;outline:none;
      -webkit-mask-image:linear-gradient(90deg,transparent,#000 34px,#000 calc(100% - 34px),transparent);
      mask-image:linear-gradient(90deg,transparent,#000 34px,#000 calc(100% - 34px),transparent)}
    .ar-ys-viewport:active{cursor:grabbing}
    .ar-ys-viewport:focus-visible{outline:2px solid rgba(255,255,255,.7);outline-offset:4px}
    .ar-ys-track{display:flex;align-items:flex-end;gap:clamp(14px,1.6vw,22px);padding:34px 34px 6px;width:max-content;will-change:transform}
    .ar-ys-item{display:flex;flex-direction:column;align-items:center;gap:9px;color:inherit;text-decoration:none;transform:rotate(var(--tilt,0deg));transition:transform .28s cubic-bezier(.22,1,.36,1)}
    .ar-ys-item:hover,.ar-ys-item:focus-visible{transform:rotate(0deg) translateY(-6px) scale(1.32);z-index:2}
    .ar-ys-frame{position:relative;display:block;width:56px;height:74px;overflow:hidden;background:#14161c}
    .ar-ys-frame img{width:100%;height:100%;object-fit:cover;object-position:center 30%;display:block}
    .ar-ys-blank{display:grid;place-items:center;width:100%;height:100%;color:rgba(255,255,255,.22);font:600 11px/1 var(--sl-mono)}
    .ar-ys-credit{position:absolute;left:0;right:0;top:0;z-index:3;padding:3px 4px;
      font:600 7px/1.25 var(--sl-mono);letter-spacing:.04em;text-align:center;color:rgba(255,255,255,.94);
      background:linear-gradient(180deg,rgba(0,0,0,.82),rgba(0,0,0,0));
      opacity:0;transition:opacity .18s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));pointer-events:none;
      overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ar-ys-item:hover .ar-ys-credit,.ar-ys-item:focus-visible .ar-ys-credit{opacity:1}
    @media(prefers-reduced-motion:reduce){.ar-ys-credit{transition:none}}
    .ar-ys-year{font-family:"PanicHand",sans-serif;font-size:19px;line-height:1;color:var(--sl-muted);transition:color .2s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-ys-item:hover .ar-ys-year,.ar-ys-item:focus-visible .ar-ys-year{color:#fff}
    /* era treatments — the photographs age with the band */
    /* THE DRUGSTORE PRINT, not a Polaroid (Alex asked whether Polaroids were a
       thing in '85 — they were, but HIS photographs are 35mm 4x6 prints, and
       a Polaroid frame is a costume that doesn't match the garment). Thin
       even white border, glossy, warm and slightly faded, with the orange
       film date-stamp those cameras burned into the corner. */
    .ar-ys-item.is-polaroid .ar-ys-frame{border:5px solid #f2ede1;border-bottom-width:15px;box-shadow:0 9px 20px rgba(0,0,0,.52)}
    .ar-ys-item.is-polaroid img{filter:sepia(.3) contrast(1.02) saturate(.76) brightness(1.05)}
    .ar-ys-item.is-print35 .ar-ys-frame{border:3px solid #f4f1e8;box-shadow:0 8px 18px rgba(0,0,0,.5)}
    .ar-ys-item.is-print35 img{filter:sepia(.24) contrast(1.03) saturate(.82) brightness(1.03)}
    .ar-ys-item.is-print35 .ar-ys-frame::after{content:attr(data-stamp);position:absolute;right:4px;bottom:3px;z-index:2;
      font:600 6.5px/1 var(--sl-mono);letter-spacing:.02em;color:#ff8a3d;text-shadow:0 0 3px rgba(255,120,40,.55);opacity:.92}
    .ar-ys-item.is-film .ar-ys-frame{border:3px solid #1c1a17;box-shadow:0 6px 16px rgba(0,0,0,.55),inset 0 0 0 1px rgba(255,244,214,.14)}
    .ar-ys-item.is-film img{filter:saturate(.62) contrast(1.12) brightness(.96) sepia(.12)}
    .ar-ys-item.is-print .ar-ys-frame{border:1px solid rgba(255,255,255,.24);box-shadow:0 6px 16px rgba(0,0,0,.45)}
    .ar-ys-item.is-print img{filter:saturate(.9)}
    .ar-ys-item.is-digital .ar-ys-frame{box-shadow:0 8px 20px rgba(0,0,0,.5);border:1px solid rgba(255,255,255,.12)}
    @media(max-width:820px){
      .ar-yearstrip{grid-template-columns:1fr;gap:2px;margin-top:8px}
      .ar-ys-item:hover,.ar-ys-item:focus-visible{transform:rotate(0deg) translateY(-3px) scale(1.14)}
    }
    @media(prefers-reduced-motion:reduce){.ar-ys-item{transition:none}}

    /* ---- THE LIFELINE RIBBON (song pages) ---- */
    /* The dye caps: the song title's first letters take the same ramp as the
       silk below — clipped to the glyphs, lightened well past half so it
       reads as a wash, drifting on the same slow clock as a playing song. */
    .ar-dye-cap{color:transparent;background-image:linear-gradient(rgba(242,242,240,.52),rgba(242,242,240,.52)),var(--ar-tie);background-size:100% 100%,240% 100%;-webkit-background-clip:text;background-clip:text;animation:ar-tiedye 12s ease-in-out infinite alternate}
    @media(prefers-reduced-motion:reduce){.ar-dye-cap{animation:none}}
    .ar-lifeline{margin-top:clamp(40px,5vw,64px)}
    .ar-ribbonwrap{position:relative;cursor:crosshair}
    .ar-ribbon-canvas{display:block;width:100%}
    .ar-ribbon-tip{position:absolute;top:6px;padding:5px 10px;border:1px solid var(--sl-line-strong);border-radius:999px;background:rgba(10,10,13,.82);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);color:var(--sl-ink);font:600 11px/1 var(--sl-mono);letter-spacing:.06em;pointer-events:none;white-space:nowrap}

    /* ---- THE GARRIE SEPARATOR (parked 7/22, unparked 2026-07-28) ----
       Two lazy strokes meeting his hand-looped "o", heavier ink where the pen
       lands, drawn with real pen pacing when it scrolls into view. The strip
       above carries no hairlines — this is the punctuation. */
    .ar-garrie{display:block;width:min(560px,calc(100% - 12px));height:auto;margin:clamp(4px,.8vw,8px) auto -16px;overflow:visible}
    .ar-garrie path{fill:none;stroke:rgba(255,255,255,.6);stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
    .ar-garrie .ln{stroke-width:2.2}
    /* The press marks were sitting AT the stroke ends, doubling the ink and
       reading as hot white tips (Alex). They live mid-stroke now, and their
       weight came down a step. */
    .ar-garrie .press{stroke-width:2.35;stroke:rgba(255,255,255,.34)}
    .ar-garrie .oo{stroke-width:2}
    .ar-garrie.anim .ah1{stroke-dasharray:34;stroke-dashoffset:34;animation:garrie-d 170ms cubic-bezier(.5,.05,.35,1) forwards}
    .ar-garrie.anim .l1{stroke-dasharray:244;stroke-dashoffset:244;animation:garrie-d 520ms cubic-bezier(.45,.05,.4,1) 140ms forwards}
    .ar-garrie.anim .p1{opacity:0;animation:garrie-ink 240ms linear 260ms forwards}
    .ar-garrie.anim .oo{stroke-dasharray:64;stroke-dashoffset:64;animation:garrie-d 240ms cubic-bezier(.5,.05,.35,1.08) 620ms forwards}
    .ar-garrie.anim .l2{stroke-dasharray:244;stroke-dashoffset:244;animation:garrie-d 520ms cubic-bezier(.45,.05,.4,1) 840ms forwards}
    .ar-garrie.anim .p2{opacity:0;animation:garrie-ink 160ms ease-in 1150ms forwards}
    .ar-garrie.anim .ah2{stroke-dasharray:34;stroke-dashoffset:34;animation:garrie-d 180ms cubic-bezier(.5,.05,.35,1.06) 1370ms forwards}
    .ar-garrie:not(.anim) path{opacity:0}
    @keyframes garrie-d{to{stroke-dashoffset:0}}
    @keyframes garrie-ink{to{opacity:1}}
    @media(prefers-reduced-motion:reduce){.ar-garrie:not(.anim) path{opacity:1}.ar-garrie.anim path{animation:none;stroke-dashoffset:0;opacity:1}}

    /* ---- ONE CARD HOVER, SITE-WIDE ----
       Ported verbatim in feel from the homepage's cross-promo cards (.xp-card,
       in renderStagelightCss): a 4px lift, a deeper shadow, the hairline coming
       up to .24 white, and the artwork easing in ~5%. Nothing tints.
       WHAT THIS REPLACED, and why: hovering a night used to run the date through
       --ar-tie, whose first stop is #d4514f — a red wash across the one line on
       the card that has to stay legible, on a different treatment from every
       other card on the site. Tie-dye still means exactly one thing here, and it
       is the thing it means everywhere else: this song is playing. It is not a
       hover colour. Every card below (.ar-night, .ar-songcard, .ar-placecard,
       .ar-eracard, the front-door lead) now shares these numbers. */
    /* THE TYPE DOES NOT MOVE (Alex, 7/28: translating the whole card "looks
       cheap"). Lifting the entire card drags the date, the city and the venue
       with it, which reads as a widget animating rather than a photograph
       responding. The frame stays exactly where it is and the IMAGE answers
       inside it — the same thing the homepage's own cards do. */
    .ar-night-media{transition:border-color .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),box-shadow .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-night:hover .ar-night-media.has-art::before,.ar-night:focus-visible .ar-night-media.has-art::before{transform:scale(1.05)}
    .ar-night:hover .ar-night-media,.ar-night:focus-visible .ar-night-media{border-color:rgba(255,255,255,.24);box-shadow:0 26px 60px -22px rgba(0,0,0,.85)}
    .ar-night:hover .ar-night-city,.ar-night:focus-visible .ar-night-city{color:#fff}
    .ar-night:focus-visible{outline:none}
    .ar-night:focus-visible .ar-night-media{outline:2px solid var(--sl-ink,#f2f2f0);outline-offset:3px}
    .ar-night-fallback{z-index:1}
    /* HOVER-PLAY, NUGS-STYLE (Alex's punch list #7): a play button surfaces at
       the lower-left of the card art on hover and starts that night without
       leaving the page — the card itself still navigates. Hidden until hover
       (and keyboard focus) so the grid stays a wall of photographs; the HERO
       keeps its embedded player and never hides controls behind a hover. */
    /* Top-right of the card (Alex, 7/29) — bottom-left sat under the card's
       own text and read as part of it; the corner keeps it clearly a control
       on the photograph. */
    .ar-card-play{position:absolute;z-index:2;right:12px;top:12px;display:grid;place-items:center;width:42px;height:42px;padding:0;border:1px solid rgba(255,255,255,.35);border-radius:50%;background:rgba(10,10,12,.62);color:#fff;cursor:pointer;opacity:0;transform:translateY(6px);transition:opacity .22s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),transform .22s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
    .ar-card-play svg{width:13px;height:14px;margin-left:2px}
    .ar-night:hover .ar-card-play,.ar-card-play:focus-visible{opacity:1;transform:none}
    .ar-card-play:hover{background:#fff;color:#111;border-color:#fff}
    @media(hover:none){.ar-card-play{opacity:1;transform:none;width:38px;height:38px}}
    @media(prefers-reduced-motion:reduce){.ar-card-play{transition:none}}
    @media(prefers-reduced-motion:reduce){
      .ar-night-media,.ar-night-media::before{transition:none}
      .ar-night:hover .ar-night-media.has-art::before{transform:none}
    }
    /* The lead is the show hero now, and a page hero does not lift under the
       pointer — the grid cards below it carry the hover language. Its own links
       and chips still answer the pointer through their own rules. */

    /* ---- show notes: history, kept apart from tape metadata ---- */
    .ar-notes{margin-top:clamp(34px,4vw,52px);padding-top:22px;border-top:1px solid var(--sl-line,rgba(255,255,255,.1))}
    .ar-guests{margin:0;padding:0;list-style:none;display:grid;gap:7px}
    .ar-guests li{font-size:15.5px;line-height:1.5;color:var(--sl-muted)}
    .ar-guests strong{color:var(--sl-ink);font-weight:640}
    .ar-notelist{margin:14px 0 0;padding:0;list-style:none;display:grid;gap:6px}
    
    /* A LINE WITH A WAY IN. The control is the first thing on the line and the
       text hangs beside it, so a note that wraps still reads as one item — the
       second line indents under the first word, never under the circle. */
    .sn-list li.has-moment{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:baseline;column-gap:9px}
    .ar-notelist li.has-moment .ar-moment{position:absolute;left:0;top:.28em}
    /* The whole row is the target: the circle is small, and nobody should have
       to hit a 14px ring to hear a rap. */
    .ar-notelist li.has-moment,.sn-list li.has-moment{cursor:pointer}
    /* Sized up from 0.9em: at the smaller size the ring and the triangle inside
       it merged into one shape at a glance (Alex: "not legible"). */
    .ar-moment{display:inline-flex;align-items:center;justify-content:center;width:19px;height:19px;padding:0;border:0;background:none;color:inherit;cursor:pointer;
      /* Optical, not geometric: the circle's mass reads high against lowercase
         text, and the triangle reads left of centre inside its own ring. */
      transform:translateY(.09em)}
    .ar-moment-glyph{width:100%;height:100%;overflow:visible;transform:translateX(.4px)}
    /* Circle and words brighten together — they are one control, so they must
       never light up independently of each other. */
    .ar-notelist li.has-moment,.sn-list li.has-moment{color:var(--sl-muted,rgba(255,255,255,.66));transition:color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-notelist li.has-moment:hover,.ar-notelist li.has-moment:focus-within,.sn-list li.has-moment:hover,.sn-list li.has-moment:focus-within{color:rgba(255,255,255,.95)}
    /* Playing: the ring fills and the glyph becomes a pause. */
    /* LOADING WEARS THE TIE-DYE (Alex, 7/31): a cold tape buffers off
       archive.org for a few seconds, and a dead-looking ring reads as a broken
       button. While the moment loads, the ring spins a dash through the site's
       four tie-dye colours; when audio actually rolls it settles into the
       playing state. */
    .ar-notelist li.is-moment-loading .ar-moment-glyph circle,.sn-list li.is-moment-loading .ar-moment-glyph circle{
      stroke-dasharray:40 23;animation:ar-moment-spin .9s linear infinite,ar-moment-tie 2.4s linear infinite}
    .ar-notelist li.is-moment-loading .ar-moment-glyph,.sn-list li.is-moment-loading .ar-moment-glyph{transform-origin:center}
    @keyframes ar-moment-spin{to{stroke-dashoffset:-63}}
    @keyframes ar-moment-tie{0%{stroke:#d4514f}30%{stroke:#e0574f}55%{stroke:#60a5d2}80%{stroke:#c9a35f}100%{stroke:#d4514f}}
    @media (prefers-reduced-motion:reduce){.ar-notelist li.is-moment-loading .ar-moment-glyph circle,.sn-list li.is-moment-loading .ar-moment-glyph circle{animation:ar-moment-tie 2.4s linear infinite}}
    .ar-notelist li.has-moment.is-playing,.sn-list li.has-moment.is-playing{color:#fff}
    /* THE RING NEVER FILLS (Alex, 7/30: "outline though no fill ... it's not
       legible as is"). A filled disc at this size is a blob — the shape that
       reads is an open circle with a mark inside it. Playing swaps the mark
       from a triangle to two bars; the ring itself is untouched, in every state. */
    .ar-moment-pause{display:none}
    .ar-notelist li.has-moment.is-playing .ar-moment-play,.sn-list li.has-moment.is-playing .ar-moment-play{display:none}
    .ar-notelist li.has-moment.is-playing .ar-moment-pause,.sn-list li.has-moment.is-playing .ar-moment-pause{display:block;fill:currentColor}
    @media (prefers-reduced-motion:reduce){.ar-notelist li.has-moment,.sn-list li.has-moment{transition:none}}

    .ar-guests+.ar-notelist{margin-top:16px;padding-top:14px;border-top:1px solid var(--sl-line,rgba(255,255,255,.07))}
    /* ONE MARKER COLUMN (Alex, 7/30: "the text should be aligned with the text
       above and below and the play button should be aligned where the dots
       are"). Every row — dotted or playable — hangs its text at the same left
       edge, and the dot and the ring share a centre inside the gutter. */
    .ar-notelist li{position:relative;padding-left:22px;font-size:14px;line-height:1.55;color:var(--sl-muted)}
    .ar-notelist li::before{content:"";position:absolute;left:5.5px;top:.62em;width:5px;height:5px;border-radius:50%;background:var(--ar-tie);background-size:300% 100%;opacity:.75}
    /* THE CHIP IS THE BULLET (Alex, 7/30). A line that offers a way in does not
       also carry a dot — one marker per line, and the play circle is it. */
    .ar-notelist li.has-moment::before{display:none}
    .ar-notes-source{display:inline-block;margin-top:15px;color:var(--sl-faint,#6b6f78);font:500 10.5px/1 var(--sl-mono);letter-spacing:.1em;text-transform:uppercase}
    .ar-notes-source:hover{color:var(--sl-ink)}
    .ar-mark{margin-left:2px;color:var(--sl-muted);font-size:.72em;vertical-align:super;text-decoration:none;cursor:help}
    /* line-height:0 + relative offset instead of vertical-align:super: a
       superscript raised by vertical-align grows the line box it sits in, so a
       single guest credit pushed one line of a 2.05-line-height setlist apart
       from its neighbours. This lifts the digit without it counting toward the
       line box at all, so a credited song sits on exactly the same baseline as
       an uncredited one. */
    .ar-guest-sup{margin-left:2px;color:var(--sl-muted);font:600 .62em/0 var(--sl-mono);position:relative;top:-.5em;letter-spacing:.05em}
    .ar-guests .ar-guest-sup{margin-left:0;margin-right:2px}
    .ar-unitlist{margin-top:12px}
    /* The lineup closes Show Notes. It describes the whole night rather than a
       moment in it, so it sits under everything and reads as the frame the rest
       happened inside — set off by a hairline rather than by another bullet. */
    .ar-lineup-note{margin-top:14px;padding-top:12px;border-top:1px solid var(--sl-line,rgba(255,255,255,.07))}
    .ar-lineup-note li::before{display:none}
    .ar-lineup-note li{padding-left:0;color:var(--sl-ink)}

    /* ---- songs / places / eras ---- */
    .ar-listhead h1{font-size:clamp(44px,7vw,96px)}
    .ar-filter{width:min(420px,100%);margin-top:22px;padding:14px 17px;border:1px solid var(--sl-line-strong);border-radius:11px;background:rgba(255,255,255,.04);color:var(--sl-ink);font:500 16px/1 var(--sl-display)}
    .ar-filter:focus{outline:2px solid rgba(255,255,255,.5);outline-offset:2px}
    .ar-songgrid,.ar-placegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(212px,100%),1fr));gap:8px;margin-top:clamp(30px,4vw,48px)}
    .ar-songcard,.ar-placecard{display:block;padding:16px 17px;border:1px solid var(--sl-line);border-radius:12px;color:inherit;transition:transform .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),box-shadow .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),border-color .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    /* Same hover as the night cards and the homepage's cross-promo pair. */
    /* Text-only cards have no image to respond, so they answer with surface and
       hairline alone — never by moving the words. */
    .ar-songcard:hover,.ar-placecard:hover,.ar-songcard:focus-visible,.ar-placecard:focus-visible{border-color:rgba(255,255,255,.28);background:rgba(255,255,255,.06);box-shadow:0 20px 44px -24px rgba(0,0,0,.8)}
    @media(prefers-reduced-motion:reduce){.ar-songcard,.ar-placecard{transition:none}}
    .ar-songcard strong,.ar-placecard strong{display:block;font:620 16px/1.25 var(--sl-display);letter-spacing:-.015em}
    .ar-songcard span,.ar-placecard span{display:block;margin-top:6px;color:var(--sl-muted);font-size:12.5px}
    .ar-placecard-n{font-family:var(--sl-mono);font-size:10.5px !important;letter-spacing:.06em;color:var(--sl-faint,#6b6f78) !important}
    .ar-lifeline-bars{display:flex;align-items:flex-end;gap:2px;height:150px}
    .ar-lifeline-bar{position:relative;flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;height:100%;color:inherit}
    .ar-lifeline-bar::before{content:"";width:100%;height:var(--h);border-radius:2px 2px 0 0;background:var(--ar-tie);background-size:180% 100%;opacity:.75;transition:opacity .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-lifeline-bar.is-quiet::before{background:rgba(255,255,255,.13)}
    .ar-lifeline-bar:hover::before{opacity:1}
    .ar-lifeline-bar span{margin-top:7px;color:var(--sl-faint,#6b6f78);font:500 9px/1 var(--sl-mono)}
    /* Search results open on the count, not on a restatement of the query. The
       kicker carries the count at kicker weight, and the term follows quietly. */
    .ar-results-for{margin-top:6px;color:var(--sl-muted);font-size:14.5px}
    .ar-results-for strong{color:var(--sl-ink);font-weight:600}
    .ar-eragrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(258px,100%),1fr));gap:12px;margin-top:clamp(30px,4vw,48px)}
    .ar-eracard{display:block;padding:26px 24px;border:1px solid var(--sl-line-strong);border-radius:16px;color:inherit;transition:transform .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),box-shadow .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),border-color .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .28s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-eracard:hover,.ar-eracard:focus-visible{border-color:rgba(255,255,255,.28);background:rgba(255,255,255,.06);box-shadow:0 20px 44px -24px rgba(0,0,0,.8)}
    @media(prefers-reduced-motion:reduce){.ar-eracard{transition:none}}
    .ar-eracard strong{display:block;margin:12px 0 9px;font:650 24px/1.1 var(--sl-display);letter-spacing:-.025em}
    .ar-eracard-n{display:block;margin-top:16px;padding-top:13px;border-top:1px solid var(--sl-line);color:var(--sl-faint,#6b6f78);font:500 10.5px/1 var(--sl-mono);letter-spacing:.07em;text-transform:uppercase}

    
    /* ---- the dock: the player follows you through the archive ---- */
    /* D1 (Alex): the page reserved a flat 96px for a dock that measures ~71px,
       leaving a dead strip between the footer and the player. --player-h is
       published by the dock itself (ResizeObserver), so the reservation now
       matches the bar exactly and the footer ends flush against it. The 96px
       fallback only applies for the frame before the first measurement. */
    body.has-dock{padding-bottom:var(--player-h,96px)}
    /* body.stagelight > * { position: relative } in the site stylesheet
       out-specifies a bare .ar-dock and silently un-fixes the player. Matching
       its specificity is what keeps the dock pinned to the viewport. */
    body.stagelight > .ar-dock{position:fixed;z-index:60;left:0;right:0;bottom:0;border-top:1px solid rgba(255,255,255,.16);background:rgba(12,13,17,.82);-webkit-backdrop-filter:blur(26px) saturate(1.5);backdrop-filter:blur(26px) saturate(1.5)}
    /* THREE ZONES: what's playing | the transport, centred | volume.
       The homepage dock (.hero-player.is-docked, in the stagelight sheet) is
       built to these same numbers on purpose — 68px tall, 44px art at radius 8,
       a 14px/620 title over a 10.5px mono context line, the transport column
       capped at 560px, a 90px volume slider hard right. One player, two
       surfaces; if a number moves here it moves there. */
    /* One gutter for the whole app (Alex): the header's content runs
       max(28px, (100% - 1400px)/2) from each edge — the shell, the lead
       image and the dock's contents all line up to the same rails now. */
    /* THE CENTRE CLUSTER IS CAPPED SO THE NIGHT KEEPS ITS ROOM (Alex, 2026-08-11,
       with a Spotify screenshot: "your player button at the bottom is too wide
       right? ... yours is pushing way into the album info on the left"). A flat
       560px centre is 56% of a 1000px window, which left the now-playing block
       172px and the song title 116px of it — measured, and it truncated. Spotify
       caps its centre and lets the sides keep what they need. clamp does that:
       42% of the window, never below 320px so the transport and timeline still
       fit, never above the 560px this was designed at. At 1000px the centre is
       420px and each side gains 70px. THE HOMEPAGE DOCK USES THESE COLUMNS
       VERBATIM and its own comment says if one moves, move both — so both moved. */
      .ar-dock-inner{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,clamp(320px,42vw,560px)) minmax(0,1fr);column-gap:20px;align-items:center;width:100%;min-height:68px;margin:0 auto;padding:5px max(28px,calc(50% - 700px))}
    /* TWO DECORATIONS WERE STANDING IN THE GRID. Alex, 2026-08-11, after sending
       the same screenshot three times: "the bar is way out of alignment the
       player buttons isn't in the center the volume is below the left side it
       should be on the right."
       .ar-dock-inner is three columns — now | transport | volume — but FIVE
       children were in flow: the atmosphere wash and the share toast are
       ordinary static divs, and auto-placement gave each one a cell. Everything
       real shifted one slot right (now-playing into the middle, transport into
       the volume's column) and the volume wrapped to a second row at the far
       left, which is exactly the screenshot. Measured live at 1000px: columns
       172/560/172, .ar-dock-now at x=220, .ar-dock-side at row 2.
       Neither element was ever meant to occupy space; both are overlays, and the
       SHEET has always said so — see the .is-sheet rules that give both
       position:absolute. They were simply never given it anywhere else, so the
       compact bar got the bare divs. Stating it in the base rule is what the
       sheet already does, which is why this cannot move the sheet: those rules
       are later and more specific and still own every other property.
       .ar-dock-inner deliberately stays position:static — .ar-dock is fixed and
       is therefore the containing block, and the sheet's own comment warns that
       these two keep exactly one containing block each. Making the inner
       relative would silently reparent them and break the sheet. */
    .ar-dock-atmosphere{position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden}
    .ar-dock-toast{position:absolute;left:50%;bottom:calc(100% + 12px);z-index:3;transform:translate(-50%,8px);
      padding:9px 16px;border-radius:999px;background:rgba(20,20,25,.92);color:#fff;
      font:600 12.5px/1 "Geist Variable","Geist",sans-serif;opacity:0;pointer-events:none;white-space:nowrap;
      transition:opacity .2s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),transform .2s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
    .ar-dock-toast.is-shown{opacity:1;transform:translate(-50%,0)}
    .ar-dock-now{display:flex;align-items:center;gap:12px;min-width:0}
    .ar-dock-art{flex:none;width:44px;height:44px;padding:0;border:1px solid rgba(255,255,255,.16);border-radius:8px;overflow:hidden;background:none;cursor:pointer}
    .ar-dock-art-fill{display:block;width:100%;height:100%;background-size:cover;background-position:center;background-image:linear-gradient(150deg,hsl(var(--tone,210) 48% 30%),#0c0d11)}
    .ar-dock-id{min-width:0;overflow:hidden}
    .ar-dock-id strong{display:block;font-size:14px;font-weight:620;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ar-dock-id span{display:block;margin-top:3px;color:var(--sl-muted);font-weight:500;font-size:10.5px;line-height:1.3;font-family:inherit;letter-spacing:.08em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    /* THE MIDDOT TRAVELS WITH THE TWO SPANS IT JOINS. Hiding the long venue/date
       line in the compact bar (the mini line says it in fewer words) left the
       static separator behind, and .ar-dock-id span above makes every span a
       block — so the lone "·" took a row of its own under the title. Alex,
       2026-08-11: "there is also a dot or something in the player bar". It is
       listed alongside its neighbours in both directions now, here and in the
       sheet rule that turns all three back on, so it can never again be shown
       without the thing it punctuates. */
    .ar-dock-id [data-dock-show],.ar-dock-id [data-dock-date],.ar-dock-id .ar-dock-sep{display:none}
    /* BUTTONS ON TOP, TIME UNDERNEATH (Alex, 2026-08-11: "before the play buttons
       were above the time bar btw"). DOM order puts the timeline first because the
       sheet wants it first; the bar wants the opposite, and order does that without
       moving a node. It also matches the homepage hero, which has always read
       transport-then-hairline — one instrument, one arrangement. */
    .ar-dock-center{display:grid;justify-items:center;row-gap:4px;width:100%}
    .ar-dock-center>.ar-dock-controls{order:1}
    .ar-dock-center>.ar-dock-timeline{order:2}
    .ar-dock-side{display:flex;align-items:center;justify-content:flex-end;gap:8px;color:rgba(255,255,255,.7)}
    .ar-vol-icon{display:block;width:16px;height:16px;flex:none}
    .ar-volume{--progress:100%;appearance:none;width:90px;height:16px;margin:0;background:transparent;cursor:pointer}
    .ar-volume::-webkit-slider-runnable-track{height:3px;border-radius:99px;background:linear-gradient(90deg,rgba(255,255,255,.85) var(--progress),rgba(255,255,255,.24) var(--progress))}
    .ar-volume::-webkit-slider-thumb{appearance:none;width:11px;height:11px;margin-top:-4px;border:0;border-radius:50%;background:#fff}
    .ar-volume::-moz-range-track{height:3px;border-radius:99px;background:rgba(255,255,255,.24)}
    .ar-volume::-moz-range-progress{height:3px;border-radius:99px;background:rgba(255,255,255,.85)}
    .ar-volume::-moz-range-thumb{width:11px;height:11px;border:0;border-radius:50%;background:#fff}
    .ar-volume:focus-visible{outline:2px solid #fff;outline-offset:3px}
    .ar-dock-controls{display:flex;align-items:center;gap:12px}
    /* THE SHARE AND TRACKLIST BUTTONS HAD NO RULE OUTSIDE THE SHEET, so in the
       compact bar they were bare <button> elements: 12px wide by 28px tall,
       squashed, off their neighbours' weight, and a target a third the size of
       the skip rings beside them. Found by sweeping every class that only ever
       appears in an .is-sheet selector — the fourth element today with styling
       written for the full-screen player and none for the bar (the taper
       eyebrow, the atmosphere wash and the share toast were the others). Sized
       to sit between the 30px skips and the 38px toggle, quiet by default so
       the transport still reads as one play button flanked by helpers.
       .ar-dock-clocks is the same story with a smaller cost: no base rule left
       the two times running together, so it gets its gap here. */
    /* NOT ON THE BAR. Alex, 2026-08-11: "the share button and the tracklist do not
       work on desktop. i never wanted those here. hide them." Both were authored
       for the full-screen sheet, where a tracklist and a share sheet are the whole
       point, and carried onto the docked bar because the markup is shared. On the
       bar the tracklist has nothing to open — .ar-dock [data-listen-tracklist] is
       display:none at every width — so the button was pointing at a hidden panel.
       Hidden here, still live in the sheet, where its rule below re-shows it. */
    .ar-dock:not(.is-sheet) .ar-dock-controls [data-share],
    .ar-dock:not(.is-sheet) .ar-dock-controls [data-open-peek]{display:none}
    .ar-ctl{display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;border-radius:50%;
      background:none;color:rgba(255,255,255,.62);cursor:pointer;transition:color .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94)),background .16s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94))}
    .ar-ctl:hover{color:#fff;background:rgba(255,255,255,.09)}
    .ar-ctl svg{width:16px;height:16px}
    .ar-dock-clocks{display:flex;align-items:center;gap:8px;white-space:nowrap}
    .ar-toggle,.ar-skip{display:grid;place-items:center;padding:0;border-radius:50%;cursor:pointer}
    /* THE GLUE HOLDS THE JOINT, NOT THE TITLE (Alex, 7/30: setlists bleeding off
       the right edge of a phone). .ar-nb exists so a comma can never start a
       line — but the song sits INSIDE it, so nowrap also made every title
       unbreakable. "I'm Just an Old Chunk of Coal (But I'm Gonna Be a Diamond
       Someday)" is wider than a phone, and one unbreakable title sets the
       paragraph's minimum width, which is why EVERY line was clipped and not
       just that one. The song is an inline-block, so it stays atomic against
       the comma whatever its own text does — it can wrap inside itself and the
       comma still cannot travel without it. */
    .ar-nb{white-space:nowrap}
    .ar-toggle{width:38px;height:38px;border:0;background:#f4f0e8;color:#111;position:relative}
    /* A thumb needs 44px+ (Apple HIG); the visual stays 38px, the tappable
       area grows underneath — part of the "pause sometimes doesn't respond"
       fix (Alex, 2026-07-28). */
    .ar-toggle::after{content:"";position:absolute;inset:-8px}
    /* THE DOCK SAYS IT IS WORKING. Alex, 8/7, on a jam over a slow tape: the
       bar sat there and he could not tell starting from stuck. The bar now
       shows 7:20 the instant you tap so it reads as PLACED, but between the tap
       and the sound there is still a window with no motion in it.
       SAME RING THE MOMENT BUTTONS ALREADY USE, deliberately: the site taught
       this gesture once on the Jessica tease — an open arc turning through the
       four house colours — and a second, different busy signal on the most
       looked-at control would be a second language. The palette is copied from
       ar-moment-tie, not invented.
       ::before, NOT ::after. The line directly above is this button's tap
       target — an invisible 8px expansion — and taking ::after for the ring
       would have silently shrunk the hit area on the most important control on
       the site. It is 38px; the finger needs the other 8.
       ONLY THE ARCHIVE DOCK, going in. .ar-toggle is the /listen/ player;
       .hero-player-toggle is the homepage and song pages; labs' .listen-toggle
       stays out because nobody reported it.
       WIDENED 2026-08-10 (Alex, walking the preview: "i haven't seen the
       spinning play bar either"). home-player.mjs was toggling is-loading on
       .hero-player-toggle the whole time — it was never in this rule, so the
       class landed on a button this selector had no opinion about. Rather than
       invent a second ring, the working one now also answers for the surface
       that already asks for it the same way.
       IT ONLY EXISTS WHILE LOADING — at rest the button is untouched, same
       cream disc, same triangle. The indicator arrives when there is something
       to say and then leaves.
       IT FADES OUT, NOT VANISHES (Alex, 2026-08-11: "would look more
       professional"). Removing .is-loading outright cut the ring mid-spin —
       the selector stopped matching and ::before disappeared on whichever
       frame the class left. .is-loading-out keeps ::before alive (still on
       .is-loading, so still spinning and cycling colour) and opacity-only
       tweens it to 0 over the same 200ms every fade on this site uses; the
       caller removes both classes once the transition has had time to run. */
    .ar-toggle.is-loading::before,.hero-player-toggle.is-loading::before{content:"";position:absolute;inset:-5px;border-radius:50%;
      border:2px solid transparent;
      animation:ar-toggle-spin .9s linear infinite,ar-toggle-tie 2.4s linear infinite;
      opacity:1;transition:opacity .2s var(--mo-out, cubic-bezier(0.25, 0.46, 0.45, 0.94));
      pointer-events:none}
    .ar-toggle.is-loading-out::before,.hero-player-toggle.is-loading-out::before{opacity:0}
    @keyframes ar-toggle-spin{to{transform:rotate(360deg)}}
    /* The same four colours as ar-moment-tie, on the same 2.4s, but driving
       BORDER rather than stroke — that keyframe is for an SVG circle and would
       have animated nothing here. Two borders, not one, so the arc is about
       two thirds of the circle and matches the moment ring's 40/23 dash: a
       full ring reads as a border, a short one as a chip of dirt. */
    @keyframes ar-toggle-tie{
      0%{border-top-color:#d4514f;border-right-color:#d4514f}
      30%{border-top-color:#e0574f;border-right-color:#e0574f}
      55%{border-top-color:#60a5d2;border-right-color:#60a5d2}
      80%{border-top-color:#c9a35f;border-right-color:#c9a35f}
      100%{border-top-color:#d4514f;border-right-color:#d4514f}}
    /* Reduced motion keeps the COLOUR and drops the rotation — the bargain the
       moment ring already strikes. A still ring that changes colour still
       answers "is this working". */
    @media (prefers-reduced-motion:reduce){
      .ar-toggle.is-loading::before,.hero-player-toggle.is-loading::before{animation:ar-toggle-tie 2.4s linear infinite}
    }
    .ar-toggle svg{width:15px;height:17px}
    .ar-skip{position:relative;width:30px;height:30px;border:1px solid rgba(255,255,255,.24);background:none;color:rgba(255,255,255,.86)}
    /* Visible ring 32px, thumb target 44px — the halo is invisible and costs no layout. */
    .ar-skip::after{content:"";position:absolute;inset:-6px;border-radius:50%}
    .ar-skip svg{width:14px;height:14px}
    .ar-skip:disabled{opacity:.3;cursor:default}
    .ar-toggle:focus-visible,.ar-skip:focus-visible,.ar-scrubber:focus-visible{outline:2px solid #fff;outline-offset:3px}
    /* TWO CHILDREN, TWO COLUMNS. This asked for three — elapsed | scrubber |
       total — from a day when the two clocks were separate cells. They are one
       .ar-dock-clocks wrapper now, so the grid had a spare track and everything
       landed one cell to the left: the scrubber was squeezed into the 129px
       "elapsed" column and the clock pair spread across the 409px column meant
       for the scrubber, with an empty 0px track on the end. Measured live at
       1000px. That is the short, left-shifted seek bar with the times floating
       beside it (Alex, 2026-08-11: "the position of the play buttons and player
       time bar is FUCKED"). The scrubber takes the room; the clocks sit after
       it at their own width. */
    /* ELAPSED LEFT, TOTAL RIGHT, BAR BETWEEN THEM (Alex: "both numbers are on the
       right side" and the bar is "far too large"). Both were one consequence: the
       two clocks live inside a single .ar-dock-clocks wrapper, so they could only
       ever sit together at one end, and the scrubber took every pixel that was
       left. display:contents dissolves the wrapper — the two clocks become items
       of this row in their own right — and order puts one on each side. The
       scrubber is flanked instead of unbounded, which is what stops it reading as
       a runway. The wrapper still exists for the sheet, which stacks them under
       the bar and needs them as a pair. */
    .ar-dock-timeline{display:flex;align-items:center;gap:11px;width:100%}
    .ar-dock-timeline>.ar-dock-clocks{display:contents}
    .ar-dock-timeline .ar-scrubber{order:2;flex:1 1 auto;min-width:0}
    .ar-dock-timeline [data-listen-elapsed]{order:1;flex:none}
    .ar-dock-timeline [data-listen-total]{order:3;flex:none}
    .ar-clock{color:rgba(255,255,255,.8);font-weight:600;font-size:11px;line-height:1;font-family:inherit;font-variant-numeric:tabular-nums}
    .ar-scrubber{--progress:0%;appearance:none;width:100%;height:18px;margin:0;background:transparent;cursor:pointer;touch-action:none}
    .ar-scrubber::-webkit-slider-runnable-track{height:4px;border-radius:99px;background:linear-gradient(90deg,#fff var(--progress),rgba(255,255,255,.24) var(--progress))}
    .ar-scrubber::-webkit-slider-thumb{appearance:none;width:13px;height:13px;margin-top:-4.5px;border:0;border-radius:50%;background:#fff}
    .ar-scrubber::-moz-range-track{height:4px;border-radius:99px;background:rgba(255,255,255,.24)}
    .ar-scrubber::-moz-range-progress{height:4px;border-radius:99px;background:#fff}
    .ar-scrubber::-moz-range-thumb{width:13px;height:13px;border:0;border-radius:50%;background:#fff}
    /* Only loading and failure ever reach this line — see muteIdleStatus in
       archive-app.mjs. Empty means gone, not an empty row. */
    .ar-dock-status{margin:0;color:rgba(255,255,255,.62);font-weight:500;font-size:10.5px;line-height:1.4;font-family:inherit;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
    .ar-dock-status:empty{display:none}

    /* The sheet's grab/dismiss chevron is a mobile-only control. Unsized, its
       SVG inflates to intrinsic size and swallows the desktop dock. */
    .dock-grab{display:none}
    .dock-grab svg{width:22px;height:22px}
    @media(max-width:820px){.ar-dock.is-sheet .dock-grab{display:grid;place-items:center}}
    @media(max-width:820px){
      body.has-dock .ar-shell{padding-bottom:104px}
      /* THE SHELL IS NOT THE BOTTOM OF THE PAGE. body.has-dock{padding-bottom:0}
         stood here, on the reasoning that .ar-shell reserves the room instead and
         reserving twice would leave a dead strip. But the footer is a SIBLING of
         .ar-shell, not a child, so the shell's padding never reached it: measured
         2026-08-20 at 390px, scrolled to the very bottom, the footer's last line
         sat 73.7px under the archive bar and 64.4px under a bar carried in from
         the homepage. Alex, 2026-08-20: "the player line is over the content."
         The body reservation is restored (the unscoped rule above pays out
         --player-h, which IS the measured bar, inset included); .ar-shell keeps
         its own 104px so the setlist still clears the bar without scrolling. */
      /* The bar's gutter is the PAGE's gutter (Alex, 2026-08-11: the show info "is
         also off to the right not on the same left line it needs to come left to
         align with the rest of the page"). The setlist and the heading above it
         sit at 16px; this bar sat at 14px, so the artwork and the song title
         started two pixels inside everything they were meant to line up with. */
      .ar-dock-inner{grid-template-columns:minmax(0,1fr) auto;column-gap:12px;width:100%;min-height:64px;padding:9px 16px calc(9px + env(safe-area-inset-bottom,0px))}
      .ar-dock-side{display:none}
      .ar-dock-center{width:auto;row-gap:0}
      .ar-dock-timeline{position:absolute;left:0;right:0;top:0;max-width:none;grid-template-columns:minmax(0,1fr);gap:0}
      .ar-dock-timeline .ar-clock{display:none}
      .ar-scrubber{height:14px;margin-top:-7px}
      .ar-dock-controls .ar-skip{display:none}
      .ar-dock-controls .ar-skip[data-listen-next]{display:grid}
      /* THE COMPACT BAR'S PLAY BUTTON (Alex: "not proportionate... larger,
         maybe a little off to the left. I would just use the one that's all
         over the site, but with the outline. roughly as tall as the other two
         icons.") Two changes, not a redesign: .ar-toggle's own base rule
         (above) is a SOLID white-filled circle — the one glyph on this bar
         that isn't drawn in the site's outline-button language .ar-skip
         already uses (1px border, no fill). Below 820px it now borrows that
         exact treatment instead of inventing a third style, sized up from
         38px so it reads as the primary control next to the outlined 30px
         skip button beside it, and nudged off the safe-area edge with the
         same margin every other edge control on this bar keeps. Nothing
         above 820px changes. */
      /* Mini-bar only: the 6px safe-area nudge belongs to the compact bar's
         edge, and it was leaking into the full-screen sheet — where the row is
         width:100% and space-between, so the margin shoved the whole row 6px
         left of the shared 42px gutter (the last of Alex's "3 different
         padding settings", 2026-08-11). */
      .ar-dock:not(.is-sheet) .ar-dock-controls{margin-right:6px}
      .ar-toggle{width:46px;height:46px;border:1px solid rgba(255,255,255,.32);background:rgba(9,9,11,.34);color:#fff;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
      .ar-toggle svg{width:18px;height:20px}
    }


    /* ---- tablet ---- */
    @media(max-width:1100px){
      .ar-shell{--rail-w:190px;--rail-full:190px;--rail-gap:30px}
      .ar-show-body{grid-template-columns:1fr;gap:38px}
      .ar-show.is-split{grid-template-columns:1fr;grid-template-areas:"head" "visual" "main" "side"}
      .ar-show.is-split .ar-show-visual{position:static}
      .ar-month{grid-template-columns:96px minmax(0,1fr);gap:18px}
    }

    /* ---- mobile ----
       DELETED (Alex, 7/28): the chip rails and the standalone search block that
       used to sit on top of every /listen/ view. "Your version with all the sort
       bars and search bars just doesn't make sense." The rail is now the drawer
       behind the head's hamburger — the SAME .ar-cabinet element, restyled as a
       panel in the shared mobile-chrome block — so nothing above the content
       competes with the show. What is left here is the canvas, which now starts
       at the top of the page because the navigation is no longer stacked on it. */
    @media(max-width:820px){
      .ar-shell{display:block;width:calc(100% - 32px);padding-top:0;padding-bottom:104px}
      /* Flush. The canvas's first child carried the page's leading, which put a
         hairline of background between the head and a full-bleed photograph. */
      .ar-canvas>section:first-child,body.listen-archive .ar-canvas>section:first-child,.ar-canvas>.ar-show{margin-top:0}
      .ar-main{padding-top:0}
      .ar-canvas h1{font-size:clamp(34px,9.5vw,50px)}
      .ar-yearhead h1,.ar-listhead h1{font-size:clamp(52px,17vw,84px)}
      /* The ribbon scrolls horizontally on mobile at ~2.2x width so nodes stay
         tappable instead of shrinking to dust. */
      /* THE RIBBON IS A DESKTOP INSTRUMENT (Alex, 7/28: "it really doesn't
         work" on a phone). It was made to scroll sideways at 220% width, which
         asked a thumb to aim at labelled blocks inside a horizontally scrolling
         strip inside a vertically scrolling page — three gestures competing over
         the same square inch. The month-grouped chronology directly below it
         already carries the whole year in a form a thumb can actually use, so
         the strip simply steps aside here rather than being made worse. The
         sideways-scroll treatment is deleted with it, not left as dead CSS. */
      .ar-ribbon{display:none}
      .ar-echo{height:340px}
      .ar-row{grid-template-columns:70px minmax(0,1fr) auto;gap:6px 12px;padding:12px 4px}
      .ar-row-city{grid-column:2;grid-row:1}
      .ar-row-venue{grid-column:2;grid-row:2;font-size:12.5px}
      .ar-row time{grid-row:1/3}
      .ar-row-count{grid-column:3;grid-row:1/3;align-self:center}
      .ar-month{display:block}
      .ar-month h3{position:static;margin-bottom:10px}
      .ar-block-night{grid-template-columns:30px minmax(0,1fr) auto;gap:4px 12px;padding:10px 6px}
      .ar-block-city{grid-column:2;grid-row:1}
      .ar-block-venue{grid-column:2;grid-row:2;white-space:normal}
      .ar-block-night time{grid-row:1/3}
      .ar-block-count{grid-column:3;grid-row:1/3;align-self:center}
      /* The stage goes edge to edge — it is the night, not a card. */
      /* THE NIGHT, FULL BLEED (Alex, 7/28). Both screen edges, and flush against
         the bottom of the head — the shell's top padding is gone and the stage
         pulls up over the canvas's own leading, so there is no strip of page
         between the bar and the photograph. Its natural 16:9, not the 4/5 crop:
         a portrait letterbox of a landscape photo was showing less of the show
         and taking more of the screen. */
      /* The stage stops being a frame with words inside it and becomes two
         things stacked: the photograph, then the night. At 16:9 there is no
         longer room to lay a headline, a venue and a Play button over a picture
         only ~220px tall — crushing them onto it is how the old 4/5 crop
         happened. So the image is the image, and the identity block drops into
         the flow underneath it where it can be read. */
      /* max-width:none is load-bearing. The base rule carries max-width:100% (it
         stops aspect-ratio + min-height resolving a width WIDER than the column
         on desktop), and here that clamped this full-bleed to the column's 338px
         while the negative margin still pulled it 26px left — so the photograph
         sat flush against the left edge and stopped 52px short of the right. That
         asymmetric gap is the "dead right-hand strip". */
      .ar-show-stage{position:relative;width:100vw;max-width:none;margin-left:calc((100% - 100vw)/2);margin-top:0;border-radius:0;border:0;aspect-ratio:auto;min-height:0;max-height:none;overflow:visible;isolation:auto}
      /* NATURAL RATIO (design system: setlist photography is never cropped). The
         16:9 box turned a 759x1024 portrait into a 1.78 letterbox — most of the
         frame thrown away. The image now sets its own height. */
      /* THE FIGURE CONSUMES THE RATIO IT CARRIES. --ar was set on this element and
       read only by .ar-show-stage and .ar-stage-art — neither of which is this
       one — so it did nothing at all and the photograph kept whatever shape the
       layout gave it. */
    /* min-height:340px, set for the image-less tie-dye stage, was still forcing
       this box taller than the photograph's own ratio — 382px wide should be
       254px tall at 3:2 and it was rendering 335. The ratio won and the floor
       overrode it, which is why three attempts at "natural size" all looked
       identical. A measured photograph needs no floor. */
    .ar-stage{position:relative;inset:auto;display:block;width:100%;aspect-ratio:var(--ar,auto)}
    /* Same colon, same reason as the desktop rule — this one predates it and
       carried the bug first, so the /listen/ hero was collapsing on phones
       before today; the desktop rule only made it visible on a laptop too. */
    .ar-stage[style*="--ar:"]{min-height:0;height:auto}
      .ar-stage-empty{position:relative;inset:auto;width:100%;aspect-ratio:16/9}
      /* The scrim exists to make white type legible on a photo. With the type
         off the photo it is just mud on the picture. */
      .ar-stage::after{background:linear-gradient(180deg,rgba(4,4,6,.28) 0%,rgba(4,4,6,0) 38%,rgba(4,4,6,.42) 100%)}
      .ar-show-id{position:static;padding:22px 16px 0}
      .ar-show.is-split{display:flex;flex-direction:column;align-items:stretch}
      .ar-show.is-split .ar-show-visual{order:-1}
      /* NO SIDE PADDING ON THE HEAD. It carried 16px that the setlist below it
         does not, so the date, city and venue started at 32px while Set 1
         started at 16px — Alex, 2026-08-12: "hero text sits right of the
         setlist." MEASURED before the change at 700px and 390px: head text 32,
         setlist 16. Desktop was already flush at 248/252 and is untouched; this
         rule only exists inside the mobile breakpoint. */
      .ar-show.is-split .ar-show-head{padding:22px 0 0}
      .ar-show.is-split .ar-show-head h1{margin:9px 0 4px;font-size:clamp(34px,9.5vw,52px)}
      /* THE LEAD CARD KEEPS ITS COMPOSITION AT EVERY SIZE (Alex, 2026-07-28:
         identity inside the card on the front door, below the photo on show
         pages, and the image size never changes on mobile). The static-id
         mobile pattern above is the SHOW PAGE's; the front door's lead pins
         the identity back into the photograph over a bottom-third scrim. */
      .ar-show-stage.is-lead{overflow:hidden;border-radius:0}
      .ar-show-stage.is-lead .ar-show-id{position:absolute;left:16px;right:16px;bottom:18px;padding:0;z-index:2}
      /* THE LEAD HERO'S OWN SCRIM, AND IT IS THE ONE THAT WINS. Alex, 2026-08-13:
         "the listen hero is not legible on mobile... can we just really darken
         the background". This was clear glass until 40% and only .55 at 72% -
         and the date, city and venue sit right through that band, so on a
         bright stage photograph they washed out. Measured live before touching
         it, because a deeper scrim written on .ar-stage::after alone changed
         nothing: this rule out-ranked it. Now near-black under the text and
         still clear across the top half, so the lights and the band keep their
         full strength. */
      .ar-show-stage.is-lead .ar-stage::after{background:linear-gradient(180deg,rgba(4,4,6,.12) 0%,rgba(4,4,6,.55) 26%,rgba(4,4,6,.86) 46%,rgba(4,4,6,.95) 66%,rgba(4,4,6,.97) 100%)}
      /* The song count and the lineup stop sharing a line here, so the dot that
         joined them is left hanging at the end of one. */
      .ar-show-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-start;gap:12px}
      .ar-show-id h1{margin:9px 0 4px;font-size:clamp(34px,9.5vw,52px)}
      .ar-show-body{margin-top:26px}
      .ar-canvas>.ar-show{margin-top:0}
      .ar-stage figcaption{display:none}
      .ar-show-actions{gap:11px;margin-top:17px;margin-bottom:14px}
      .ar-set{grid-template-columns:1fr;gap:5px}
      /* Stacked at this width — the label sits ABOVE its songs, so it goes back
         to reading left and to its own compact line box. */
      .ar-set-label{padding-top:0;text-align:left;line-height:1.2}
      /* Thumb-sized songs, WITHOUT the setlist reading like a list of headlines.
         Alex, 2026-08-11: "did you adjust the line height too? it seems extra
         large." Nothing adjusted it — the airiness WAS the tap target. The two
         numbers here are one number: a song's hit area is its 21px of ink plus
         its vertical padding on both sides, and the line-height has to clear
         that or two songs on consecutive lines overlap and a thumb gets the
         wrong one. At 10px of padding the hit area is 41px, which forced 2.4
         (43.2px) and left every line stranded in twice its own height.
         8px of padding puts the hit area at 37px and lets the line close to
         2.05 (36.9px) — still a target half again the size of the text, still
         no overlap, and 15% of the vertical air gone. The negative margin
         continues to hand the padding back to the line box so the setlist keeps
         its own line breaks. The underline stays anchored to the text. */
      .ar-set-songs{font-size:18px;line-height:2.05}
      .ar-song{padding:8px 0;margin:-8px 0}
      .ar-song::after{bottom:calc(8px - .16em)}
      .ar-nights{grid-template-columns:1fr 1fr;gap:12px}
      .ar-night-city{font-size:16px}
      .ar-night-venue{font-size:12.5px}
      .ar-songgrid,.ar-placegrid{grid-template-columns:1fr 1fr;gap:7px}
      .ar-songcard,.ar-placecard{padding:13px 14px}
      .ar-songcard strong,.ar-placecard strong{font-size:14.5px}
      /* Dock: title + transport stay reachable; the scrubber becomes a hairline
         at the very top edge so nothing competes for the thumb. */
      /* Content must be able to scroll clear of the dock, or the last line of a
         setlist sits underneath it forever. */
      body.has-dock .ar-shell{padding-bottom:104px}
      /* THE SHELL IS NOT THE BOTTOM OF THE PAGE. body.has-dock{padding-bottom:0}
         stood here, on the reasoning that .ar-shell reserves the room instead and
         reserving twice would leave a dead strip. But the footer is a SIBLING of
         .ar-shell, not a child, so the shell's padding never reached it: measured
         2026-08-20 at 390px, scrolled to the very bottom, the footer's last line
         sat 73.7px under the archive bar and 64.4px under a bar carried in from
         the homepage. Alex, 2026-08-20: "the player line is over the content."
         The body reservation is restored (the unscoped rule above pays out
         --player-h, which IS the measured bar, inset included); .ar-shell keeps
         its own 104px so the setlist still clears the bar without scrolling. */
      /* The bar's gutter is the PAGE's gutter (Alex, 2026-08-11: the show info "is
         also off to the right not on the same left line it needs to come left to
         align with the rest of the page"). The setlist and the heading above it
         sit at 16px; this bar sat at 14px, so the artwork and the song title
         started two pixels inside everything they were meant to line up with. */
      .ar-dock-inner{grid-template-columns:minmax(0,1fr) auto;column-gap:12px;width:100%;min-height:64px;padding:9px 16px calc(9px + env(safe-area-inset-bottom,0px))}
      .ar-dock-side{display:none}
      .ar-dock-center{width:auto;row-gap:0}
      .ar-dock-timeline{position:absolute;left:0;right:0;top:0;max-width:none;grid-template-columns:minmax(0,1fr);gap:0}
      .ar-dock-timeline .ar-clock{display:none}
      .ar-scrubber{height:14px;margin-top:-7px}
      .ar-dock-controls .ar-skip{display:none}
      .ar-dock-controls .ar-skip[data-listen-next]{display:grid}
      /* THE COMPACT BAR'S PLAY BUTTON (Alex: "not proportionate... larger,
         maybe a little off to the left. I would just use the one that's all
         over the site, but with the outline. roughly as tall as the other two
         icons.") Two changes, not a redesign: .ar-toggle's own base rule
         (above) is a SOLID white-filled circle — the one glyph on this bar
         that isn't drawn in the site's outline-button language .ar-skip
         already uses (1px border, no fill). Below 820px it now borrows that
         exact treatment instead of inventing a third style, sized up from
         38px so it reads as the primary control next to the outlined 30px
         skip button beside it, and nudged off the safe-area edge with the
         same margin every other edge control on this bar keeps. Nothing
         above 820px changes. */
      /* Mini-bar only: the 6px safe-area nudge belongs to the compact bar's
         edge, and it was leaking into the full-screen sheet — where the row is
         width:100% and space-between, so the margin shoved the whole row 6px
         left of the shared 42px gutter (the last of Alex's "3 different
         padding settings", 2026-08-11). */
      .ar-dock:not(.is-sheet) .ar-dock-controls{margin-right:6px}
      .ar-toggle{width:46px;height:46px;border:1px solid rgba(255,255,255,.32);background:rgba(9,9,11,.34);color:#fff;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
      .ar-toggle svg{width:18px;height:20px}
    }
    @media(max-width:400px){
      /* The stage is a plain block below 820px — the photograph inside it owns
         the 16:9, and pinning the wrapper to a ratio here clipped the identity
         block into the setlist. */
      .ar-songgrid,.ar-placegrid{grid-template-columns:1fr}
    }
    @media(prefers-reduced-motion:reduce){
      /* The leading fill still shows where the show is; it just snaps to each
         reading instead of crawling between them. */
      .ar-song,.ar-song::after,.ar-song.is-leading,.ar-invite,.ar-invite-wild::before,.ar-songcard,.ar-placecard,.ar-eracard,.ar-spine-bar::before{transition:none}
      .ar-song.is-current,.ar-invite-wild::before{animation:none}
    }
  