:root{
  /* Yahn Dawn ’26: indigo night → teal dawn glow · aspen yellow-gold · cream */
  --bg:#0b1430; --bg2:#0f1d3a; --panel:#12203c; --line:#243a64;
  --ink:#f3ecd5; --mut:#9aacd2; --dim:#7d90bd;  /* --dim lifted from #5e739f to clear WCAG AA 4.5:1 on bg/panel */
  --gold:#e6c84f; --gold2:#d4ad38; --teal:#3d97a8; --green:#7fae6b; --cream:#f3ecd5;
  --dawn:linear-gradient(90deg,#1b3a6b 0%,#2f7e90 34%,#4ea7bb 52%,#7fae6b 70%,#e6c84f 100%);
  /* chakra spectrum — a sound-healing colour system used as the site's vibrant palette. A designed
     colour choice that EXTENDS the gold/teal/green brand; no claim about any track's real pitch. */
  --ch-root:#e23b54; --ch-sacral:#f07a2e; --ch-solar:#f5c542; --ch-heart:#5cc46b;
  --ch-throat:#3d97a8; --ch-brow:#3a6fb0; --ch-crown:#7b5cd6;
  --dawn-spectrum:linear-gradient(90deg,#3a6fb0,#3d97a8,#5cc46b,#f5c542,#f07a2e,#e23b54,#7b5cd6);
  --ch-spectrum:var(--dawn-spectrum);
  /* type system — Roboto is the readable workhorse; Monoton stays for the hero/logo brand moment */
  --h1:"Monoton","Roboto",system-ui,sans-serif;            /* hero + logo */
  --body:"Roboto",system-ui,sans-serif;                    /* section headings + all UI + data */
  --accent:"Roboto",system-ui,sans-serif;                  /* h3 + header kicker + footer + special accents */
  --mono:var(--body);                                       /* legacy alias — no monospace/"code" fonts */
  --grain:.03;
  /* type scale — one ladder, bumped ~15-20% for readability (Roboto runs a touch smaller than the old condensed face) */
  --fs-3xs:12px; --fs-2xs:13px; --fs-xs:14px; --fs-sm:16px; --fs-md:18px;
  --fs-base:20px; --fs-lg:24px; --fs-xl:31px; --fs-2xl:64px; --fs-3xl:148px;
  --fs-hero:clamp(38px,5.6vw,70px);
  /* --fs-hero-name sizes the site name (28 characters, "Pretty Lights Music Database")
     so it stays the dominant hero line at every width without overflowing on narrow
     phones. --fs-hero-tag sizes "Swirl Samples" underneath it: still the Monoton display
     treatment, but deliberately smaller so the hierarchy reads identity-first. Both are
     separate from --fs-hero, which playlist.html's own h1 still uses unchanged. */
  --fs-hero-name:clamp(32px,7.4vw,60px);
  --fs-hero-tag:clamp(23px,4.2vw,40px);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
html,body{margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--body);font-size:var(--fs-base);letter-spacing:.02em;-webkit-font-smoothing:antialiased}
body{position:relative;overflow-x:hidden;
  background:
    radial-gradient(110% 60% at 50% 96%, rgba(61,151,168,.16), transparent 62%),
    radial-gradient(70% 45% at 50% 2%, rgba(230,200,79,.07), transparent 60%),
    linear-gradient(180deg,#0a1230 0%,#0b1838 60%,#0d2240 100%);
  background-attachment:fixed}
body::after{content:"";position:fixed;inset:0;pointer-events:none;z-index:60;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity:var(--grain);mix-blend-mode:overlay}
.swirl-mark{position:fixed;right:-60px;bottom:-60px;width:340px;height:340px;z-index:1;opacity:.05;pointer-events:none;color:var(--gold)}

/* Fluid cap, not a fixed 1080px: a 1920px viewport used to leave ~840px of bare background
   beside every chart (measured fill ratio 1.00 on the SVGs themselves — the width was simply
   never offered to them). 94vw keeps a hairline of breathing room at the viewport edge; 1680px
   stops the line length running away on ultra-wide monitors. nav's padding calc and .foot-grid's
   max-width below MUST mirror this value, or the full-bleed nav/footer content drifts out of
   alignment with .wrap's edge the moment the viewport is wide enough to hit the cap. */
.wrap{max-width:min(94vw,1680px);margin:0 auto;padding:0 28px;position:relative;z-index:2}
.dawn-bar{height:5px;background:var(--dawn-spectrum);position:relative;z-index:3}
h1{font-family:var(--h1);font-weight:400;letter-spacing:.05em;margin:0;line-height:1.05}
h2{font-family:var(--body);font-weight:800;letter-spacing:.03em;margin:0;line-height:1.05}
h3{font-family:var(--accent);font-weight:400;letter-spacing:.03em;margin:0;line-height:1.1}
.label{font-family:var(--body);font-weight:600;font-size:var(--fs-2xs);letter-spacing:.28em;text-transform:uppercase;color:var(--dim)}

nav{display:flex;align-items:center;justify-content:space-between;padding:18px max(28px,calc((100% - min(94vw,1680px)) / 2 + 28px));border-bottom:1px solid var(--line);position:relative;z-index:3}
.logo{font-family:var(--h1);font-size:var(--fs-lg);letter-spacing:.06em;color:var(--cream);display:flex;align-items:center;gap:0;text-decoration:none}
.logo b{font-weight:400;margin-left:5px;background:var(--dawn-spectrum);-webkit-background-clip:text;background-clip:text;color:transparent}
.logo .lemon{width:16px;height:16px;opacity:.9;margin-left:10px}
.navlinks{display:flex;gap:26px}
.navlinks a{font-family:var(--body);font-weight:600;font-size:var(--fs-sm);letter-spacing:.08em;color:var(--mut);text-decoration:none}
.navlinks a.active{color:var(--gold)}
.navlinks a:hover{color:var(--ink)}

.hero{padding:52px 0 40px;text-align:center}
.swirl-sun{display:block;margin:0 auto 18px;color:var(--gold)}
.kicker{display:inline-block;margin-bottom:18px;font-family:var(--accent);font-size:var(--fs-base);letter-spacing:.05em;color:var(--mut)}
/* text-wrap:balance lets the browser pick line breaks that even out a multi-word
   headline instead of greedily filling each line, which is what keeps "Pretty Lights
   Music Database" reading as two tidy lines on a phone rather than one long line plus a
   short leftover word on its own. */
.hero h1{font-size:var(--fs-hero-name);line-height:1.12;text-wrap:balance}
.hero h2{font-family:var(--h1);font-weight:400;letter-spacing:.05em;line-height:1.18;
  margin:14px 0 0;color:var(--mut);font-size:var(--fs-hero-tag);text-wrap:balance}
.hero h1 .glow{background:var(--dawn-spectrum);-webkit-background-clip:text;background-clip:text;color:transparent}
.hero p{color:var(--mut);font-size:var(--fs-md);max-width:580px;margin:20px auto 0;line-height:1.5;letter-spacing:.025em}
.searchbox{margin:52px auto 0;max-width:680px;display:flex;align-items:center;border:1.5px solid var(--line);background:rgba(18,32,60,.7);position:relative;overflow:hidden}
.searchbox .edge{position:absolute;inset:0;padding:1.5px;pointer-events:none;background:var(--dawn);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity .2s}
.searchbox:focus-within .edge{opacity:1}
.searchbox input{flex:1;background:transparent;border:0;outline:0;color:var(--ink);font-family:var(--body);font-size:var(--fs-md);letter-spacing:.03em;padding:18px 20px}
.searchbox input::placeholder{color:var(--dim)}
.searchbox button{font-family:var(--body);font-weight:700;font-size:var(--fs-sm);letter-spacing:.14em;text-transform:uppercase;background:var(--gold);color:#0b1430;border:0;padding:0 24px;align-self:stretch;cursor:pointer}
.chips{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:18px}
.chip{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.06em;color:var(--mut);border:1px solid var(--line);padding:6px 12px;border-radius:999px;cursor:pointer;text-decoration:none;display:inline-block}
.chip:hover{color:var(--gold);border-color:var(--gold)}

.sec{padding:54px 0;border-top:1px solid var(--line)}
.sec-head{display:flex;flex-wrap:wrap;gap:8px 16px;align-items:baseline;justify-content:space-between;margin-bottom:26px}
.sec-head h2{font-size:clamp(20px,2.8vw,30px)}
.sec-head .label{margin-bottom:8px;display:block}

.viz-card{background:var(--panel);border:1px solid var(--line);padding:22px}
.viz-top{display:flex;flex-wrap:wrap;gap:12px 18px;justify-content:space-between;align-items:flex-start;margin-bottom:14px}
/* The text column (always the first child) flexes to claim the row's leftover space
   instead of shrinking to its own content: a plain flex child with no flex-grow sizes to
   at most its max-width and never actually fills the space around it, which is why the
   subtext used to read short on both ends, capped well inside the 1680px wrap on desktop
   and squeezed beside a legend or stat-strip sibling on phones instead of taking the row
   for itself. min-width:0 lets it shrink past its own text's min-content width so it can
   still wrap normally at the narrowest widths instead of forcing the row wider than the
   viewport. */
.viz-top>div:first-child{flex:1 1 280px;min-width:0}
.viz-top p{margin:6px 0 0;color:var(--mut);font-size:var(--fs-md);line-height:1.45;letter-spacing:.02em}
/* Structural caveat under the network blurb: quieter than the blurb because it qualifies the
   chart rather than describing it, but not hidden — it bounds how far the colours can be read. */
.viz-top p.net-shape{color:var(--dim);font-size:var(--fs-sm)}
.viz-top p.net-shape:empty{display:none}
.legend{display:flex;gap:10px;align-items:center}
.legend .bar{width:120px;height:8px;background:var(--dawn-spectrum)}
.legend span{font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.06em;color:var(--dim)}
.legend .dot{display:inline-flex;align-items:center;gap:6px;font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.06em;color:var(--mut)}
.legend .dot i{width:10px;height:10px;border-radius:50%;display:inline-block}
.viz-svg{width:100%;height:auto;display:block}

.recap-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.stat{padding:22px 20px;border:1px solid var(--line);position:relative;overflow:hidden;background:var(--bg2);min-height:168px;display:flex;flex-direction:column;justify-content:space-between}
.stat .label,.stat .big,.stat .sub{position:relative;z-index:2}
.stat .big{font-family:var(--body);font-weight:900;font-size:clamp(34px,4.2vw,var(--fs-2xl));line-height:.9;letter-spacing:.005em}
.stat .sub{font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.04em;color:var(--mut)}
.stat .blob{position:absolute;width:160px;height:160px;border-radius:50%;filter:blur(36px);opacity:.4;right:-40px;bottom:-50px}
.s1 .blob{background:var(--gold)}.s1 .big{color:var(--gold)}
.s2 .blob{background:var(--teal)}.s2 .big{color:#79c3d3}
.s3 .blob{background:var(--green)}.s3 .big{color:#a6cf90}
.s4 .blob{background:#3a6fb0}.s4 .big{color:var(--cream)}
/* A single generated sentence (can carry a "deepest cut tonight: Artist · Title (xN career)"
   clause), so unlike .foot-tag/.sf-note it earns a wider cap than a short caption but still
   needs one now that .wrap can run past 1600px. */
.recap-note{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.05em;color:var(--dim);margin-top:14px;max-width:60ch}

.results{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.res-group{grid-column:1/-1;display:flex;align-items:center;gap:10px;margin-top:14px;
  font-family:var(--body);font-weight:700;font-size:var(--fs-2xs);letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}
.res-group:first-child{margin-top:0}
.res-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
.res-n{color:var(--mut);font-weight:600;letter-spacing:.05em}
.res{border:1px solid var(--line);background:var(--panel);padding:16px 18px;display:flex;gap:14px;align-items:center}
.res .era{width:6px;align-self:stretch;border-radius:2px}
.res .body{flex:1}
.res .ttl{font-family:var(--body);font-weight:700;font-size:var(--fs-lg);letter-spacing:.02em}
.res .meta{font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.04em;color:var(--mut);margin-top:3px}
.res .count{font-family:var(--body);font-weight:600;font-size:var(--fs-sm);letter-spacing:.06em;color:var(--gold);border:1px solid var(--line);padding:6px 10px;white-space:nowrap}
.res .links{display:flex;gap:8px;margin-top:8px}
.res .links a{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.05em;color:var(--dim);text-decoration:none;border-bottom:1px solid var(--line)}
.res .links a:hover{color:var(--gold);border-color:var(--gold)}

/* (legacy generic `footer{display:flex}` removed — every page now uses the
   structured `.site-foot` below; the old rule was shrink-wrapping its grid.) */
.tag{font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.1em;text-transform:uppercase;color:var(--dim);border:1px solid var(--line);padding:3px 8px}
/* Cover flag on a setlist row. Separate from .tag because .tag is the neutral pill used for
   several unrelated things, and this one carries a specific claim: the entry is not a PL
   original. Gold-tinted fill rather than a border, and tighter than .tag, so it reads as an
   annotation on the song rather than another chip in a row of chips. `.mixed` is deliberately
   quieter: a mixed medley only PARTLY belongs to someone else. */
.tag-cover{font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.08em;text-transform:uppercase;color:var(--gold);background:rgba(230,200,79,.1);padding:2px 7px;white-space:nowrap}
.tag-cover.mixed{color:var(--dim);background:rgba(230,200,79,.05)}

/* segmented type / sort control */
.seg{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:30px}
.segbtn{font-family:var(--body);font-weight:700;font-size:var(--fs-2xs);letter-spacing:.12em;text-transform:uppercase;
  color:var(--mut);background:transparent;border:1px solid var(--line);padding:8px 16px;border-radius:999px;cursor:pointer;transition:.15s}
.segbtn:hover{color:var(--ink);border-color:var(--mut)}
.segbtn.active{background:var(--gold);color:#0b1430;border-color:var(--gold)}

/* shows index — full-width rows, each fronted by its poster (or a branded placeholder) */
.show-grid{display:flex;flex-direction:column;gap:12px}
.show-row{position:relative;display:flex;align-items:stretch;gap:20px;background:var(--panel);
  border:1px solid var(--line);padding:16px 22px 16px 24px;text-decoration:none;color:inherit;
  overflow:hidden;transition:border-color .15s ease,transform .15s ease,box-shadow .15s ease}
.show-row:hover{border-color:var(--gold);transform:translateY(-2px);box-shadow:0 10px 30px rgba(0,0,0,.35)}
/* Era colour sits ON the date it encodes, not down the row's edge.
   It used to be a 5px coloured rail pinned to the left of every row. Two problems: the row
   already PRINTS the value that rail was colouring (the date, or "last heard 2025"), so the
   colour restated a channel that was right there in text, and a coloured left border is the
   classic decoration-in-place-of-structure move this project's design rules reject outright.
   As a dot against the date it is direct labelling instead: colour touching its own datum. */
.show-row .era-dot{display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:8px;vertical-align:baseline;flex:0 0 auto}
/* poster slot: portrait 2:3. A branded era-gradient placeholder (swirl + year) sits underneath;
   the user's image layers on top via .sp-img and removes itself on a load error. */
.show-poster{position:relative;flex:0 0 auto;width:104px;aspect-ratio:2/3;overflow:hidden;
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;
  background:var(--bg2);
  background:linear-gradient(155deg,color-mix(in srgb,var(--era) 40%,var(--bg2)),var(--bg2) 72%)}
.show-poster .sp-swirl{position:absolute;width:62%;height:62%;color:var(--era);opacity:.5}
.show-poster .sp-year{position:relative;font-family:var(--body);font-weight:900;font-size:var(--fs-lg);
  letter-spacing:.04em;color:var(--ink);opacity:.82;text-shadow:0 1px 6px rgba(0,0,0,.45)}
.show-poster .sp-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
  background:var(--bg2);transition:transform .25s ease}
.show-row:hover .sp-img{transform:scale(1.03)}
/* info column + chevron */
.sr-body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:5px}
.sr-body .sc-title{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.sr-go{align-self:center;flex:0 0 auto;font-family:var(--body);font-size:var(--fs-lg);color:var(--dim);
  padding-left:6px;transition:color .15s ease,transform .15s ease}
.show-row:hover .sr-go{color:var(--gold);transform:translateX(3px)}
.sc-date{font-family:var(--body);font-weight:600;font-size:var(--fs-2xs);letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
.sc-title{font-family:var(--body);font-weight:800;font-size:var(--fs-lg);line-height:1.12;letter-spacing:.01em;color:var(--ink)}
.sc-where{font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.03em;color:var(--mut)}
.sc-stats{margin-top:6px;font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.04em;color:var(--mut)}
.sc-stats b{color:var(--gold);font-weight:800}
.sc-dot{margin:0 8px;color:var(--dim)}
/* "live ▶" chip — opens the night's set on Pretty Lights Live (a sibling action inside the row) */
.sr-pll{align-self:center;flex:0 0 auto;font-family:var(--body);font-weight:700;font-size:var(--fs-3xs);letter-spacing:.08em;text-transform:uppercase;color:var(--gold);border:1px solid var(--line);border-radius:999px;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}
.sr-pll:hover,.sr-pll:focus-visible{background:var(--gold);color:#0b1430;border-color:var(--gold);outline:none}
/* per-show detail: the same link, styled as a pill button in the hero meta */
.sh-pll{display:inline-block;margin-top:14px;font-family:var(--body);font-weight:700;font-size:var(--fs-sm);letter-spacing:.03em;color:var(--gold);text-decoration:none;border:1.5px solid var(--gold);border-radius:999px;padding:9px 18px;transition:background .15s,color .15s}
.sh-pll:hover,.sh-pll:focus-visible{background:var(--gold);color:#0b1430}
.sh-pll[hidden]{display:none}
/* per-row rarity meter — how deep-cut vs crowd-pleaser the setlist runs */
.sr-meter{display:flex;align-items:center;gap:9px;margin-top:7px}
.rm-track{position:relative;width:128px;height:4px;background:var(--line);overflow:hidden;flex:0 0 auto}
.rm-fill{position:absolute;left:0;top:0;bottom:0;background:linear-gradient(90deg,var(--teal),var(--gold))}
.rm-label{font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}
/* per-row "set silhouette": records sampled per song across the night, in play order */
.sr-spark{flex:0 0 auto;align-self:center;display:flex;align-items:center;gap:5px;opacity:.9}
.sr-spark-plot{display:flex;flex-direction:column;gap:3px}
.sr-spark .spark{display:block;width:156px;height:54px}
.sr-ax{font-family:var(--body);font-size:10px;line-height:1;letter-spacing:.03em;color:var(--dim);white-space:nowrap}
.sr-ax-y{writing-mode:vertical-rl;transform:rotate(180deg);text-align:center}
.sr-ax-x{text-align:center}
.spark-area{fill:var(--gold);opacity:.15}
.spark-line{fill:none;stroke:var(--gold);stroke-width:1.3;opacity:.85;vector-effect:non-scaling-stroke}
/* hover marker on the row sparkline (revealed by shows.js) */
.spark-cursor{stroke:var(--cream);stroke-opacity:.45;stroke-width:1;vector-effect:non-scaling-stroke}
.spark-dot{fill:var(--gold);stroke:var(--bg);stroke-width:1.4;vector-effect:non-scaling-stroke}
.show-row:hover .sr-spark{opacity:1}
.show-row:hover .spark-area{opacity:.24}
/* "how to read each row" key — names the two row micro-charts (rarity meter + set silhouette),
   which otherwise read as unlabelled decoration. Uses live sample swatches, not just text. */
.shows-key{display:flex;flex-wrap:wrap;align-items:center;gap:11px 30px;margin:0 0 18px;
  padding:13px 18px;border:1px solid var(--line);background:var(--bg2)}
.shows-key .sk-label{font-family:var(--body);font-weight:800;font-size:var(--fs-3xs);letter-spacing:.22em;
  text-transform:uppercase;color:var(--dim)}
.shows-key .sk-item{display:inline-flex;align-items:center;gap:11px}
.shows-key .sk-txt{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.02em;color:var(--mut);
  line-height:1.45;max-width:48ch}
.shows-key .sk-txt b{color:var(--ink);font-weight:700}
.shows-key .rm-track{flex:0 0 auto;width:58px}
.shows-key .sk-spark{width:64px;height:22px;flex:0 0 auto;opacity:.95}
@media(max-width:760px){ .shows-key .sk-item.is-spark{display:none} }  /* the row silhouette is hidden ≤760 too */
@media(max-width:560px){ .shows-key{gap:10px 16px;padding:11px 14px} .shows-key .sk-txt{max-width:none} }

/* per-show hero */
.show-hero{padding:40px 0 14px;position:relative;z-index:2}
.show-hero .backlink{font-family:var(--body);font-weight:600;font-size:var(--fs-xs);letter-spacing:.1em;text-transform:uppercase;color:var(--mut);text-decoration:none;display:inline-block;margin-bottom:18px}
.show-hero .backlink:hover{color:var(--gold)}
.show-hero .label{display:block;margin-bottom:10px}
.show-hero h1{font-family:var(--body);font-weight:900;font-size:clamp(28px,4.6vw,50px);line-height:1.04;letter-spacing:.01em}
.show-hero p{color:var(--mut);font-size:var(--fs-md);letter-spacing:.03em;margin:12px 0 0}
/* hero poster beside the title (reuses .show-poster); credit sits under it */
.sh-head{display:flex;gap:26px;align-items:flex-start}
.sh-poster{flex:0 0 auto;display:flex;flex-direction:column;gap:9px}
.sh-poster .show-poster{width:150px}
.sh-meta{flex:1;min-width:0}
.sh-credit{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.04em;color:var(--dim);
  text-decoration:none;max-width:150px;line-height:1.35}
a.sh-credit:hover{color:var(--gold)}
@media(max-width:560px){
  .sh-head{gap:16px}
  .sh-poster .show-poster{width:104px}
  .sh-credit{max-width:104px}
}

/* poster gallery — a contact sheet of the run's art (official / promo / fan-made); click to enlarge */
.poster-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:14px}
.pg-tile{position:relative;display:block;padding:0;border:1px solid var(--line);background:var(--bg2);
  aspect-ratio:2/3;overflow:hidden;cursor:zoom-in;transition:border-color .15s,transform .15s}
.pg-tile:hover{border-color:var(--gold);transform:translateY(-2px)}
.pg-tile img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .3s}
.pg-tile:hover img{transform:scale(1.04)}
.pg-kind{position:absolute;left:0;bottom:0;font-family:var(--body);font-size:var(--fs-3xs);
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink);background:rgba(11,20,48,.82);
  border-top:1px solid var(--line);border-right:1px solid var(--line);padding:3px 8px}
.pg-note{font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.03em;color:var(--dim);margin:16px 0 0}
@media(max-width:560px){ .poster-gallery{grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:10px} }

/* poster lightbox — full-bleed dark overlay, contained image, prev/next + caption
   (scroll-lock is set on documentElement in show.js, matching palette.js — body has overflow-x:hidden) */
.pg-lightbox{position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;
  gap:8px;padding:40px;background:rgba(7,12,28,.93);backdrop-filter:blur(2px)}
.pg-lightbox[hidden]{display:none}
.pgl-fig{margin:0;display:flex;flex-direction:column;align-items:center;gap:14px;max-width:min(92vw,820px);max-height:86vh}
.pgl-img{max-width:100%;max-height:78vh;object-fit:contain;border:1px solid var(--line);
  box-shadow:0 24px 70px rgba(0,0,0,.6);background:var(--bg2)}
.pgl-cap{font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.12em;text-transform:uppercase;color:var(--mut)}
.pgl-close{position:absolute;top:18px;right:20px;width:42px;height:42px;font-size:20px}
.pgl-arrow{flex:0 0 auto;width:48px;height:48px;font-size:30px;line-height:1}
.pgl-close,.pgl-arrow{display:flex;align-items:center;justify-content:center;font-family:var(--body);
  color:var(--ink);background:rgba(18,32,60,.72);border:1px solid var(--line);cursor:pointer;transition:.15s}
.pgl-close:hover,.pgl-arrow:hover{color:var(--gold);border-color:var(--gold)}
@media(max-width:560px){
  .pg-lightbox{padding:14px;gap:4px}
  .pgl-arrow{width:38px;height:38px;font-size:24px}
  .pgl-close{top:10px;right:10px}
  .pgl-img{max-height:74vh}
}

/* per-show setlist (two newspaper columns) */
.setlist{list-style:none;counter-reset:sl;margin:0;padding:0;column-count:2;column-gap:38px}
.sl-row{counter-increment:sl;display:flex;flex-wrap:wrap;align-items:baseline;gap:10px;padding:10px 0;border-bottom:1px solid var(--line);break-inside:avoid}
.sl-row::before{content:counter(sl,decimal-leading-zero);font-family:var(--body);font-weight:700;font-size:var(--fs-xs);letter-spacing:.06em;color:var(--dim);min-width:22px}
.sl-song{flex:1;font-family:var(--body);font-weight:600;font-size:var(--fs-md);letter-spacing:.02em;color:var(--ink)}
.sl-row:not(.has) .sl-song{color:var(--mut)}
.sl-row.has:hover .sl-song{color:var(--gold)}
.sl-badge{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.05em;color:var(--gold);border:1px solid var(--line);padding:2px 8px;white-space:nowrap}
.sl-badge.dim{color:var(--dim)}
/* max-width caps the measure at roughly 75ch. Without it the expanded record list ran to ~83ch
   after the 2026-08-01 widening, past the readable-line guideline the rest of the page keeps. */
.sl-ex{flex-basis:100%;max-width:75ch;font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.02em;color:var(--mut);padding:6px 0 2px 32px;line-height:1.55}

/* ============ search auto-suggest dropdown ============ */
.suggest{position:absolute;z-index:205;background:#0a1330;border:1.5px solid var(--gold2);border-top:none;
  box-shadow:0 14px 34px rgba(0,0,0,.55);max-height:360px;overflow-y:auto}
.suggest[hidden]{display:none}
.sug-item{display:flex;align-items:center;gap:12px;padding:11px 16px;cursor:pointer;
  border-bottom:1px solid rgba(36,58,100,.5)}
.sug-item:last-child{border-bottom:none}
.sug-item.active{background:var(--panel)}
.sug-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
.sug-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.sug-title{font-family:var(--body);font-weight:700;font-size:var(--fs-md);letter-spacing:.01em;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sug-title b{color:var(--gold);font-weight:800}
.sug-meta{font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.04em;color:var(--mut);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sug-count{font-family:var(--body);font-size:var(--fs-2xs);font-weight:700;letter-spacing:.05em;color:var(--dim);
  flex-shrink:0;white-space:nowrap}
.sug-group{font-family:var(--body);font-weight:700;font-size:var(--fs-3xs);letter-spacing:.14em;text-transform:uppercase;
  color:var(--dim);padding:9px 16px 4px;border-bottom:1px solid rgba(36,58,100,.5)}
.sug-group:first-child{padding-top:11px}

/* ============ explore page: D3 visualizations ============ */
/* sticky section sub-nav */
.subnav{position:sticky;top:0;z-index:20;display:flex;gap:6px;flex-wrap:wrap;
  padding:12px 0;margin-bottom:6px;background:rgba(10,18,48,.86);backdrop-filter:blur(8px);
  border-bottom:1px solid var(--line)}
.subnav a{font-family:var(--body);font-weight:700;font-size:var(--fs-xs);letter-spacing:.1em;text-transform:uppercase;
  color:var(--mut);text-decoration:none;padding:7px 13px;border:1px solid transparent}
.subnav a:hover{color:var(--ink)}
.subnav a.active{color:var(--gold);border-color:var(--line);background:var(--panel)}

/* viz host + controls */
.viz-host{width:100%;min-height:340px}
.viz-d3{display:block;width:100%;height:auto;overflow:visible}
.viz-empty{display:flex;align-items:center;justify-content:center;min-height:300px;
  color:var(--dim);font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.04em}
.viz-controls{display:flex;align-items:center;gap:14px 18px;flex-wrap:wrap;
  padding:4px 0 16px;border-bottom:1px solid var(--line);margin-bottom:16px}
.ctrl{display:inline-flex;align-items:center;gap:8px;font-family:var(--body);font-size:var(--fs-2xs);
  font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--mut)}
.ctrl b{color:var(--gold);font-weight:800;min-width:18px;text-align:right;font-size:var(--fs-sm)}
.ctrl.check{cursor:pointer;text-transform:none;letter-spacing:.04em;font-size:var(--fs-xs)}
.ctrl.check input{accent-color:var(--gold);width:15px;height:15px;cursor:pointer}
.ctrl-note,.ctrl-search,.ctrl-btn{font-family:var(--body)}
.ctrl-note{font-size:var(--fs-xs);letter-spacing:.04em;color:var(--dim);margin-left:auto}
.ctrl-search{background:rgba(18,32,60,.7);border:1px solid var(--line);color:var(--ink);
  font-size:var(--fs-sm);letter-spacing:.03em;padding:8px 12px;min-width:170px;outline:none}
.ctrl-search:focus{border-color:var(--gold)}
.ctrl-search::placeholder{color:var(--dim)}
.ctrl-btn{background:transparent;border:1px solid var(--line);color:var(--mut);cursor:pointer;
  font-size:var(--fs-2xs);font-weight:700;letter-spacing:.12em;text-transform:uppercase;padding:8px 14px}
.ctrl-btn:hover,.ctrl-btn.active{color:var(--gold);border-color:var(--gold)}
/* range sliders */
.ctrl input[type=range]{-webkit-appearance:none;appearance:none;width:108px;height:3px;
  background:var(--line);outline:none;cursor:pointer}
.ctrl input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:15px;height:15px;
  background:var(--gold);border:2px solid var(--bg);cursor:pointer;border-radius:50%}
.ctrl input[type=range]::-moz-range-thumb{width:13px;height:13px;background:var(--gold);
  border:2px solid var(--bg);cursor:pointer;border-radius:50%}

/* network: graph + detail panel */
.net-wrap{display:flex;gap:18px;align-items:stretch}
.net-wrap #net{flex:1;min-width:0}
.net-detail{width:294px;flex-shrink:0;background:var(--bg2);border:1px solid var(--line);
  padding:22px;min-height:340px;max-height:560px;overflow-y:auto}
.nd-hint{color:var(--dim);font-family:var(--body);font-size:var(--fs-sm);line-height:1.5;letter-spacing:.02em;padding-top:8px}

/* Network / Rotation / Show DNA reserve a 294px card for .net-detail before the first
   click/brush, when it holds nothing but a one-line .nd-hint — real content (.nd-kind/.nd-title/
   .nd-list, or Rotation's .ql-grid) always replaces that markup rather than sitting alongside it,
   so :has(.nd-hint) is an exact "still empty" test. While it's empty, drop the row layout for a
   column one: the chart takes the full width and the hint becomes a caption line underneath,
   reusing .chart-foot's border-top-as-separator and .nd-hint's own type rather than inventing a
   new visual language. The moment real content lands, .nd-hint is gone, :has() stops matching,
   and the base rules below restore the 294px side-by-side card — no JS change needed: the
   VIZ.onResize ResizeObserver (viz/core.js) already watches the host's width and redraws it at
   the new size, and force-graph.js's resize() already handles this exact "box changed size" case
   for the fullscreen toggle. (Circle of Fifths' panel is populated unconditionally on load, never
   just a hint, so this rule is a correct no-op there — its two-column layout is by design.) */
.net-wrap:has(.nd-hint){flex-direction:column}
.net-wrap:has(.nd-hint) .net-detail{width:auto;min-height:0;max-height:none;padding:0;
  margin-top:14px;background:transparent;border:0;border-top:1px solid var(--line);overflow:visible}
.net-wrap:has(.nd-hint) .nd-hint{max-width:70ch}
.nd-kind{font-family:var(--body);font-weight:700;font-size:var(--fs-3xs);letter-spacing:.24em;text-transform:uppercase;color:var(--dim)}
.nd-title{font-family:var(--body);font-weight:800;font-size:var(--fs-lg);line-height:1.12;color:var(--ink);margin:6px 0 4px}
.nd-meta{font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.02em;color:var(--mut)}
.nd-sub{font-family:var(--body);font-weight:700;font-size:var(--fs-3xs);letter-spacing:.2em;text-transform:uppercase;
  color:var(--teal);margin:16px 0 7px;border-top:1px solid var(--line);padding-top:13px}
.nd-spotify,.nd-link{display:inline-block;margin-top:11px;font-family:var(--body);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.04em;color:#0b1430;background:var(--green);padding:7px 12px;text-decoration:none}
.nd-spotify:hover,.nd-link:hover{background:var(--gold)}
/* detail-hero platform pills sit in one wrapping row; the row owns the top gap */
.nd-links{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
.nd-links .nd-link{margin-top:0}
.nd-list{list-style:none;margin:0;padding:0}
.nd-list li{font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.02em;color:var(--ink);
  padding:6px 0;border-bottom:1px solid rgba(36,58,100,.5);cursor:pointer;display:flex;justify-content:space-between;gap:8px}
.nd-list li:hover{color:var(--gold)}
.nd-list.small li{font-size:var(--fs-xs);color:var(--mut)}
.nd-w{color:var(--gold);font-weight:800;flex-shrink:0}

/* force-graph node states (override d3 presentation attrs) */
.fg-nodes circle.fg-hit{stroke:var(--gold);stroke-width:3.5px}
.fg-nodes circle.fg-selected{stroke:var(--cream);stroke-width:3px}

/* quadrant brush list */
.quad-list{margin-top:16px}
.quad-list .ql-head{font-family:var(--body);font-weight:700;font-size:var(--fs-2xs);letter-spacing:.16em;
  text-transform:uppercase;color:var(--gold);margin-bottom:10px}
.ql-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
.ql-item{display:flex;justify-content:space-between;gap:10px;align-items:center;text-align:left;
  background:var(--bg2);border:1px solid var(--line);color:var(--ink);cursor:pointer;
  font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.02em;padding:8px 11px}
.ql-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ql-item b{color:var(--mut);flex-shrink:0;font-weight:700}
.ql-item:hover{border-color:var(--gold);color:var(--gold)}
.ql-item:hover b{color:var(--gold)}

/* shared floating tooltip */
.viz-tip{position:fixed;z-index:90;pointer-events:none;max-width:280px;
  background:#0a1330;border:1px solid var(--gold2);padding:9px 12px;
  box-shadow:0 6px 22px rgba(0,0,0,.5)}
.viz-tip strong{display:block;font-family:var(--body);font-weight:800;font-size:var(--fs-sm);
  letter-spacing:.02em;color:var(--ink);line-height:1.2}
.viz-tip .t-sub{display:block;font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.03em;color:var(--gold);margin-top:3px}
.viz-tip .t-row{display:block;font-family:var(--body);font-size:var(--fs-2xs);letter-spacing:.03em;color:var(--mut);margin-top:2px}

/* let any host inside a .net-wrap grow next to its side panel (network + rotation + matrix) */
.net-wrap .viz-host{flex:1;min-width:0}

/* fullscreen Sample Network — the card fills the viewport; the graph stays clipped to its (now
   larger) box by the force-graph clipPath, so it fills the screen but never spills past it. */
.viz-card.is-fullscreen{position:fixed;inset:0;z-index:120;margin:0;border:0;background:var(--bg);
  padding:14px clamp(14px,4vw,40px);display:flex;flex-direction:column;overflow:hidden}
.viz-card.is-fullscreen .viz-top,.viz-card.is-fullscreen .viz-controls{flex:0 0 auto}
.viz-card.is-fullscreen .net-wrap{flex:1 1 auto;min-height:0}
.viz-card.is-fullscreen #net.viz-host{height:100%;min-height:0}
.viz-card.is-fullscreen .net-detail{max-height:none;height:auto}

/* analysis tab bar (each view is its own page now); reuses .subnav, scrolls on small screens */
.subnav.anav{flex-wrap:wrap;row-gap:8px;margin-top:14px}
.subnav.anav a{white-space:nowrap;flex:0 0 auto}
.subnav a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* Show DNA cell drill-in: open-show links in the detail panel */
.md-links{display:flex;flex-wrap:wrap;gap:12px;margin:11px 0 4px}
.md-open{font-family:var(--body);font-size:var(--fs-xs);font-weight:700;letter-spacing:.04em;color:var(--gold);
  text-decoration:none;border-bottom:1px solid var(--line)}
.md-open:hover{border-color:var(--gold)}

/* headline stat strip: number-led, so the numeral is the graphic and the card exists
   only to frame it. Centered and fully self-contained (explicit min-height and
   justify-content) rather than leaning on the unrelated bare .stat rule it happens to
   share a class name with: that rule's own min-height:168px and
   justify-content:space-between were leaking in here uninvited, which is what made these
   read as tall, empty, and unfinished rather than deliberate. white-space:nowrap on the
   numeral keeps a comma-grouped figure like "29,105" on one line instead of an ugly
   mid-number wrap; the clamp's floor is sized to still fit it two-up on a 320px phone. */
.stat-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:2px 0 18px}
.stat-strip .stat{background:var(--bg2);border:1px solid var(--line);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:6px;text-align:center;padding:22px 16px;min-height:148px}
.stat-strip .stat b{font-family:var(--body);font-weight:900;font-size:clamp(28px,5vw,var(--fs-2xl));
  line-height:1;color:var(--gold);letter-spacing:.01em;white-space:nowrap;font-variant-numeric:tabular-nums}
.stat-strip .stat span{font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.05em;color:var(--mut);text-transform:uppercase;line-height:1.25}

/* node centrality line in the network detail panel */
.nd-cent{font-family:var(--body);font-size:var(--fs-xs);letter-spacing:.04em;color:var(--mut);margin:7px 0 2px}
.nd-cent b{color:var(--gold);font-weight:800}

/* sequential heatmap legend swatch (Show DNA) — matches VIZ.seqColor (cividis, navy→gold) */
.legend .bar-seq{background:linear-gradient(90deg,#00224e,#35456c,#6e7074,#a39a6e,#fee838)}

/* editorial chart footer — the data-journalism convention: takeaway + source + method/honesty.
   Sits at the foot of a .viz-card so every chart states what it's drawn from and how to read it. */
.chart-foot{margin-top:16px;padding-top:12px;border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:7px 20px;font-family:var(--body);font-size:var(--fs-3xs);
  letter-spacing:.04em;color:var(--dim);line-height:1.55}
.chart-foot b{color:var(--mut);font-weight:700;text-transform:uppercase;letter-spacing:.12em;margin-right:5px}
/* Take/Source/Reading are editorial prose, not chart chrome — cap them so a wide .wrap can't
   stretch the "Reading" clause (the longest of the three) into a 150+ch line. flex-basis:100%
   still forces .take onto its own row; the cap just bounds how long that row reads. */
.chart-foot>span{max-width:64ch}
.chart-foot .take{flex-basis:100%;color:var(--ink);font-size:var(--fs-xs);letter-spacing:.02em;font-weight:600}
.chart-foot .take::before{content:"↘";color:var(--gold);font-weight:800;margin-right:7px}

/* a colour-dimension key that names what hue means (never colour alone) */
.legend.seqkey{align-items:center;gap:8px}
.legend.seqkey .bar{width:90px}

/* Set Flow / Repertoire — ranked "spine" bar lists (openers/closers, staples) */
.spine-wrap{display:flex;flex-wrap:wrap;gap:22px 44px}
.spine-col{flex:1 1 320px;min-width:0}
.spine-h{font-family:var(--body);font-size:var(--fs-2xs);font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:var(--mut);margin:2px 0 12px}
.spine-row{position:relative;display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  background:var(--bg2);border:1px solid var(--line);color:inherit;cursor:pointer;
  padding:9px 14px;margin-bottom:7px;overflow:hidden;transition:border-color .14s ease}
.spine-row:hover{border-color:var(--gold)}
.spine-bar{position:absolute;left:0;top:0;bottom:0;opacity:.16;transition:opacity .14s ease}
.spine-row:hover .spine-bar{opacity:.3}
.spine-name{position:relative;z-index:1;flex:1;min-width:0;font-family:var(--body);font-size:var(--fs-sm);
  font-weight:600;letter-spacing:.01em;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.spine-n{position:relative;z-index:1;flex:0 0 auto;font-family:var(--body);font-weight:800;font-size:var(--fs-sm);color:var(--gold)}
.spine-n em{font-style:normal;font-weight:600;font-size:var(--fs-3xs);letter-spacing:.08em;text-transform:uppercase;color:var(--dim);margin-left:2px}
.fifths-note{position:relative;z-index:1;flex:0 0 auto;width:26px;font-family:var(--body);font-weight:800;font-size:var(--fs-sm);color:var(--gold);text-align:center}

@media(max-width:860px){
  .net-wrap{flex-direction:column}
  .net-detail{width:auto;max-height:none}
  /* .stat-strip needs no override here: the auto-fit track in the analysis-surface
     block below reflows it by available width at every breakpoint. */
  /* fullscreen stacks on phones — let it scroll, give the graph a tall-but-bounded slice */
  .viz-card.is-fullscreen{overflow:auto}
  .viz-card.is-fullscreen #net.viz-host{height:62vh}
}
/* (shows are full-width rows now; only the poster thumb shrinks on small screens) */
@media(max-width:760px){
  .recap-grid{grid-template-columns:1fr 1fr}
  .results{grid-template-columns:1fr}
  .navlinks{display:none}
  .sr-spark{display:none}   /* the silhouette needs width — drop it before rows get cramped */
  /* Stack instead of sharing a row: a legend or stat-strip sibling has no flex-grow, so
     without this the subtext above it shrinks to share the row instead of taking it.
     display:block turns every child (the text column, and the legend or stat-strip when
     present) into a normal full-width block, stacked top to bottom. */
  .viz-top{display:block}
}
@media(max-width:680px){.setlist{column-count:1}}
@media(max-width:560px){
  .show-row{gap:14px;padding:14px 16px}
  .show-poster{width:76px}
  .sr-go{display:none}
}

/* ============================================================================
   UI/UX POLISH LAYER — Spotify/UMG-tier pass (Yahn Dawn '26).
   Additive: each rule targets new markup or refines an existing state without
   touching the data/query/D3 layout. Grouped by concern. Loaded last so its
   responsive refinements take precedence over the base media queries above.
   ============================================================================ */

/* --- tokens: focus + a tracking ladder (two eyebrow trackings, not five) --- */
:root{ --tk-micro:.26em; --tk-eyebrow:.18em; --tk-ui:.08em; }

/* --- unified keyboard focus (previously only .subnav a). :focus-visible =
       keyboard/AT only, so mouse users keep the clean look. --- */
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,
[tabindex]:focus-visible,.segbtn:focus-visible,.chip:focus-visible,
.show-card:focus-visible,.res:focus-visible,.ql-item:focus-visible,
.sug-item:focus-visible,.ctrl input:focus-visible,.ctrl.check input:focus-visible{
  outline:2px solid var(--gold);outline-offset:2px}
.searchbox button:focus-visible,.segbtn.active:focus-visible{outline-offset:3px}
/* the inputs set outline:0 for mouse — restore a ring only for keyboard focus */
.searchbox input:focus-visible,.ctrl-search:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* --- skip-to-content (first focusable element on every page) --- */
.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--gold);color:#0b1430;
  font-family:var(--body);font-weight:700;font-size:var(--fs-sm);letter-spacing:.1em;
  text-transform:uppercase;padding:11px 16px}
.skip:focus{left:8px;top:8px}

/* --- screen-reader-only utility (chart fallbacks, labels) --- */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* --- shared micro-transitions (scoped to interactive chrome ONLY; never `*` —
       a global transition would repaint the d3 SVG on every simulation tick) --- */
a,button,.chip,.segbtn,.show-card,.res,.nd-list li,.ql-item,.sug-item,.md-open,
.nd-spotify,.nd-link,.backlink,.sl-row.has .sl-song,.to-top,.tag-share,.foot-nav a,.navlinks a{
  transition:color .15s ease,background-color .15s ease,border-color .15s ease,
    transform .15s ease,box-shadow .15s ease}
.searchbox button:active{transform:translateY(1px)}
.chip:active,.segbtn:active{transform:scale(.97)}
.chip:hover{background:rgba(230,200,79,.08)}

/* --- mobile nav: no-build checkbox + label disclosure ("burger"). The checkbox is
       display:none on desktop (no stray tab stop) and made sr-only-but-focusable only at the
       mobile breakpoint, where the burger is actually used. --- */
.nav-toggle{display:none}
.nav-burger{display:none}
.nav-burger span{display:block;width:22px;height:2px;background:var(--gold);margin:5px 0;
  transition:transform .2s ease,opacity .2s ease}

/* --- a Rye second voice: hero kicker is already styled (.kicker); add a
       section eyebrow so Big-Shoulders isn't the only family doing work --- */
.sec-head .kick{display:block;font-family:var(--accent);font-size:var(--fs-sm);
  letter-spacing:.03em;color:var(--teal);margin-bottom:3px}
.show-hero .kicker{margin-bottom:8px}

/* --- recap: kill the blurred gradient blob (the one AI-default tell), swap in
       an oversized ghost numeral + a flat dawn rule, and break the 4-up grid on
       purpose into a 4-2 composition with one focal tile --- */
.stat .blob{display:none}
.recap-grid .stat{position:relative;overflow:hidden}
/* one dominant tile + three — gapless single row on wide screens only. MUST be min-width-scoped:
   an unconditional rule here would beat the ≤760 two-column base rule (media adds no specificity,
   later source wins) and cram 4 columns onto phones, clipping the numerals. */
@media(min-width:761px){
  .recap-grid{grid-template-columns:2fr 1fr 1fr 1fr}
  .recap-grid .stat.s1 .big{font-size:clamp(46px,7.2vw,82px)}
}
.recap-grid .stat::after{content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background:var(--dawn);opacity:.6;z-index:1}
.recap-grid .stat.s2::after{background:var(--teal)}
.recap-grid .stat.s3::after{background:var(--green)}
.recap-grid .stat.s4::after{background:var(--gold)}
.recap-grid .stat .label,.recap-grid .stat .big,.recap-grid .stat .sub{position:relative;z-index:2}

/* --- loading: dawn-sheen skeletons (NOT a gray pulse), a viz spinner reusing
       the swirl idiom, and a thin dawn progress bar under .dawn-bar --- */
@keyframes dawn-sheen{0%{background-position:-160% 0}100%{background-position:260% 0}}
.skel{position:relative;overflow:hidden;background:var(--bg2);border:1px solid var(--line)}
.skel::after{content:"";position:absolute;inset:0;background-size:200% 100%;
  background-image:linear-gradient(90deg,transparent,rgba(61,151,168,.10),
    rgba(230,200,79,.16),rgba(127,174,107,.10),transparent);
  animation:dawn-sheen 1.5s ease-in-out infinite}
.skel-line{height:13px;margin:8px 0;border-radius:2px}
.skel-card{min-height:96px}
.viz-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;min-height:300px;color:var(--mut);font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.06em}
.viz-loading .viz-spin{color:var(--teal);animation:swirl-spin 1.1s linear infinite;transform-origin:50% 50%}
@keyframes swirl-spin{to{transform:rotate(360deg)}}
.db-progress{position:fixed;top:0;left:0;height:2px;width:0;z-index:61;background:var(--dawn);
  opacity:0;transition:width .25s ease,opacity .4s ease;pointer-events:none}
body.db-loading .db-progress{opacity:1}
/* recap numerals shimmer while the DB loads — no layout shift, per-tile colour restored on settle */
body.db-loading .recap-grid .big{color:transparent;background:linear-gradient(90deg,var(--line),var(--mut),var(--line));
  background-size:200% 100%;-webkit-background-clip:text;background-clip:text;animation:dawn-sheen 1.5s ease-in-out infinite}

/* --- scroll-reveal stagger (motion-pref gated; guarded so a load failure can
       never strand content at opacity:0) --- */
@media (prefers-reduced-motion: no-preference){
  .reveal{opacity:0;transform:translateY(14px);
    transition:opacity .5s ease,transform .5s cubic-bezier(.2,.7,.3,1)}
  .reveal.in{opacity:1;transform:none}
}

/* --- command palette (⌘K / "/") — indigo card, dawn top edge, gold caret --- */
.cmdk{position:fixed;inset:0;z-index:200;display:flex;justify-content:center;align-items:flex-start;
  padding-top:14vh;background:rgba(6,11,28,.66);opacity:0;transition:opacity .18s ease}
.cmdk.on{opacity:1}
.cmdk[hidden]{display:none}
.cmdk-card{width:min(620px,92vw);background:#0a1330;border:1.5px solid var(--gold2);
  box-shadow:0 24px 60px rgba(0,0,0,.6);transform:translateY(-8px) scale(.985);
  transition:transform .18s cubic-bezier(.2,.7,.3,1)}
.cmdk.on .cmdk-card{transform:none}
.cmdk-card::before{content:"";display:block;height:3px;background:var(--dawn)}
.cmdk-row{display:flex;align-items:center;gap:12px;padding:16px 18px}
.cmdk-swirl{color:var(--gold);font-size:var(--fs-lg);line-height:1}
.cmdk-input{flex:1;background:transparent;border:0;outline:0;color:var(--ink);
  font-family:var(--body);font-size:var(--fs-lg);letter-spacing:.02em}
.cmdk-input::placeholder{color:var(--dim)}
.cmdk-esc{font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.1em;color:var(--dim);
  border:1px solid var(--line);padding:3px 7px;text-transform:uppercase}
.cmdk-foot{display:flex;gap:18px;padding:9px 18px;border-top:1px solid var(--line);
  font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
/* ⌘K discoverability chip in the nav */
.kbd-hint{display:inline-flex;align-items:center;gap:6px;background:transparent;border:1px solid var(--line);
  color:var(--mut);font-family:var(--body);font-weight:600;font-size:var(--fs-2xs);letter-spacing:.1em;
  text-transform:uppercase;padding:6px 10px;cursor:pointer}
.kbd-hint:hover{border-color:var(--gold);color:var(--gold)}
.kbd-hint kbd{font-family:var(--body);font-size:var(--fs-3xs);color:var(--dim);border:1px solid var(--line);padding:1px 5px}

/* --- toast (copy-link / share feedback; reused on the playlist builder) --- */
.toast{position:fixed;left:50%;bottom:30px;transform:translate(-50%,12px);z-index:210;
  max-width:min(440px,calc(100vw - 32px));overflow-wrap:anywhere;
  background:#0a1330;border:1px solid var(--gold2);color:var(--ink);
  font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.04em;padding:11px 18px 11px 21px;
  box-shadow:0 10px 30px rgba(0,0,0,.5);opacity:0;pointer-events:none;
  transition:opacity .2s ease,transform .2s ease}
.toast::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--dawn)}
.toast.on{opacity:1;transform:translate(-50%,0)}
.tag-share{cursor:pointer;background:transparent;color:var(--gold);border:1px solid var(--gold2);
  font-family:var(--body);font-size:var(--fs-3xs);letter-spacing:.1em;text-transform:uppercase;padding:3px 9px}
.tag-share:hover{background:rgba(230,200,79,.08)}

/* --- back-to-top swirl button --- */
.to-top{position:fixed;right:22px;bottom:22px;z-index:70;width:46px;height:46px;
  background:var(--panel);border:1px solid var(--line);color:var(--gold);
  font-family:var(--body);font-size:var(--fs-lg);line-height:1;cursor:pointer;
  opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity .2s ease,transform .2s ease,border-color .15s ease,box-shadow .15s ease}
.to-top.show{opacity:1;transform:none;pointer-events:auto}
.to-top:hover{border-color:var(--gold);box-shadow:0 0 0 1px var(--gold) inset}

/* --- real footer (replaces the lone word "swirls") --- */
.site-foot{margin-top:36px;border-top:1px solid var(--line);padding:46px 0 78px;
  position:relative;z-index:2;color:var(--mut)}
.foot-grid{display:grid;grid-template-columns:1.5fr .8fr 1fr;gap:34px;align-items:start;
  max-width:min(94vw,1680px);margin:0 auto;padding:0 28px}
.foot-logo{font-family:var(--h1);font-size:var(--fs-base);letter-spacing:.06em;color:var(--cream)}
.foot-logo b{font-weight:400;margin-left:3px;background:var(--dawn-spectrum);-webkit-background-clip:text;background-clip:text;color:transparent}
.foot-tag{font-family:var(--body);font-size:var(--fs-sm);line-height:1.5;color:var(--mut);max-width:36ch;margin:11px 0 12px}
.foot-curr{font-family:var(--accent);font-size:var(--fs-xs);letter-spacing:.03em;color:var(--gold)}
/* reset the generic `nav{}` bleed (border-bottom + 18/28 padding + align-items:center)
   onto this <nav>, so the footer columns all share one left edge */
.foot-nav{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:0;border:0}
.foot-nav a{font-family:var(--body);font-weight:600;font-size:var(--fs-xs);letter-spacing:.08em;
  color:var(--mut);text-decoration:none}
.foot-nav a:hover{color:var(--gold)}
.foot-stats{display:flex;flex-direction:column;gap:8px;font-family:var(--body);font-size:var(--fs-sm);
  letter-spacing:.04em;color:var(--mut)}
.foot-stats b{color:var(--gold);font-weight:800;font-size:var(--fs-xl);margin-right:7px}
.foot-base{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:34px;
  font-family:var(--accent);font-size:var(--fs-xs);letter-spacing:.04em;color:var(--dim);text-align:center;
  padding:0 28px}
.foot-base .lemon{width:13px;height:13px;opacity:.8}


/* ============================== RESPONSIVE ================================ */
@media(max-width:760px){
  nav{flex-wrap:wrap;position:relative}
  .nav-toggle{display:block;position:absolute;width:1px;height:1px;opacity:0;clip:rect(0 0 0 0)}
  .nav-burger{display:block;cursor:pointer;padding:8px;margin:-8px -4px}
  /* the operable control is the clipped checkbox — show its keyboard focus on the visible burger */
  .nav-toggle:focus-visible ~ .nav-burger{outline:2px solid var(--gold);outline-offset:2px}
  .navlinks{flex-basis:100%;flex-direction:column;gap:0;margin-top:12px;border-top:1px solid var(--line)}
  .nav-toggle:checked ~ .navlinks{display:flex}
  .navlinks a{padding:15px 4px;border-bottom:1px solid var(--line);font-size:var(--fs-md);letter-spacing:.1em;text-transform:uppercase}
  .nav-toggle:checked ~ .nav-burger span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  .nav-toggle:checked ~ .nav-burger span:nth-child(2){opacity:0}
  .nav-toggle:checked ~ .nav-burger span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
  .kbd-hint{display:none}                 /* ⌘K chip is desktop-only; "/" still works */
  .chip,.segbtn{min-height:40px;display:inline-flex;align-items:center}
  .res .links a{padding:6px 0;display:inline-block;min-height:34px}
  .res .links{gap:14px}
  .ql-item{min-height:44px}
  .ctrl-btn,.ctrl-search{min-height:42px}
  .setlist{column-count:1}
  .foot-grid{grid-template-columns:1fr;gap:24px}
}
@media(max-width:560px){
  .wrap{padding:0 16px}
  .viz-card{padding:16px 14px}
  .sec{padding:38px 0}
  .foot-grid,.foot-base{padding:0 16px}
  .viz-controls{gap:10px}
  .ctrl-note{margin-left:0;width:100%}
  .ctrl-search{min-width:0;width:100%}
  .seg{justify-content:flex-start}
  .recap-grid{grid-template-columns:1fr 1fr}
  .recap-grid .stat.s1 .big{font-size:clamp(40px,12vw,54px)}
}
@media(max-width:440px){
  .recap-grid{grid-template-columns:1fr}
}

/* ============================================================================
   ARTIST + RECORD pages (artists/samples list + artist/sample detail).
   Reuse the show shell almost wholesale (.show-row, .recap-grid, .spine-*, .results,
   .net-wrap, .viz-card, .chart-foot, .nd-*). These few rules cover the only new bits:
   the branded "sigil" (a poster stand-in — artists/records have no cover art) and a
   couple of layout touch-ups.
   ============================================================================ */
/* sigil = the .show-poster placeholder pressed into service as a cover stand-in, with a
   monogram glyph instead of a year. Bigger, tracked glyph so 1–2 letters read as a mark. */
.sigil .sg-mono{letter-spacing:.05em}
.sh-poster .sigil .sg-mono{font-size:clamp(34px,6vw,44px)}
/* detail hero meta: stack the descriptor line and the Spotify button instead of inlining them */
#a-where,#s-where{display:flex;flex-direction:column;align-items:flex-start;gap:2px}
#a-where .nd-spotify,#s-where .nd-spotify,#a-where .nd-links,#s-where .nd-links{align-self:flex-start}
/* a spine row can be a link (artist→record, record→track) as well as a button */
a.spine-row{text-decoration:none}

/* --- reduced motion: honor the OS flag everywhere --- */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important}
  .show-card:hover{transform:none}
}

/* ============================================================================
   SUBMIT page — visitor-contributed sample identifications.
   Extends the existing form vocabulary (.searchbox edge-glow, .ctrl-* controls,
   hairline --line borders, sharp corners) rather than introducing a second one.
   ============================================================================ */
.subform{max-width:720px}
.sf-row{display:grid;gap:18px;margin-bottom:18px}
@media(min-width:641px){ .sf-row.two{grid-template-columns:1fr 1fr} }

.sf-field{display:flex;flex-direction:column;gap:7px;min-width:0}
.sf-field>label{font-family:var(--body);font-weight:600;font-size:var(--fs-3xs);
  letter-spacing:var(--tk-eyebrow);text-transform:uppercase;color:var(--mut)}
.sf-field>label .opt{color:var(--dim);font-weight:400;letter-spacing:.06em;text-transform:none}
.sf-hint{font-size:var(--fs-2xs);color:var(--dim);letter-spacing:.02em;line-height:1.45}

.sf-input{width:100%;background:rgba(18,32,60,.7);border:1px solid var(--line);color:var(--ink);
  font-family:var(--body);font-size:var(--fs-sm);letter-spacing:.02em;padding:12px 14px;outline:0}
.sf-input::placeholder{color:var(--dim)}
.sf-input:focus{border-color:var(--gold)}
textarea.sf-input{resize:vertical;min-height:86px;line-height:1.5}

/* Invalid state is border + icon-free text, never colour alone (WCAG 1.4.1). The message is
   wired to the input with aria-describedby so a screen reader reads it on focus. */
.sf-input[aria-invalid="true"]{border-color:var(--ch-root)}
.sf-err{font-size:var(--fs-2xs);color:#f5a3ae;letter-spacing:.02em;min-height:0}
.sf-err:empty{display:none}
.sf-err::before{content:"↳ ";color:var(--ch-root)}

/* Honeypot. Off-screen rather than display:none — a bot that filters on display:none would skip
   it, and the point is that it gets filled. Never focusable, never announced. */
.sf-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.sf-actions{display:flex;flex-wrap:wrap;align-items:center;gap:16px;margin-top:26px;
  padding-top:22px;border-top:1px solid var(--line)}
.sf-submit{font-family:var(--body);font-weight:700;font-size:var(--fs-sm);letter-spacing:.14em;
  text-transform:uppercase;background:var(--gold);color:#0b1430;border:0;padding:14px 30px;cursor:pointer}
.sf-submit:hover{background:var(--gold2)}
.sf-submit[disabled]{opacity:.5;cursor:progress}
.sf-note{font-size:var(--fs-2xs);color:var(--dim);letter-spacing:.02em;max-width:36ch;line-height:1.45}

/* Status region. One element carries loading / server-error / success, so aria-live announces
   exactly one change per submit instead of three competing regions. */
.sf-status{margin-top:20px;font-size:var(--fs-xs);letter-spacing:.03em;line-height:1.5}
.sf-status:empty{display:none}
.sf-status.work{color:var(--mut)}
.sf-status.bad{color:#f5a3ae;border-left:0;background:rgba(226,59,84,.08);padding:12px 14px}
.sf-status.good{color:var(--ink);background:rgba(127,174,107,.1);padding:14px 16px}
.sf-status .sf-id{color:var(--dim);font-size:var(--fs-2xs);display:block;margin-top:6px;letter-spacing:.06em}

/* Post-success panel replaces the form; "submit another" restores it. */
.sf-again{margin-top:14px;background:transparent;border:1px solid var(--line);color:var(--mut);
  cursor:pointer;font-family:var(--body);font-size:var(--fs-2xs);font-weight:700;
  letter-spacing:.12em;text-transform:uppercase;padding:9px 16px}
.sf-again:hover{color:var(--gold);border-color:var(--gold)}

/* ============================================================================
   ANALYSIS SURFACE — 2026-07-31 overhaul
   Structural, not a repaint: the Yahn Dawn palette and the Roboto/Monoton
   pairing are unchanged. What changes is hierarchy. The analysis pages had
   grown from four views to fourteen while keeping a layout designed for one
   chart per page, so the additions here are a tab bar that survives fourteen
   entries, a stat strip that reads before the chart does, and a side panel for
   the enumerations that several of these analyses now lead with.
   ========================================================================= */

/* --- tab bar at fourteen entries -------------------------------------------
   Every analysis stays visible: the bar wraps rather than scrolling, because a
   tab you have to discover by dragging is a tab most readers never find. The
   cost of wrapping is vertical space, so the type is tightened here (smaller
   size, less tracking, tighter padding) to buy the rows back. Fourteen entries
   land in two rows at desktop width.
   Deliberately NOT horizontally scrollable. If this grows past ~18 entries,
   shorten the labels before reaching for a scroller. */
.subnav.anav{flex-wrap:wrap;row-gap:5px;column-gap:4px;padding:10px 0}
.subnav.anav a{font-size:var(--fs-3xs);letter-spacing:.055em;padding:6px 9px;
  white-space:nowrap;flex:0 0 auto}
/* The bar is now two rows tall and sticky, so it must not dominate a short
   viewport: below 900px it releases and scrolls away with the page. */
@media(max-height:900px){
  .subnav.anav{position:static}
}

/* --- the enumerated rank list ----------------------------------------------
   Several of these analyses answer with a SHORT list rather than a magnitude
   (every cross-night repeat, the longest hops, the strongest pairings), and
   naming the members is what makes the headline number checkable. This is that
   list: an ordered list whose numerals are part of the typography rather than
   browser default markers. */
.rank-list{list-style:none;counter-reset:rank;margin:0;padding:0;
  display:grid;grid-template-columns:1fr;gap:1px;background:var(--line);border:1px solid var(--line)}
.rank-list li{counter-increment:rank;background:var(--panel);
  display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:0 14px;
  padding:12px 16px}
.rank-list li::before{content:counter(rank);font-family:var(--body);font-weight:900;
  font-size:var(--fs-2xs);letter-spacing:.04em;color:var(--dim);
  font-variant-numeric:tabular-nums;min-width:1.4em}
.rank-list li b{font-family:var(--body);font-weight:800;font-size:var(--fs-sm);
  color:var(--ink);letter-spacing:.02em}
.rank-list li span{grid-column:2;font-family:var(--body);font-size:var(--fs-3xs);
  letter-spacing:.06em;color:var(--mut);text-transform:uppercase;line-height:1.4}
.rank-list li em{grid-row:1 / span 2;grid-column:3;font-style:normal;
  font-family:var(--body);font-weight:900;font-size:var(--fs-sm);color:var(--gold);
  font-variant-numeric:tabular-nums;align-self:center}
/* Variant for lists whose third column is the emphasis, not the first. */
.rank-list.rank-3 li b{color:var(--gold)}
.rank-list.rank-3 li em{color:var(--ink);font-weight:700}

/* --- side panel ------------------------------------------------------------
   Sits under its chart and carries the enumeration. Deliberately NOT a card:
   two stacked panels would read as two charts of equal weight, and this is
   supporting evidence for the one above it. */
.side-panel{margin-top:22px}
.side-panel h3{font-family:var(--accent);font-size:var(--fs-2xs);font-weight:700;
  letter-spacing:.28em;text-transform:uppercase;color:var(--dim);margin:0 0 12px}

/* --- inline bits in the control row and prose ------------------------------ */
.stat-inline{display:flex;gap:18px;margin-left:auto;font-family:var(--body);
  font-size:var(--fs-3xs);letter-spacing:.08em;text-transform:uppercase;color:var(--mut)}
.stat-inline b{color:var(--gold);font-weight:800;font-variant-numeric:tabular-nums}
.note-inline{color:var(--dim)}
.note-inline:empty{display:none}

/* The run-rotation chart is a row per run, so its height is driven by the data
   rather than by an aspect ratio; give it room instead of a fixed min. */
.viz-host-tall{min-height:520px}

/* --- stat strip: let it hold three or four at tablet/desktop widths ---------- */
.stat-strip{grid-template-columns:repeat(auto-fit,minmax(168px,1fr))}

@media(max-width:760px){
  .rank-list li{grid-template-columns:auto 1fr;gap:0 12px}
  .rank-list li em{grid-row:auto;grid-column:2;align-self:start}
  .stat-inline{margin-left:0;width:100%}
}

/* Phone: hold a deliberate 2-up instead of letting auto-fit's 168px floor collapse to a
   single column around 390px. A tuned 2-up reads as intentional; a collapsed 1-up reads
   like the layout gave up. Padding and gap tighten to give the cards a bit more room. */
@media(max-width:560px){
  .stat-strip{grid-template-columns:1fr 1fr;gap:10px}
  .stat-strip .stat{padding:18px 10px;min-height:128px}
}
