/* ------------------------------------------------------------------
   Music discography — global tokens + page-level layout only.
   Section styling lives in each *-component.html (template.js injects it).
   ------------------------------------------------------------------ */

* { box-sizing:border-box; }
body { margin:0; }
::selection { background:var(--accent); color:#fff; }

/* No-JS fallback (rendered inside <noscript>): the component body never hydrates,
   so this plain-HTML version of the page is all a non-JS visitor sees. Keep it
   readable and on-brand; JS visitors never see it. */
/* <noscript> is display:inline by default; make it a block so the fallback
   inside it gets a proper block container even as a direct child of #app
   (on the release page it isn't wrapped in .wrap like the discography is). */
noscript { display:block; }
.ssr-fallback { max-width:720px; margin:0 auto; padding:var(--space-7) var(--space-5); font-family:var(--display); }
.ssr-fallback img { width:320px; max-width:100%; height:auto; border:var(--rule) solid var(--border); }
.ssr-fallback h1 { font-size:var(--fs-xl); margin:var(--space-5) 0 var(--space-2); }
.ssr-fallback h2 { font-size:var(--fs-md); margin:var(--space-6) 0 var(--space-2); color:var(--muted); text-transform:uppercase; letter-spacing:var(--track); }
.ssr-fallback p { color:var(--muted); margin:var(--space-2) 0; }
.ssr-fallback ul, .ssr-fallback ol { padding-left:var(--space-5); line-height:1.9; }
.ssr-fallback a { color:var(--accent); }
.ssr-fallback .ssr-links { list-style:none; padding-left:0; }
.ssr-fallback ol span { color:var(--muted); }

/* Keyboard focus — one visible accent ring for every interactive element.
   The page is almost entirely links, so this is the cheapest a11y win: mouse
   clicks stay clean (:focus-visible), only keyboard/AT focus paints the ring.
   Components with clipped/edge-to-edge targets (seg buttons, index rows) inset
   their own ring so an ancestor's overflow:hidden can't clip it. */
#app :focus-visible { outline:2px solid var(--accent); outline-offset:3px; }

#app[data-theme="dark"] {
    --bg:#0e1014; --text:#f1ede4; --panel:#131417; --ptext:#f1ede4;
    --border:#36312a; --accent:#ff5219; --muted:#a39c8d; --hair:rgba(241,237,228,0.12); --grain:0.16;
}
#app[data-theme="light"] {
    --bg:#ece8e1; --text:#16140f; --panel:#f8f6f1; --ptext:#16140f;
    /* --border was full-black #16140f (= text), which framed every cover as a hard
       wireframe; soften to a warm grey with the same restraint as dark's border. */
    --border:#c5bdae; --accent:#ff5219; --muted:#67625a; --hair:rgba(22,20,15,0.13); --grain:0.08;
}
#app {
    --display:'Space Grotesk', sans-serif;

    /* spacing — single 4/8px scale; every padding/gap/margin snaps to a step */
    --space-1:4px;  --space-2:8px;  --space-3:12px; --space-4:16px; --space-5:24px;
    --space-6:32px; --space-7:48px; --space-8:64px; --space-9:96px;

    /* borders — two weights only: a hairline and a structural rule */
    --hairline:1px;
    --rule:1.5px;

    /* type — 7 steps (display/title are hero; xl…xs cover everything else) */
    --fs-display:128px; --fs-title:84px;
    --fs-xl:30px; --fs-lg:24px; --fs-md:18px; --fs-sm:15px; --fs-xs:12px;

    /* uppercase tracking — one value for all caps labels */
    --track:1.5px;

    /* Fallback gradient-aura palette for releases with no baked palette — the
       hero (.feat-aura) and the per-release backdrop (.rd-bg) both fall back to
       these when their inline --g0/1/2 (from auraVars) is absent. */
    --aura-f0:#ff5219; --aura-f1:#ff9d3c; --aura-f2:#3a1d6e;

    position:relative; min-height:100vh; background:var(--bg); color:var(--text);
    font-family:'IBM Plex Mono', ui-monospace, monospace; overflow:hidden;
}

.grain {
    position:fixed; inset:0; pointer-events:none; z-index:9;
    opacity:var(--grain); mix-blend-mode:overlay; background-size:160px 160px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.wrap { position:relative; z-index:1; max-width:1120px; margin:0 auto; padding:0 var(--space-6); }

/* Server-rendered snapshot shell: parsed before the heavy component templates so
   cross-document view transitions always have laid-out cover targets. JS removes
   this shell immediately after normal template hydration. */
.vt-shell { position:relative; z-index:1; }
.vt-shell img { display:block; width:100%; height:100%; object-fit:cover; }

.vt-catalog .featured {
    display:flex; gap:var(--space-7); padding:var(--space-7) 0; align-items:stretch; justify-content:center;
}
.vt-catalog .featured:not(.is-single) { padding-left:var(--space-7); }
.vt-catalog .feat-cover { width:380px; flex:none; }
.vt-catalog .featured .cover {
    display:block; width:380px; height:380px; border:var(--rule) solid var(--border); overflow:hidden;
    position:relative; background-size:cover; background-position:center;
}
.vt-catalog .featured .cover img,
.vt-catalog .rc-cover img,
.vt-catalog .rr-thumb img,
.vt-release .rd-cover-img { opacity:1; }
.vt-catalog .feat-body { flex:none; width:min(100%, 520px); display:flex; flex-direction:column; min-width:0; }
.vt-catalog .feat-eyebrow { font-size:var(--fs-xs); letter-spacing:var(--track); color:var(--accent); }
.vt-catalog .feat-title { font-family:var(--display); font-weight:600; font-size:var(--fs-title); line-height:0.95; letter-spacing:-0.03em; margin:var(--space-4) 0 var(--space-2); }
.vt-catalog .feat-meta { font-size:var(--fs-xs); letter-spacing:var(--track); color:var(--muted); text-transform:uppercase; }
.vt-catalog .featured.is-single { flex-direction:column; align-items:center; text-align:center; }
.vt-catalog .featured.is-single .feat-cover,
.vt-catalog .featured.is-single .cover { width:420px; }
.vt-catalog .featured.is-single .cover { height:420px; }
.vt-catalog .featured.is-single .feat-body { align-items:center; }

.vt-catalog .rc { display:block; min-width:0; text-decoration:none; color:var(--text); }
.vt-catalog .rc-cover {
    position:relative; width:100%; aspect-ratio:1/1; overflow:hidden;
    border:var(--rule) solid var(--border); background-size:cover; background-position:center;
}
.vt-catalog .rc-head { display:flex; justify-content:space-between; align-items:flex-start; margin-top:var(--space-3); gap:var(--space-2); }
.vt-catalog .rc-title {
    font-family:var(--display); font-size:var(--fs-md); font-weight:600; min-width:0;
    display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2;
    overflow:hidden; line-height:1.2; min-height:1.2em;
}
.vt-catalog .rc-year,
.vt-catalog .rc-sub,
.vt-catalog .rr-year,
.vt-catalog .rr-dur,
.vt-catalog .rr-kind { font-size:var(--fs-xs); color:var(--muted); }
.vt-catalog .rc-year { flex:none; line-height:1.2; }
.vt-catalog .rc-sub,
.vt-catalog .rr-kind { margin-top:var(--space-1); letter-spacing:var(--track); text-transform:uppercase; }

.vt-catalog .rr {
    display:grid; grid-template-columns:56px 1fr 64px 64px; gap:var(--space-3); align-items:center;
    padding:var(--space-3) 0; border-bottom:var(--hairline) solid var(--hair); color:var(--text);
}
.vt-catalog .rr-thumb {
    width:56px; height:56px; border:var(--hairline) solid var(--border); overflow:hidden; position:relative;
    background-size:cover; background-position:center;
}
.vt-catalog .rr-title {
    display:block; text-decoration:none; color:inherit;
    font-family:var(--display); font-size:var(--fs-lg); font-weight:600; letter-spacing:-0.01em;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

.vt-release .rd { position:relative; z-index:1; display:flex; align-items:stretch; min-height:100vh; }
.vt-release .rd-cover-pane {
    flex:1 1 50%; position:relative; min-height:100vh; overflow:hidden;
    background-size:cover; background-position:center;
}
.vt-release .rd-cover-img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.vt-release .rd-cover-fade {
    position:absolute; inset:0; pointer-events:none;
    background:linear-gradient(90deg, transparent 55%, color-mix(in srgb, var(--bg) 55%, transparent) 100%);
}
.vt-release .rd-panel {
    flex:1 1 50%; min-width:0; display:flex; flex-direction:column; justify-content:center;
    padding:var(--space-9) var(--space-7);
    background:color-mix(in srgb, var(--bg) 72%, transparent); backdrop-filter:blur(7px);
    border-left:var(--hairline) solid var(--hair);
}
.vt-release .rd-eyebrow { font-size:var(--fs-xs); letter-spacing:var(--track); color:var(--accent); text-transform:uppercase; }
.vt-release .rd-title { font-family:var(--display); font-weight:600; font-size:var(--fs-title); line-height:.96; letter-spacing:-0.03em; margin:var(--space-4) 0 0; overflow-wrap:break-word; }
.vt-release .rd-meta { font-size:var(--fs-xs); letter-spacing:var(--track); color:var(--muted); margin-top:var(--space-2); text-transform:uppercase; }

@media (max-width:900px) {
    .vt-catalog .featured { flex-direction:column; align-items:center; text-align:center; gap:var(--space-5); padding:var(--space-5) 0; }
    .vt-catalog .featured:not(.is-single) { padding-left:0; }
    .vt-catalog .feat-cover,
    .vt-catalog .featured .cover,
    .vt-catalog .featured.is-single .feat-cover,
    .vt-catalog .featured.is-single .cover { width:100%; }
    .vt-catalog .featured .cover,
    .vt-catalog .featured.is-single .cover { height:auto; aspect-ratio:1/1; }
    .vt-catalog .feat-body { width:100%; align-items:center; }
    .vt-catalog .feat-title { font-size:60px; }

    .vt-release .rd {
        flex-direction:column; align-items:center; text-align:center; min-height:auto;
        max-width:760px; margin:0 auto; padding:var(--space-6) var(--space-6) var(--space-8);
    }
    .vt-release .rd-cover-pane {
        flex:none; width:360px; max-width:80vw; aspect-ratio:1/1; min-height:auto;
        border:var(--rule) solid var(--border); box-shadow:0 30px 80px rgba(0,0,0,.5);
    }
    .vt-release .rd-cover-fade { display:none; }
    .vt-release .rd-panel {
        flex:none; width:100%; padding:0; margin-top:var(--space-6); align-items:center;
        background:none; backdrop-filter:none; border-left:none;
    }
    .vt-release .rd-title { font-size:60px; }
}
@media (max-width:760px) {
    .vt-catalog .rr { grid-template-columns:56px 1fr auto; gap:var(--space-4); }
    .vt-catalog .rr-dur { display:none; }
    .vt-catalog .rr-title { font-size:var(--fs-md); white-space:normal; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; }
}
@media (max-width:520px) {
    .vt-catalog .feat-title,
    .vt-release .rd-title { font-size:44px; }
}

/* ===== Cross-document view transitions ======================================
   Both the discography and the release page load this stylesheet, so opting into
   same-origin navigation transitions here covers the pair. Every cover carries a
   unique view-transition-name (cover-<slug>, set inline from release data) shared
   with the matching release-page hero — so clicking a cover MORPHS it into the
   hero, and back. Covers in the hidden layout (display:none) don't participate, the
   featured release is never duplicated in the wall, and the old static pages don't
   load this CSS — so names never collide. Unsupported browsers ignore all of this
   and navigate normally. */
@view-transition { navigation: auto; }

/* Tag every cover with a shared transition class so the morph can be tuned in one
   place (browsers without view-transition-class just get the default morph). The
   cover changes aspect ratio across the morph (square card -> tall hero pane), so
   cover-fit both snapshots: the artwork crops to fill instead of stretching. */
.rc-cover, .rr-thumb, .featured .cover, .rd-cover-pane { view-transition-class: cover; }
/* Morph easing: a smooth, slightly emphasised ease-out so the cover sets off
   quickly and settles into place rather than arriving linearly. A touch longer than
   instant — a larger morph reads as smoother (and less janky) at ~.45s than rushed
   at ~.3s, since the eye tracks fewer pixels per frame. */
::view-transition-group(.cover) {
    animation-duration: .45s;
    animation-timing-function: cubic-bezier(.32, .72, 0, 1);
}
/* Old/new just cross-fade (the group handles the move/resize). They inherit the
   curve + duration so the fade tracks the morph. Deliberately NO filter here: the
   cover's group resizes every frame, so any blur would force a full re-raster of the
   artwork texture per frame — that was the artwork-specific stutter. Opacity is
   compositor-cheap, which is why the text/panel (root cross-fade) stayed smooth. */
::view-transition-old(.cover), ::view-transition-new(.cover) {
    height: 100%; width: 100%; object-fit: cover;
    animation-duration: .45s;
    animation-timing-function: cubic-bezier(.32, .72, 0, 1);
}

/* Reverse navigation only: the non-morph covers re-enter as their OWN layer rather
   than riding the catalog crossfade — JS retags them `.art` on the way back (see
   music.php). That lets the grid "develop" in more slowly than the catalog text,
   and sit below the returning hero. They're new-only here (there's no `.art` on the
   forward trip), so only the entrance is styled. */
::view-transition-group(.art) {
    animation-duration: .8s;
    animation-timing-function: cubic-bezier(.22,.7,.2,1);
}
::view-transition-new(.art) {
    height: 100%; width: 100%; object-fit: cover;
    animation: artIn .4s cubic-bezier(.22,.7,.2,1) both;
}
/* The re-entering covers can't be forced under the morphing hero — neither group
   z-index nor real paint order is honored for view-transition stacking here — so they
   MUST stay transparent while it travels (otherwise they paint over it). The hold lasts
   the full morph, then hands off: the covers develop in exactly as the hero lands, with
   no dead beat. The morph runs to ~50% of the cover timeline (and the same ratio under
   the proportional debug slowdown), so 50% is that landing point. Push higher for a
   deliberate pause after the morph; lower and the covers start mid-morph (clipping it). */
@keyframes artIn { 0% { opacity: 0; } 5% { opacity:0; } 100% { opacity:1; } }

/* Catalog section (either wall or index) fades during cross-document nav
   to/from a release page. Only the active section participates because the
   inactive one is display:none. */
.wall, .index { view-transition-name: catalog; }
::view-transition-group(catalog) {
    animation-duration: .52s;
    animation-timing-function: cubic-bezier(.22,.7,.2,1);
}
::view-transition-old(catalog) {
    animation: catalogOut .32s ease both;
}
::view-transition-new(catalog) {
    animation: catalogIn .52s cubic-bezier(.22,.7,.2,1) both;
}
@keyframes catalogOut { to { opacity:0; } }
@keyframes catalogIn { from { opacity:0; } to { opacity:1; } }

/* bfcache restore fallback: some back/forward traversals skip cross-document
   transition painting, so replay a lightweight section fade on pageshow. */
#app.vt-back-fade[data-layout="wall"] .wall,
#app.vt-back-fade[data-layout="index"] .index { animation:catalogBackFade .36s ease both; }
@keyframes catalogBackFade { from { opacity:0; } to { opacity:1; } }

/* During the same-document layout swap (wall <-> index) keep a plain crossfade
   rather than 12 covers flying between grid and list — drop their morph names for
   that transition only (toggled by layout-controls-component). The cover morph
   stays for cross-document navigation. !important to beat the inline name. */
#app.vt-toggling .rc-cover,
#app.vt-toggling .rr-thumb,
#app.vt-toggling .wall,
#app.vt-toggling .index { view-transition-name: none !important; }

@media (prefers-reduced-motion: reduce) {
    /* Honour reduced-motion: cut the morph/crossfade, navigate instantly. */
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
}

/* layout toggle — the WALL grid vs the INDEX list (section styling is in the
   release-card / release-row components; only placement + visibility is global). */
.wall { padding:var(--space-5) 0 var(--space-9); display:grid; grid-template-columns:repeat(4,1fr); gap:var(--space-5); }
.index { padding:0 0 var(--space-9); }
#app[data-layout="wall"]  .index { display:none; }
#app[data-layout="index"] .wall  { display:none; }

/* Scroll-reveal — wall cards / index rows start lowered + transparent and settle
   as they scroll into view, staggered within each batch via --i (set in JS).
   JS-gated through #app.reveal-init so non-JS visitors and the pre-hydration
   paint never get stuck on hidden content; the hero/header are never hidden.
   Reduced-motion shows everything at rest with no transition. */
#app.reveal-init .wall > *,
#app.reveal-init .index > * { opacity:0; transform:translateY(14px); }
#app.reveal-init .wall > *.revealed,
#app.reveal-init .index > *.revealed {
    opacity:1; transform:none;
    transition:opacity .5s ease, transform .6s cubic-bezier(.22,.7,.2,1);
    transition-delay:calc(var(--i,0) * 45ms);
}
@media (prefers-reduced-motion: reduce) {
    #app.reveal-init .wall > *,
    #app.reveal-init .index > * { opacity:1; transform:none; transition:none; }
}

@media (max-width:900px) { .wall { grid-template-columns:repeat(2,1fr); } }
/* Mobile: tighter side gutters and section padding so the page doesn't feel
   stranded in whitespace, and the wall holds two columns down to the smallest
   width (a single tower of covers reads as too sparse / scroll-heavy). */
@media (max-width:640px) {
    .wrap  { padding:0 var(--space-4); }
    .wall  { padding:var(--space-4) 0 var(--space-7); gap:var(--space-4); }
    .index { padding:0 0 var(--space-7); }
    /* Mirror the catalog .wrap gutters + site-header's tightened top padding so the
       snapshot shell's back-bar matches the catalog header (see release-detail-component). */
    .vt-release .rd { padding:var(--space-5) var(--space-4) var(--space-8); }
}
@media (max-width:520px) { .wall { gap:var(--space-3); } }
