/* =============================================================================================
   app.css: every style in the app. Vignelli transit-map look (approved 2026-10-31).
   Black backdrop (light mode optional), Helvetica, bold line colours. All motion is slow
   ease-in-out, plays once, never loops, and is switched off for people who ask for reduced motion.

   QUICK ADJUSTMENTS: most of the look is set by the variables in :root just below:
     colours (--bg, --ink, the transit colours), the font (--font), the motion curve (--ease)
     and the side margins (--pad). Change them there and the whole app follows.
   Sizes elsewhere are in px; durations like .6s are how long a fade or slide takes.

   Sections, in order:
     fonts / variables / basics
     header ............. .sign, .word, .back, .mode, .menu
     menu panel ......... .panel, .stations
     shared pieces ...... .lbl, .bul (coloured rating circles), .frame (cover frames), .msg
     home ............... .headline, .searchbox, .recent, .how (install-guide steps)
     search results ..... .res
     album page ......... .hero, .title, .rbox (rating), .critics, .discogs, .blurb, .quote, .tbtn (tracks)
     best pressing ...... .picks, .ppick (one card per list), .why (its reasons), .plink
     artist page ........ .ahead, .line (discography line), .branch (singles/EPs/live...)
     install guide ...... .guide, .btn
     about & contact .... .prose, .sub, .contact, .field (contact.html, contact-sent.html)
     footer ............. .foot
     screen change ...... .wipe
     motion ............. .anim ... and @keyframes
     home artwork ....... .artframe, .art (the record drawn by art.js)
   Class names are set in app.js (the screen templates) and index.html.
   ============================================================================================= */

/* Fallback copy of Helvetica (TeX Gyre Heros, free licence) for phones without it, served
   from web/fonts/. Regular and bold only. */
@font-face {
  font-family: "TeX Gyre Heros";
  src: local("TeX Gyre Heros"), local("TeXGyreHeros-Regular"), url("fonts/heros-regular.woff2") format("woff2");
  font-weight: 400; font-display: swap;
}
@font-face {
  font-family: "TeX Gyre Heros";
  src: local("TeX Gyre Heros Bold"), local("TeXGyreHeros-Bold"), url("fonts/heros-bold.woff2") format("woff2");
  font-weight: 700; font-display: swap;
}

/* Settings for the whole app (dark mode, the default). TWEAK HERE:
     --bg page background   --ink main text   --mute quiet text   --soft body text
     --rule thin dividers   --frame cover/art frames   --none "no reviews" grey
     --red ... --grey   the transit colours (keep in step with PALETTE in colors.js)
     --f1 --f2 --f3     three random bright colours, reset on every screen by app.js
     --font             typeface list, first one the phone has wins
     --ease             motion curve for every fade/slide (slow start and end)
     --pad              left and right margin of every screen */
:root {
  --bg: #000; --ink: #fff; --mute: #9a9a9a; --soft: #d6d6d6; --rule: #2a2a2a; --frame: #5a5a5a; --none: #808183;
  --red: #EE352E; --orange: #FF6319; --yellow: #FCCC0A; --lime: #6CBE45; --green: #00933C;
  --sky: #00A1DE; --blue: #0039A6; --purple: #B933AD; --pink: #E6007E; --brown: #996633; --grey: #808183;
  --f1: var(--red); --f2: var(--yellow); --f3: var(--sky);
  --font: "Helvetica Neue", Helvetica, "TeX Gyre Heros", Arial, sans-serif;
  --ease: cubic-bezier(.65, 0, .35, 1);
  --pad: 18px;
  color-scheme: dark;
}
/* Light mode: only the colours that differ (warm paper background, dark ink). */
:root[data-theme="light"] {
  --bg: #F2EDE3; --ink: #1D1B18; --mute: #6E685E; --soft: #3a3631; --rule: #DCD4C5; --frame: #B8AF9F;
  color-scheme: light;
}

/* Basics: reset browser defaults, colours fade smoothly when switching light/dark. */
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--bg); -webkit-text-size-adjust: 100%; }
body {
  background: var(--bg); color: var(--ink); font-family: var(--font); line-height: 1.3;
  transition: background-color .6s var(--ease), color .6s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; text-align: left; }
/* Keyboard focus ring. .sr = hidden on screen but read by screen readers. */
:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Page column: max 560px wide, centred, padded clear of the phone's notch. */
.app {
  max-width: 560px; margin: 0 auto; min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column; position: relative;
  padding: max(14px, env(safe-area-inset-top)) var(--pad) 0;
}
main { flex: 1; outline: none; padding-bottom: 28px; }
#view:focus, #view:focus-visible { outline: none; }

/* ---------- header: thin rule above bold type, like a subway sign ---------- */
.sign {
  border-top: 3px solid var(--ink); padding-top: 8px; min-height: 44px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  position: relative; z-index: 6; background: var(--bg); transition: background-color .6s var(--ease), border-color .6s var(--ease);
}
/* Left side: the app name (.word) on home, or a back link (.back) on other screens.
   TWEAK: .word font-size = size of "Crate Digger's Companion". */
.sign-left { min-width: 0; flex: 1; }
.word { font-weight: 700; font-size: 21px; letter-spacing: -.025em; line-height: 1.05; text-decoration: none; display: block; }
.back {
  display: flex; text-decoration: none; align-items: center; gap: 6px; font-weight: 700; font-size: 17px; max-width: 100%;
  min-height: 40px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.back span { overflow: hidden; text-overflow: ellipsis; }
.back::before { content: "‹"; font-size: 26px; line-height: 1; margin-top: -3px; }
/* Right side: light/dark switch (.mode, a half-filled circle) and the menu button (.menu,
   three coloured dots on a line, which spread apart when the menu is open). */
.tools { display: flex; align-items: center; gap: 6px; flex: none; }
.mode, .menu { width: 44px; height: 44px; display: grid; place-items: center; }
/* Home button: a small house, shown on every screen except home. */
.homebtn { width: 44px; height: 44px; display: grid; place-items: center; }
.homebtn[hidden] { display: none; }
.homebtn svg { width: 22px; height: 22px; fill: none; stroke: var(--ink); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; transition: stroke .6s var(--ease); }
.mode::before {
  content: ""; width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--ink);
  background: linear-gradient(90deg, var(--ink) 50%, transparent 50%); transition: transform .6s var(--ease);
}
:root[data-theme="light"] .mode::before { transform: rotate(180deg); }
.menu { display: flex; align-items: center; justify-content: center; gap: 6px; position: relative; width: 56px; }
.menu::before { content: ""; position: absolute; left: 11px; right: 11px; top: 50%; height: 2px; margin-top: -1px; background: var(--ink); }
.menu i { position: relative; width: 10px; height: 10px; border-radius: 50%; display: block; transition: transform .5s var(--ease), background-color .6s var(--ease); }
.menu i:nth-child(1) { background: var(--f1); } .menu i:nth-child(2) { background: var(--f2); } .menu i:nth-child(3) { background: var(--f3); }
.menu[aria-expanded="true"] i:nth-child(1) { transform: translateX(-4px); }
.menu[aria-expanded="true"] i:nth-child(3) { transform: translateX(4px); }

/* ---------- menu: a vertical transit line of tools ---------- */
.panel {
  position: absolute; left: 0; right: 0; top: calc(max(14px, env(safe-area-inset-top)) + 55px); bottom: 0; z-index: 5;
  background: var(--bg); padding: 4px var(--pad) 0;
  clip-path: inset(0 0 100% 0); opacity: 0; pointer-events: none;
  transition: clip-path .6s var(--ease), opacity .5s var(--ease), background-color .6s var(--ease);
}
.panel.open { clip-path: inset(0 0 0 0); opacity: 1; pointer-events: auto; }
/* Menu items as stations on a vertical line: --c is each item's line colour, set by app.js;
   .cur = the screen you are on (filled station). */
.stations { list-style: none; padding-left: 30px; margin-top: 8px; }
.stations li { position: relative; }
.stations li::after { content: ""; position: absolute; left: -23px; top: 0; bottom: 0; width: 6px; background: var(--c); }
.stations li:first-child::after { top: 50%; } .stations li:last-child::after { bottom: 50%; }
.stations li:only-child::after { display: none; }
.stations li::before {
  content: ""; position: absolute; z-index: 1; left: -30px; top: 50%; margin-top: -10px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--bg); border: 4px solid var(--ink);
}
.stations li.cur::before { background: var(--c); }
.stations a, .stations .soon {
  display: block; padding: 12px 0; font-size: 20px; font-weight: 700; letter-spacing: -.02em; text-decoration: none; min-height: 44px;
}
.stations small { display: block; font-size: 13px; font-weight: 400; color: var(--mute); letter-spacing: 0; margin-top: 2px; }
.stations .soon { color: var(--mute); }

/* ---------- shared pieces ---------- */
.lbl {
  font-size: 14px; font-weight: 700; letter-spacing: -.005em; color: var(--mute);
  margin: 22px 0 6px; display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.lbl .key { font-size: 12px; font-weight: 400; display: flex; align-items: center; gap: 5px; }
/* Rating circles ("bullets" like subway route signs). --c fill colour, --t text colour.
   .none = no reviews (grey ring), .ring = Discogs, .xs = tiny legend dot. */
.bul {
  border-radius: 50%; display: grid; place-items: center; font-weight: 700; flex: none; letter-spacing: -.02em;
  background: var(--c); color: var(--t, #fff); width: 40px; height: 40px; font-size: 15px;
}
.bul.none { background: transparent; border: 3px solid var(--none); color: var(--mute); font-size: 11px; font-weight: 700; letter-spacing: 0; }
.bul.ring { background: var(--bg); border: 3px solid var(--grey); color: var(--ink); }
.bul.xs { width: 14px; height: 14px; font-size: 0; }
.bul.xs.none { border-width: 2px; }
.s { font-size: 13px; color: var(--mute); margin-top: 2px; }
.t { font-weight: 700; font-size: 16px; line-height: 1.15; display: block; min-width: 0; }
/* Long words: titles and names never split in the middle of a word. If a word is too wide for
   its space, fit.js makes that text a little smaller instead. Only if it still doesn't fit at
   the smallest allowed size does it get .squeeze: a proper hyphenated break ("Whitta-ker"). */
.title, .headline, .t, .artist .nm { overflow-wrap: normal; word-break: normal; }
.squeeze { overflow-wrap: anywhere !important; hyphens: auto; -webkit-hyphens: auto; }
.grow { flex: 1; min-width: 0; }
/* Cover frames: thin border with a gap, cover fades in once loaded (.in).
   .nocover = drawn record in the album colour when there is no cover image. */
.frame { flex: none; border: 1px solid var(--frame); padding: 3px; background: var(--bg); display: block; position: relative; }
.frame img { display: block; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .5s var(--ease); }
.frame img.in { opacity: 1; }
.frame .nocover {
  position: absolute; inset: 3px; display: grid; place-items: center; background: #111;
}
:root[data-theme="light"] .frame .nocover { background: #e4ddcf; }
.nocover::before { content: ""; width: 62%; aspect-ratio: 1; border-radius: 50%; background: radial-gradient(circle, var(--c) 0 17%, var(--bg) 17% 21%, #222 21% 100%); }
:root[data-theme="light"] .nocover::before { background: radial-gradient(circle, var(--c) 0 17%, var(--bg) 17% 21%, #2a2723 21% 100%); }
.link { color: var(--mute); text-decoration: underline; text-underline-offset: 3px; }
.link:hover { color: var(--ink); }
.msg { color: var(--mute); font-size: 15px; line-height: 1.45; margin-top: 18px; }
.msg b { color: var(--ink); }

/* ---------- home ---------- */
/* Home headline "Find Your Next Favourite Album". TWEAK: font-size (bigger on wide screens,
   see the @media rule near the end). */
.headline { font-size: 32px; font-weight: 700; letter-spacing: -.03em; line-height: 1.02; margin-top: 22px; }
/* Search box: white bar with dark text (inverted in light mode). TWEAK: height, input font size. */
.searchbox {
  margin-top: 14px; background: var(--ink); color: var(--bg); height: 54px; display: flex; align-items: center;
  transition: background-color .6s var(--ease);
}
.searchbox input {
  flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; background: transparent; color: var(--bg);
  font: 700 19px var(--font); padding: 0 14px; letter-spacing: -.01em;
}
.searchbox input::placeholder { color: var(--mute); font-weight: 400; opacity: 1; }
.searchbox input::-webkit-search-cancel-button { display: none; }
.searchbox .clear { width: 48px; height: 100%; display: grid; place-items: center; color: var(--bg); font-size: 26px; font-weight: 400; }
.searchbox .clear[hidden] { display: none; }
/* Search button (magnifier) at the right end of the box. */
.searchbox .go { width: 52px; height: 100%; display: grid; place-items: center; flex: none; }
.searchbox .go svg { width: 24px; height: 24px; fill: none; stroke: var(--bg); stroke-width: 2.6; stroke-linecap: round; }
.hint { font-size: 13px; color: var(--mute); margin-top: 8px; }

/* Numbered steps on a short transit line (used by the install guide). */
.how { list-style: none; padding-left: 38px; margin-top: 4px; }
.how li { position: relative; padding: 10px 0 12px; }
.how li::after { content: ""; position: absolute; left: -26px; top: 0; bottom: 0; width: 6px; background: var(--c); }
.how li:first-child::after { top: 22px; } .how li:last-child::after { bottom: calc(100% - 22px); }
.how .st {
  position: absolute; z-index: 1; left: -35px; top: 10px; width: 24px; height: 24px; border-radius: 50%;
  background: var(--bg); border: 4px solid var(--ink); display: grid; place-items: center;
  font-size: 11px; font-weight: 700;
}
.how h3 { font-size: 17px; font-weight: 700; letter-spacing: -.01em; line-height: 24px; }
.how p { font-size: 14px; color: var(--soft); line-height: 1.4; margin-top: 2px; }
.how .ex { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13px; color: var(--mute); }
.how .ex .bul { width: 32px; height: 32px; font-size: 12px; }

/* Recently viewed: a row of small covers that scrolls sideways. TWEAK: width/height 84px. */
.recent { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; margin-right: calc(-1 * var(--pad)); padding-right: var(--pad); }
.recent::-webkit-scrollbar { display: none; }
.recent a { flex: none; width: 84px; text-decoration: none; }
.recent .frame { width: 84px; height: 84px; }
.recent .t { font-size: 12px; margin-top: 5px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* ---------- search results: stations on a line ---------- */
/* One result per row: colour stripe, cover, title/artist, rating circle, arrow.
   .busy dims old results while a new search runs. */
.res { list-style: none; }
.res li { border-bottom: 1px solid var(--rule); }
.res a { display: flex; align-items: center; gap: 12px; padding: 9px 0; text-decoration: none; min-height: 60px; }
.res .stripe { width: 6px; align-self: stretch; flex: none; border-radius: 3px; background: var(--c); }
.res .frame { width: 58px; height: 58px; }
.res .go { color: var(--mute); font-size: 22px; flex: none; }
/* "Show More Albums": a quiet line under the first albums (each tap shows a few more, see
   ALBUMS_FIRST / ALBUMS_STEP in app.js). TWEAK: font-size, colour (--mute), padding. */
.res + .more {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 10px 0;
  font-size: 14px; font-weight: 700; color: var(--mute);
}
.res + .more::after { content: "+"; font-size: 18px; font-weight: 400; line-height: 1; }
.res + .more:hover { color: var(--ink); }
.res + .more[hidden] { display: none; }
/* Perfect Albums? page (Joe, 2026-11-04): the sort switch above the list (Highest / Lowest / Oldest / Newest),
   four buttons side by side like a transit timetable tab; the chosen one is filled in (ink).
   The "Random New Listens" button reuses .btn; its picks sit above the full list. */
.sort { display: flex; gap: 0; margin: 4px 0 8px; border: 2px solid var(--ink); border-radius: 999px; width: 100%; max-width: 440px; overflow: hidden; }
.sort button { flex: 1; text-align: center; font-size: 14px; font-weight: 700; padding: 8px 6px; min-height: 40px; white-space: nowrap; color: var(--ink); }
.sort button[aria-pressed="true"] { background: var(--ink); color: var(--bg); }
#pickRandom { margin-top: 18px; }
.results { transition: opacity .3s var(--ease); }
.results.busy { opacity: .55; }

/* ---------- album page ---------- */
/* Top of the album page: cover (150px) and title. .title.long = smaller font for long titles. */
.hero { display: flex; gap: 14px; margin-top: 16px; align-items: flex-end; }
.hero .frame { width: 150px; height: 150px; padding: 4px; }
.hero .frame .nocover { inset: 4px; }
/* The cover can be tapped to see it big (.zoom, below). */
.zoomable:has(img) { cursor: zoom-in; }
/* Big cover over the page: dark backdrop, the cover as large as fits, fades and grows in. */
.zoom {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center; padding: 24px;
  background: rgba(0, 0, 0, .92); opacity: 0; transition: opacity .35s var(--ease); cursor: zoom-out;
}
.zoom.open { opacity: 1; }
.zoom img {
  max-width: min(92vw, 1000px); max-height: 86vh; width: auto; height: auto; display: block;
  border: 1px solid #555; padding: 4px; background: #000;
  transform: scale(.92); transition: transform .35s var(--ease);
}
.zoom.open img { transform: none; }
.zclose { position: absolute; top: max(12px, env(safe-area-inset-top)); right: 14px; width: 48px; height: 48px; color: #fff; font-size: 34px; text-align: center; }
.title { font-size: 30px; font-weight: 700; letter-spacing: -.03em; line-height: 1.02; }
/* The title beside the cover takes the rest of the row; min-width: 0 lets it be narrower than
   its longest word, so fit.js can see the word doesn't fit and shrink it. */
.hero .title { flex: 1; min-width: 0; }
.title.long { font-size: 23px; }
/* Artist link(s) with their colour circle, then year / type / label (.facts). */
.byline { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; margin-top: 14px; }
.artist { display: inline-flex; align-items: center; gap: 8px; font-size: 17px; font-weight: 700; text-decoration: none; min-height: 40px; }
.artist { max-width: 100%; }
.artist .nm { min-width: 0; }
.artist .bul { width: 30px; height: 30px; font-size: 11px; }
/* The arrow after the artist's name: the same solid triangle as the one under the critic
   rating circle (.caret), turned to point right, so it's clear the name opens the discography.
   Used after the artist name on the album page and on artist rows in search results. */
.caret.next { border-top: 6px solid transparent; border-bottom: 6px solid transparent; border-left: 7px solid var(--ink); border-right: 0; flex: none; margin-left: 2px; }
.facts { font-size: 14px; color: var(--mute); margin-top: 2px; }

/* Headline rating: big circle (.bigbul) + text; tapping opens the per-critic list.
   .stops = five stations, filled to the star average (--f = how full each one is). */
.rbox {
  display: flex; align-items: center; gap: 14px; margin-top: 16px; padding: 12px 0; border-top: 3px solid var(--ink); width: 100%;
  transition: border-color .6s var(--ease);
}
.rbox[disabled] { cursor: default; }
.rwrap { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: none; }
.bigbul { width: 64px !important; height: 64px !important; font-size: 24px !important; }
.bigbul.none { font-size: 14px !important; }
.caret { width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 7px solid var(--ink); transition: transform .5s var(--ease); }
.rbox[aria-expanded="true"] .caret { transform: rotate(180deg); }
.rtext b { font-weight: 700; font-size: 16px; display: block; }
.rtext .s { margin-top: 4px; }
.stops { display: flex; align-items: center; position: relative; margin-top: 8px; gap: 10px; }
.stops::before { content: ""; position: absolute; left: 4px; right: 4px; top: 50%; height: 4px; margin-top: -2px; background: var(--c); transform-origin: left; }
.stops i { width: 16px; height: 16px; border-radius: 50%; border: 3px solid var(--c); position: relative; display: block;
  background: linear-gradient(90deg, var(--ink) var(--f, 0%), var(--bg) var(--f, 0%)); }

/* Per-critic list that slides open (grid-template-rows 0fr -> 1fr). Each critic is a
   station on a short line in their own colour (--k). .off = "well below/above the others". */
.cwrap { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows .6s var(--ease), opacity .5s var(--ease); }
.cwrap.open { grid-template-rows: 1fr; opacity: 1; }
.cinner { overflow: hidden; min-height: 0; }
.critics { list-style: none; margin: 2px 0 6px 7px; padding-left: 22px; }
.critics li { position: relative; display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 14px; padding: 5px 0; }
.critics li::after { content: ""; position: absolute; left: -22px; top: 0; bottom: 0; width: 4px; background: var(--k); transform-origin: top; }
.critics li:first-child::after { top: 50%; } .critics li:last-child::after { bottom: 50%; }
.critics li:only-child::after { display: none; }
.critics li::before { content: ""; position: absolute; z-index: 1; left: -28px; top: 50%; margin-top: -8px; width: 16px; height: 16px; border-radius: 50%; background: var(--ink); border: 3px solid var(--k); }
.critics b { font-weight: 700; }
.critics .sc { white-space: nowrap; font-weight: 700; text-align: right; }
.critics .off { display: block; font-weight: 400; font-size: 12px; color: var(--mute); }
.aggs { font-size: 13px; color: var(--mute); margin: 4px 0 8px 29px; line-height: 1.5; }
.aggs b { color: var(--ink); }

/* Discogs member rating line (kept separate from the critics). .wait = still loading. */
/* Discogs members' score: same size circle as the critics' one (Joe, 2026-11-02), but a grey
   ring with no colour, so it never looks like part of the critic score. */
.discogs { display: flex; align-items: center; gap: 14px; margin-top: 4px; padding: 12px 0 0; border-top: 1px solid var(--rule); text-decoration: none; }
.discogs .bul { width: 64px; height: 64px; font-size: 24px; border-width: 4px; }
.discogs b { font-size: 16px; }
.discogs.wait .bul { animation: breathe 1.6s var(--ease) infinite alternate; }
@keyframes breathe { to { border-color: var(--rule); } }
/* Short description, its source credit, and other versions of the album. */
.blurb { font-size: 15px; line-height: 1.5; color: var(--soft); margin-top: 18px; }
/* "What Critics Said": a review quote hung off a short transit line in the album's colour. */
.quote { margin: 18px 0 0; padding-left: 14px; border-left: 3px solid var(--c, var(--ink)); }
.quote figcaption { font-size: 13px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.quote blockquote { margin: 0; font-size: 15px; line-height: 1.5; color: var(--soft); }
/* "Track Listing" (trackListing() in app.js): a tap-to-open row, then the tracks in three columns:
   number as printed (A1, B2...), title, length. .thead = heading per record/CD when there are
   several. Opens with the same slide as the critics list (.cwrap). TWEAK: font sizes, .tn width. */
.tbtn { display: flex; align-items: center; gap: 14px; width: 100%; margin-top: 18px; padding: 12px 0; border-top: 1px solid var(--rule); text-align: left; }
.tbtn b { font-size: 16px; }
.tbtn .grow { flex: 1; min-width: 0; }
.tbtn .caret { flex: none; border-left-width: 8px; border-right-width: 8px; border-top-width: 9px; }
.tbtn[aria-expanded="true"] .caret { transform: rotate(180deg); }
.thead { font-size: 13px; color: var(--mute); margin: 10px 0 2px; }
.thead b { color: var(--ink); }
.tracks { list-style: none; margin: 0 0 6px; padding: 0; }
.tracks li { display: flex; align-items: baseline; gap: 10px; font-size: 14px; padding: 5px 0; border-bottom: 1px solid var(--rule); }
.tracks li:last-child { border-bottom: 0; }
.tracks li.side { border-top: 2px solid var(--soft); margin-top: -1px; }
.tracks .tn { flex: none; min-width: var(--tnw, 2.6em); white-space: nowrap; color: var(--mute); font-weight: 700; font-variant-numeric: tabular-nums; }
.tracks .tt { flex: 1; min-width: 0; }
.tracks .tl { flex: none; color: var(--mute); font-variant-numeric: tabular-nums; }
.credit { font-size: 12px; color: var(--mute); margin-top: 8px; }
.others { font-size: 13px; color: var(--mute); margin-top: 6px; }

/* ---------- best pressing (#/pressing, see "best pressing" in app.js) ----------
   Each pick is a "station": a thick line in its colour (--c) down the left, the list's name in
   bold, then the pressing itself as a tap target that opens it on Discogs, then the reasons
   as small stops on the line. .wait = still asking Discogs, .none = nothing fits that list.
   TWEAK: border-left width = the line, .why li::before = the stop dots. */
.pfor { margin-top: 10px; }
.picks { margin-top: 18px; display: grid; gap: 22px; }
.ppick { border-left: 6px solid var(--c); padding: 2px 0 4px 16px; }
.ppick .ph { display: flex; align-items: center; gap: 10px; font-size: 19px; letter-spacing: -.015em; }
.ppick .tag { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; border: 2px solid var(--ink); padding: 2px 6px; }
.ppick.wait .s { animation: breathe-text 1.6s var(--ease) infinite alternate; }
@keyframes breathe-text { to { opacity: .4; } }
.ppick .pz { display: block; text-decoration: none; margin-top: 10px; padding: 10px 12px; border: 1px solid var(--rule); }
.ppick .pz:hover { border-color: var(--mute); }
.ppick .pz .t { font-size: 17px; }
.ppick .dg { display: block; font-size: 13px; font-weight: 700; margin-top: 6px; }
.ppick .pnote { font-size: 15px; line-height: 1.45; color: var(--soft); margin-top: 8px; }
.why { list-style: none; margin-top: 8px; }
.why li { position: relative; padding-left: 18px; font-size: 14px; color: var(--soft); line-height: 1.4; margin-top: 4px; }
.why li::before { content: ""; position: absolute; left: 0; top: 5px; width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--c); box-sizing: border-box; }
.anim .ppick { animation: fade .5s var(--ease) both; }
/* Album page: the "Best Pressing" link under the Discogs rating, a record drawn in the ring. */
.plink .bul svg { width: 30px; height: 30px; fill: none; stroke: var(--ink); stroke-width: 2.4; }
.plink .go { color: var(--mute); font-size: 22px; }
.others a { color: var(--ink); }

/* ---------- artist page: one transit line ---------- */
/* Artist name with their colour circle. */
.ahead { display: flex; align-items: center; gap: 12px; margin-top: 18px; }
.ahead .bul { width: 58px; height: 58px; font-size: 22px; }
.ahead .title { font-size: 28px; }
.ahead > div { flex: 1; min-width: 0; }   /* lets a long artist name be fitted (fit.js) */
/* Studio albums as stations on one thick line, by year. --c = album colour, set by app.js.
   TWEAK: .line li::before width = line thickness, .line .st = station size, padding-left 64px =
   room for the years on the left. .here = the album you came from. */
.line { list-style: none; padding-left: 64px; margin-top: 6px; }
.line li { position: relative; }
.line li::before { content: ""; position: absolute; left: -17px; top: 0; bottom: 0; width: 10px; background: var(--c); transform-origin: top; }
.line li:first-child::before { top: 50%; } .line li:last-child::before { bottom: 50%; }
.line li:only-child::before { display: none; }
.line a { display: flex; align-items: center; gap: 10px; padding: 7px 0; min-height: 46px; text-decoration: none; }
.line .yr { position: absolute; left: -66px; width: 36px; text-align: right; font-size: 13px; color: var(--mute); font-weight: 700; }
.line .st { position: absolute; z-index: 1; left: -23px; width: 22px; height: 22px; border-radius: 50%; background: var(--ink); border: 4px solid var(--bg); box-shadow: 0 0 0 3px var(--ink); }
.line .t { flex: 1; margin-left: 16px; font-size: 15px; }
.line .bul { width: 32px; height: 32px; font-size: 12px; }
.line .bul.none { width: 22px; height: 22px; border-width: 2px; font-size: 0; margin: 0 5px; }
.line li.here .st { background: var(--c); }
.line li.here .t { text-decoration: underline; text-decoration-color: var(--c); text-decoration-thickness: 3px; text-underline-offset: 4px; }

/* "Singles, EPs and more": a thin grey branch line that opens below the main line. */
.branchbtn { display: flex; align-items: center; gap: 10px; margin-top: 16px; padding: 8px 0; min-height: 44px; color: var(--mute); font-size: 14px; font-weight: 700; width: 100%; }
.branchbtn .caret { border-top-color: var(--mute); }
.branchbtn[aria-expanded="true"] .caret { transform: rotate(180deg); }
.branch { list-style: none; padding-left: 64px; position: relative; }
.branch::before { content: ""; position: absolute; left: 49px; top: 12px; bottom: 18px; width: 5px; background: var(--grey); }
.branch li { position: relative; }
.branch a { display: flex; align-items: center; gap: 10px; padding: 6px 0; min-height: 40px; text-decoration: none; }
.branch .yr { position: absolute; left: -64px; width: 38px; text-align: right; font-size: 12px; color: var(--mute); }
.branch .st { position: absolute; left: -19px; width: 13px; height: 13px; border-radius: 50%; background: var(--bg); border: 3px solid var(--grey); }
.branch .t { font-size: 14px; font-weight: 400; margin-left: 2px; flex: 1; }
.branch .ty { font-size: 13px; color: var(--mute); flex: none; }

/* ---------- install guide ---------- */
/* Add to Home Screen guide: section headings and the install button. */
.guide h2 { font-size: 21px; margin-top: 30px; letter-spacing: -.02em; }
/* Install guide picker: tap-to-open rows (phone, then browser) that slide open like the
   critics list. .os = phone row (bold rule above), .br = browser row inside it. */
.pick { display: flex; align-items: center; justify-content: space-between; width: 100%; min-height: 52px; font-weight: 700; text-align: left; }
.pick.os { font-size: 21px; letter-spacing: -.02em; border-top: 3px solid var(--ink); padding: 10px 0; }
.pick.br { font-size: 17px; border-top: 1px solid var(--rule); padding: 8px 0 8px 14px; min-height: 48px; }
.pick[aria-expanded="true"] .caret { transform: rotate(180deg); }
.picker .how { margin: 0 0 12px 14px; }
.btn { display: inline-flex; align-items: center; gap: 10px; background: var(--ink); color: var(--bg); font-weight: 700; font-size: 17px; padding: 14px 18px; margin-top: 16px; min-height: 48px; }

/* ---------- about & contact ---------- */
/* .prose = readable body text, .sub = section heading. Contact form: labels reuse .lbl, the
   boxes (.field) are the same solid bar as the search box. .robot = spot for the robot check. TWEAK: .field font size, textarea height. */
.prose { font-size: 16px; line-height: 1.5; color: var(--soft); margin-top: 14px; }
.sub { font-size: 17px; font-weight: 700; margin: 28px 0 6px; }
.how h3 a { text-decoration: none; }
/* Data Policy: the opening promise in bigger type; "Clear" next to Recently Viewed on home. */
.prose.lead { font-size: 20px; font-weight: 700; color: var(--ink); line-height: 1.35; }
.lbl .clr { font-size: 13px; font-weight: 400; min-height: 32px; padding: 0 2px; }
.contact { display: flex; flex-direction: column; margin-top: 6px; }
.contact .lbl { justify-content: flex-start; }
.contact .opt { font-weight: 400; font-size: 12px; }
.field {
  width: 100%; border: 0; border-radius: 0; outline: 0; background: var(--ink); color: var(--bg);
  font: 700 17px var(--font); padding: 14px; min-height: 50px; letter-spacing: -.01em;
}
textarea.field { resize: vertical; line-height: 1.4; font-weight: 400; }
.field:focus-visible { box-shadow: 0 0 0 3px var(--f1); }
.contact .btn { align-self: flex-start; margin-top: 22px; }
/* The robot check is shrunk to fit narrow phones (page.js sets --fit); its height shrinks too. */
.contact .robot { margin-top: 20px; width: 100%; height: calc(78px * var(--fit, 1)); overflow: visible; }
.contact .robot > * { transform: scale(var(--fit, 1)); transform-origin: left top; }
.contact .lbl { margin-top: 0; padding-top: 18px; }

/* Contact page motion (contact.html adds .anim on arrival; plays once).
   .stripes = three bold signage bars that sweep in under the headline.
   .cstop = each part of the form is a station on one vertical transit line that draws itself
   downward, station by station. TWEAK: 300ms = time per station, .stripes i height/gap. */
.stripes { display: flex; flex-direction: column; gap: 5px; margin-top: 14px; }
.stripes i { display: block; height: 7px; transform-origin: left; }
.stripes i:nth-child(1) { background: var(--f1); width: 100%; }
.stripes i:nth-child(2) { background: var(--f2); width: 72%; }
.stripes i:nth-child(3) { background: var(--f3); width: 44%; }
.anim .stripes i { animation: grow .9s var(--ease) both; }
.anim .stripes i:nth-child(2) { animation-delay: .12s; } .anim .stripes i:nth-child(3) { animation-delay: .24s; }
.contact { padding-left: 34px; }
.cstop { position: relative; display: flex; flex-direction: column; }
.cstop::after { content: ""; position: absolute; left: -26px; top: 0; bottom: 0; width: 6px; background: var(--c); transform-origin: top; }
.cstop:first-of-type::after { top: 25px; }
.cstop.last::after { bottom: 26px; }   /* runs down to the Send station, past the robot check */
.cstop::before {
  content: ""; position: absolute; z-index: 1; left: -35px; top: 15px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--bg); border: 4px solid var(--ink); transition: background-color .5s var(--ease);
}
.cstop.last::before { top: auto; bottom: 14px; }
.cstop:focus-within::before { background: var(--c); }
.anim .cstop::after { animation: growY .3s cubic-bezier(.45, 0, .55, 1) both; animation-delay: calc(var(--i) * 300ms + 450ms); }
.anim .cstop::before { animation: pop .45s var(--ease) both; animation-delay: calc(var(--i) * 300ms + 450ms); }
.anim .cstop > * { animation: fade .5s var(--ease) both; animation-delay: calc(var(--i) * 300ms + 520ms); }
.anim > .prose, .anim > .msg { animation: fade .6s var(--ease) both; animation-delay: .3s; }

/* "Message sent" page: a train (white dot) runs along the line from You to Crate, the end
   station fills, then the thanks fade in. TWEAK: 1.4s = the train's trip. */
.sent { margin-top: 40px; }
.track { position: relative; height: 26px; margin: 0 12px; }
.track::before { content: ""; position: absolute; left: 0; right: 0; top: 10px; height: 6px; background: var(--f1); transform-origin: left; }
.track .stn { position: absolute; top: 0; width: 26px; height: 26px; margin-left: -13px; border-radius: 50%; background: var(--bg); border: 5px solid var(--ink); }
.track .a { left: 0; } .track .b { left: 100%; background: var(--f1); }
.track .train { opacity: 0; position: absolute; top: 5px; left: 100%; width: 16px; height: 16px; margin-left: -8px; border-radius: 50%; background: var(--ink); z-index: 1; }
.names { display: flex; justify-content: space-between; font-size: 14px; font-weight: 700; color: var(--mute); margin-top: 8px; }
.anim .track::before { animation: grow .7s var(--ease) both; }
.anim .track .a { animation: pop .45s var(--ease) both; }
.anim .track .train { animation: ride 1.4s var(--ease) .6s both, vanish .3s var(--ease) 2s both; }
.anim .track .b { animation: pop .45s var(--ease) both, arrive .4s var(--ease) 1.9s both; }
.anim .sent-h { animation: fade .6s var(--ease) 2.1s both; }
.anim .sent-p { animation: fade .6s var(--ease) 2.3s both; }
@keyframes ride { from { left: 0; opacity: 1; } to { opacity: 1; } }
@keyframes vanish { from { opacity: 1; } to { opacity: 0; } }
@keyframes arrive { from { background: var(--bg); } }

/* ---------- footer: centred text on a mini transit line ---------- */
/* Footer: credit text between two coloured line segments (colours --f1 / --f3). */
.foot {
  display: flex; align-items: center; gap: 10px; padding: 14px 0 max(16px, env(safe-area-inset-bottom));
  font-size: 11px; font-weight: 700; letter-spacing: .04em; color: var(--mute);
}
.foot .seg { flex: 1; height: 4px; border-radius: 2px; transition: background-color .6s var(--ease); }
.foot .end { width: 12px; height: 12px; border-radius: 50%; background: var(--bg); border: 3px solid; flex: none; transition: border-color .6s var(--ease); }
.foot .l { background: var(--f1); border-color: var(--f1); } .foot .end.l { background: var(--bg); }
.foot .r { background: var(--f3); border-color: var(--f3); } .foot .end.r { background: var(--bg); }
.foot a { white-space: nowrap; color: var(--mute); text-decoration: none; }
.foot a:hover { color: var(--ink); }

/* ---------- screen change: a black panel wipes across, edges in the artist's colour ---------- */
/* Bar that sweeps across between screens; app.js moves it (transform) and sets --edge. */
.wipe {
  position: fixed; top: 0; bottom: 0; left: -4px; right: -4px; z-index: 20; pointer-events: none;
  background: var(--bg); border-left: 4px solid var(--edge, var(--ink)); border-right: 4px solid var(--edge, var(--ink));
  transform: translateX(105%);
}

/* ---------- motion: plays once on arrival, never loops ---------- */
/* Arrival animations, added by app.js with the .anim class. TWEAK: the first time (.8s, .45s)
   is how long each piece takes; animation-delay staggers them. --n / --i = item number,
   --step = time per station on the artist line (set by app.js). */
.anim .stops::before { animation: grow .8s var(--ease) both; }
.anim .stops i { animation: pop .45s var(--ease) both; animation-delay: calc(var(--n) * 120ms + 100ms); }
.anim .bigbul { animation: pop .6s var(--ease) both; }
.critics.draw li::after { animation: growY .35s var(--ease) both; animation-delay: calc(var(--n) * 160ms + 120ms); }
.critics.draw li::before { animation: pop .45s var(--ease) both; animation-delay: calc(var(--n) * 160ms + 120ms); }
.critics.draw li > * { animation: fade .5s var(--ease) both; animation-delay: calc(var(--n) * 160ms + 180ms); }
.anim .line li::before { animation: growY var(--step) cubic-bezier(.45, 0, .55, 1) both; animation-delay: calc(var(--i) * var(--step)); }
.anim .line li .st { animation: pop .4s var(--ease) both; animation-delay: calc(var(--i) * var(--step)); }
.anim .line li .t, .anim .line li .yr { animation: fade .3s var(--ease) both; animation-delay: calc(var(--i) * var(--step) + 40ms); }
.anim .line li .bul { animation: pop .45s var(--ease) both; animation-delay: calc(var(--i) * var(--step) + 80ms); }
.anim:not(.settled) .how li::after { animation: growY .5s var(--ease) both; animation-delay: calc(var(--i) * 260ms); }
.anim:not(.settled) .how .st { animation: pop .45s var(--ease) both; animation-delay: calc(var(--i) * 260ms); }
.anim:not(.settled) .how h3, .anim:not(.settled) .how p, .anim:not(.settled) .how .ex { animation: fade .5s var(--ease) both; animation-delay: calc(var(--i) * 260ms + 80ms); }
/* Branch list slide-open, same trick as .cwrap. */
.bwrap { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows .6s var(--ease), opacity .5s var(--ease); }
.bwrap.open { grid-template-rows: 1fr; opacity: 1; }
.bwrap > div { overflow: hidden; min-height: 0; }
/* The four movements: grow sideways, grow downward, pop in, fade up. */
@keyframes grow { from { transform: scaleX(0); } }
@keyframes growY { from { transform: scaleY(0); } }
@keyframes pop { from { transform: scale(.4); opacity: 0; } }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } }
/* People who ask their phone for less motion get no animations at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
/* Tablets and computers: bigger headline. */
@media (min-width: 700px) {
  .headline { font-size: 40px; margin-top: 34px; }
}
/* ---------- wide screens (computers): a wider page, same look ----------
   TWEAK: 900px = from what window width this applies; .app max-width = page width;
   340px = album cover size; 32px = gap between the two result columns. */
@media (min-width: 900px) {
  .app { max-width: 1040px; --pad: 32px; }
  .headline { font-size: 52px; }
  .searchbox { height: 62px; }
  .searchbox input { font-size: 22px; }
  .artframe { max-height: 560px; }
  .recent { margin-right: 0; padding-right: 0; flex-wrap: wrap; }
  /* Search results in two columns. */
  .res { display: grid; grid-template-columns: 1fr 1fr; column-gap: 32px; }
  .res li[hidden] { display: none; }
  /* Album page: big cover on the left (stays in view while scrolling), everything else on the right. */
  .album { display: grid; grid-template-columns: 340px minmax(0, 1fr); column-gap: 44px; margin-top: 24px; }
  .album .hero { display: contents; }
  .album .hero .frame { grid-column: 1; grid-row: 1 / span 2; width: 340px !important; height: 340px !important; position: sticky; top: 20px; align-self: start; }
  .album .title { grid-column: 2; grid-row: 1; font-size: 44px; align-self: end; }
  .album .title.long { font-size: 32px; }
  .album .ainfo { grid-column: 2; grid-row: 2; }
  .blurb { font-size: 16px; max-width: 64ch; }
  .quote blockquote { font-size: 16px; max-width: 62ch; }
  /* Artist page, info pages and the install guide: a comfortable reading width. */
  .ahead, .line, .branchbtn, .bwrap, .prose, .how, .picker, .msg, .sub, .picks, .pfor { max-width: 720px; }
  .pfor { display: block; }
}
/* Lists: "no reviews" is a small quiet ring so rated albums stand out. */
.res .bul.none { width: 26px; height: 26px; margin: 0 7px; border-width: 2px; font-size: 0; }
/* Search results: Discogs member rating for albums with no critic reviews (see fillDiscogs()
   in app.js). Grey ring, no colour, with a small "Discogs" label under it, so it never looks
   like a critic average. .dcg is the slot; .on = a rating was found. */
.dcg { flex: none; display: flex; flex-direction: column; align-items: center; min-width: 40px; }
.dcg.on .bul.ring { width: 40px; height: 40px; border-width: 2px; font-size: 14px; color: var(--soft); animation: fadein .6s var(--ease); }
.dcg .dl { font-size: 10px; line-height: 1; margin-top: 3px; color: var(--mute); letter-spacing: .01em; }
.dnote { font-size: 12px; color: var(--mute); margin-top: 10px; }
/* On the artist page's line of albums the Discogs circle matches the critic circles' size. */
.line .dcg { min-width: 32px; }
.line .dcg.on .bul.ring { width: 32px; height: 32px; font-size: 12px; }
.line .dcg .dl { font-size: 9px; margin-top: 2px; }
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
.frame img { background: #151515; }
:root[data-theme="light"] .frame img { background: #e4ddcf; }

/* Home artwork. The frame stretches to fill the space under the search box.
   TWEAK: margin-top = gap above it, min-height / max-height = smallest and largest frame,
   padding = gap between frame and art. The record itself (colours, sizes, motion) is set in
   ART at the top of art.js; .label / .spindle colours follow light and dark mode. */
#view.home { display: flex; flex-direction: column; }
#view.home #intro:not([hidden]) { flex: 1; display: flex; flex-direction: column; }
.artframe { margin-top: 26px; border: 1px solid var(--frame); padding: 4px; flex: 1; min-height: 230px; max-height: 460px; width: 100%; display: flex; }
.art { display: block; flex: 1; width: 100%; min-height: 0; background: var(--bg); }
.art .label { fill: var(--ink); transition: fill .6s var(--ease); }
.art .spindle { fill: var(--bg); transition: fill .6s var(--ease); }
/* Loading spin while a search runs (started and stopped by spinArt in art.js).
   The record turns around its own centre. It speeds up gently over the first 0.8 s
   ("spinup", which ends at the same speed as "spin"), then turns steadily, one turn every
   1.2 s, until the results appear. The small mark on the label fades in so the solid
   "rings" version visibly turns too. TWEAK: change 1.2s for a faster or slower spin
   (keep spinup's 120deg / 0.8s at half the spin speed so the hand-over stays smooth). */
.art .disc { transform-box: view-box; transform-origin: 50% 50%; }
.art .mark { stroke: var(--bg); fill: none; opacity: 0; transition: opacity .4s var(--ease), stroke .6s var(--ease); }
.art.spinning .mark { opacity: 1; }
.art.spinning .disc { animation: spinup .8s cubic-bezier(.333, 0, .667, .333) both, spin 1.2s linear .8s infinite; }
@keyframes spinup { from { transform: rotate(0deg); } to { transform: rotate(120deg); } }
@keyframes spin { from { transform: rotate(120deg); } to { transform: rotate(480deg); } }
@media (prefers-reduced-motion: reduce) { .art.spinning .disc { animation: none; } }
