/* trackmatch component library — the locked aesthetic, reusable.
   Every component is built to match FILES/SCREENSHOTS OF ORIGINAL UI/.
   Pages compose these; the results body itself comes from the engine's
   templates/results.html (reused unchanged). */

/* ---------- layout ---------- */
.tm-shell { max-width: var(--maxw); margin: 0 auto; padding: 40px 20px; }
.tm-center {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; min-height: 100svh; text-align: center; padding: 24px;
}
/* vertically centre a module in the viewport; margin:auto keeps it scroll-safe
   when the content is taller than the screen (mobile) instead of clipping. */
.tm-vcenter { min-height: 100svh; display: flex; flex-direction: column; padding: 24px 0; }
.tm-vcenter > .tm-shell { margin-top: auto; margin-bottom: auto; }
.tm-stack { display: flex; flex-direction: column; gap: 12px; }
.tm-row-h { display: flex; gap: 12px; align-items: stretch; }
.tm-muted { color: var(--grey-3); }
.tm-faint { color: var(--grey-4); }
.tm-italic { font-style: normal; }

/* ---------- wordmark + intro ---------- */
.tm-wordmark { font-style: normal; color: var(--fg); letter-spacing: .5px; text-align: center; }
.tm-intro { color: var(--grey-2); text-align: center; max-width: 560px; margin: 0 auto; }
.tm-intro b, .tm-intro strong { color: var(--fg); }

/* ---------- inputs (recessed / sunken bevel, win98 text-field look) ---------- */
/* data-entry fields keep the mono face (tracklists/urls read + paste better);
   everything else inherits the win98 UI font from <body>. */
.tm-input, .tm-textarea, .tm-select { font-family: var(--font-mono); }
.tm-input, .tm-textarea, .tm-select, .tm-upload {
  font-size: .95rem;
  text-transform: lowercase;
  color: var(--fg);
  /* transparent "empty space" over the bg animation — only borders + text show */
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-chip);
  box-shadow: none;
  padding: 10px 13px;
  width: 100%;
}
.tm-input:focus, .tm-textarea:focus, .tm-select:focus { border-color: var(--accent); outline: none; }
.tm-textarea { resize: vertical; min-height: 240px; line-height: 1.6; }
.tm-textarea::placeholder { font-style: normal; color: var(--grey-3); }

/* labelled field (stacked) */
.tm-field { display: flex; flex-direction: column; gap: 6px; }
.tm-field > label { color: var(--grey-2); font-size: .9rem; }

/* file upload — an outlined "empty space" button (animation shows through) */
.tm-upload {
  cursor: pointer; text-align: center; color: var(--grey-1);
  background: transparent; box-shadow: none;
  display: inline-flex; align-items: center; justify-content: center; min-height: 42px;
}
.tm-upload input[type="file"] { display: none; }
.tm-upload:hover { border-color: var(--accent); color: var(--accent); }
.tm-upload:active { transform: translateY(1px); }

/* ---------- buttons: the original "empty space" outlined control (transparent
   fill so the bg animation shows underneath), in the new 8-bit font ---------- */
.tm-btn {
  font-family: var(--font-ui); font-size: .85rem; text-transform: lowercase; letter-spacing: .01em;
  cursor: pointer; color: var(--fg);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-chip);
  box-shadow: none;
  padding: 10px 20px;
  white-space: nowrap;
  /* centre content for both <button> and <a class="tm-btn"> */
  display: inline-flex; align-items: center; justify-content: center; text-align: center;
  transition: color .1s ease, border-color .1s ease;
}
.tm-btn:hover { border-color: var(--accent); color: var(--accent); }
.tm-btn:active { transform: translateY(1px); }  /* pressed-in */
.tm-btn:disabled { opacity: .5; cursor: default; transform: none; }
.tm-btn.inverted, .tm-btn.solid {
  background: var(--border); color: #111; border-color: #000;
  box-shadow: inset 1.5px 1.5px 0 rgba(255,255,255,.6), inset -1.5px -1.5px 0 rgba(0,0,0,.35);
}
.tm-btn.inverted:hover, .tm-btn.solid:hover { background: var(--accent); color: #061015; }
.tm-btn.inverted:active, .tm-btn.solid:active { box-shadow: inset 1.5px 1.5px 0 rgba(0,0,0,.35), inset -1.5px -1.5px 0 rgba(255,255,255,.6); }
.tm-btn.block { width: 100%; }
.tm-btn.ghost { background: transparent; border-color: transparent; box-shadow: none; color: var(--grey-2); padding: 9px 12px; }
.tm-btn.ghost:hover { color: var(--accent); }
.tm-btn.ghost:active { box-shadow: none; }

/* input + button on one line (mix url + match, upload + search) */
.tm-inline { display: flex; gap: 12px; align-items: stretch; }
.tm-inline > .tm-input, .tm-inline > .tm-upload { flex: 1 1 auto; }
.tm-inline > .tm-btn { flex: 0 0 auto; }

/* equal-width button pair (login/signup, saved/more) */
.tm-btn-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* saved/more + dash/change-default sit at the tabs' height */
.tm-btn-pair .tm-btn { min-height: 0; padding-top: 10px; padding-bottom: 10px; }

/* one shared 3-column grid for the input zone: mix-url spans 2 + match in col 3,
   and upload | record | search are three equal columns. So match aligns exactly
   above search and the action row is symmetrical. */
.tm-grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; align-items: stretch; }
.tm-grid3 > .tm-btn, .tm-grid3 > .tm-upload { min-height: 44px; }
.tm-grid3 .tm-upload #tm-upload-label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}

/* live-scan listening overlay (the one place, besides results loading, w/o bg video) */
.tm-listen-overlay {
  position: fixed; inset: 0; z-index: 60; background: #000;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px;
  padding: 24px; text-align: center;
}
.tm-listen-status { color: var(--grey-1); font-style: normal; }

/* ---------- cards ---------- */
/* frosted tint + brighter border to match the panels. NO backdrop-filter here:
   cards appear several-to-a-page on scrolling views (info / subscribe), where a
   blur over the fixed mp4 would re-composite every scroll frame. The tint alone
   carries the material and keeps scrolling buttery. */
.tm-box, .tm-card {
  border: 1px solid var(--frost-border);
  border-radius: var(--radius-chip);
  box-shadow: var(--frost-rim);
  background: var(--frost-bg);
  color: var(--grey-1);
  padding: 18px 22px;
}
.tm-card { text-align: center; }
.tm-card b, .tm-card strong { color: var(--fg); }
.tm-card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.tm-card-tick { text-align: center; color: var(--grey-1); margin-top: 8px; font-size: .9rem; }
.tm-card-tick small { display: block; color: var(--grey-4); }

/* ---------- chips: the same raised beveled control, just a touch tighter, for
   dense rows of secondary actions. Same visual language as .tm-btn (one system). ---------- */
.tm-chips { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.tm-chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--font-ui); font-size: .82rem; text-transform: lowercase; letter-spacing: .02em;
  color: var(--grey-1); text-decoration: none; cursor: pointer; white-space: nowrap;
  background: transparent; box-shadow: none;
  border: 1px solid var(--line); border-radius: var(--radius-chip);
  padding: 7px 12px; line-height: 1;
  transition: border-color .1s ease, color .1s ease;
}
.tm-chip:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.tm-chip:active { transform: translateY(1px); }
.tm-chip.accent { color: var(--accent); }
.tm-chip.solid {
  background: var(--border); color: #111; border-color: #000;
  box-shadow: inset 1.5px 1.5px 0 rgba(255,255,255,.6), inset -1.5px -1.5px 0 rgba(0,0,0,.35);
}
.tm-chip.solid:hover { background: var(--accent); border-color: var(--accent); color: #061015; }
.tm-chip[disabled], .tm-chip.saved { opacity: .6; pointer-events: none; }
.tm-chip.saved { color: var(--border); }

/* ---------- footer links (underlined italic) — kept for true inline text links ---------- */
.tm-flinks { display: flex; gap: 22px; justify-content: center; flex-wrap: wrap; }
.tm-flink { color: var(--grey-2); font-style: normal; text-decoration: underline; cursor: pointer; }
.tm-flink:hover { color: var(--accent); }

/* ---------- welcome / status ---------- */
.tm-welcome { text-align: center; color: var(--grey-2); font-family: var(--font-ui); }
.tm-welcome .hl { color: var(--accent); }
.tm-status { color: var(--accent); text-align: center; font-family: var(--font-ui); margin-top: 10px; font-size: .82rem; }

/* share-page join popup: a plain benefits list, one door to the welcome page */
.tm-join-list { list-style: none; padding: 0; margin: 14px 0 4px; }
.tm-join-list li { padding: 5px 0; color: var(--grey-2); text-align: center; }
.tm-join-list li::before { content: "* "; color: var(--accent); }

/* "more" modal: even rhythm between its three zones; status fades to a footnote */
.tm-more-rule { border: 0; border-top: 1px solid #222; margin: 18px 0; }
.tm-more-account { display: flex; flex-direction: column; gap: 10px; }
.tm-more-signedin { text-align: center; font-size: .82rem; color: var(--grey-3); }
.tm-more-status { margin-top: 16px; font-size: .75rem; opacity: .75; }

/* "more" panel: one symmetric grid of equal-width chips */
.tm-more-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.tm-more-grid .tm-chip { width: 100%; text-align: center; }
.tm-more-wide { grid-column: 1 / -1; }   /* the add-to-home/bookmarks row spans both */

/* settings modal: quiet label-left / control-right rows, one hairline between
   each — the same list rhythm as the saved library, so it reads native */
.tm-set-rows { display: flex; flex-direction: column; margin-top: 6px; }
.tm-set-row { padding: 14px 2px; border-bottom: 1px solid #1c1c1c; }
.tm-set-row:last-child { border-bottom: 0; }
.tm-set-main { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
/* labels wrap (min-width:0 lets the flex item shrink) — a long label ("auto-listen
   when navigating…") must never push its control off a phone screen */
.tm-set-label { color: var(--grey-1); cursor: pointer; flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.tm-set-check { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; flex: 0 0 auto; cursor: pointer; }
.tm-set-note { color: var(--grey-4); font-size: .78rem; margin: 7px 0 0; line-height: 1.5; }
/* tight inline select; inside a settings row both selects share ONE fixed width,
   so the control column lines up row to row (out-specifies .tm-select-wrap's 100%) */
.tm-select-inline { width: auto; min-width: 132px; }
.tm-set-main .tm-select-wrap { flex: 0 0 190px; width: 190px; min-width: 0; }
.tm-select-inline .tm-select { padding: 7px 34px 7px 12px; font-size: .82rem; }
.tm-select-inline.tm-select-wrap::after { right: 12px; }

/* account removal: a quiet status-sized link with an inline are-you-sure state */
.tm-remove-account { margin-top: 14px; text-align: center; font-size: .75rem; color: var(--grey-4); }
.tm-remove-account .tm-foot-dot { margin: 0 6px; color: var(--grey-4); }
/* the more-menu foot: remove account + how it works, centred side by side below
   the status line (wraps to stacked lines when the inline are-you-sure expands) */
.tm-more-foot {
  margin-top: 10px; font-size: .75rem;
  display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline;
  gap: 6px 16px; text-align: center;
}
.tm-more-foot .tm-linklike {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: inherit; color: var(--grey-4); text-decoration: underline;
}
.tm-more-foot .tm-linklike:hover { color: var(--accent); }
/* remove account keeps its red-on-hover danger cue; its inline confirm takes the
   full width so it never crowds the "how it works" link mid-confirm */
.tm-more-foot #tm-remove-wrap .tm-linklike[data-remove="start"]:hover { color: var(--error); }
.tm-more-foot #tm-remove-wrap:not(:has([data-remove="start"])) { flex-basis: 100%; }

/* ---------- disclosure toggle (▸ more details / ▾ show / hide) ---------- */
.tm-toggle {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  color: var(--grey-2); background: none; border: none; font: inherit;
  text-transform: lowercase;
}
.tm-toggle::before { content: "▸"; color: var(--grey-2); }
.tm-toggle[aria-expanded="true"]::before { content: "▾"; }
.tm-toggle:hover { color: var(--accent); }

/* ---------- pill (parts) ---------- */
.tm-pill {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 14px; color: var(--grey-2); cursor: pointer;
}
.tm-pill:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- top bar (results / saved) ---------- */
.tm-topbar {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 12px; padding: 14px 0;
}
.tm-topbar .tm-topbar-title { text-align: center; color: var(--fg); }
.tm-topbar .left { justify-self: start; }
.tm-topbar .right { justify-self: end; }

/* results page: pin dash · title · save to the top and let it follow the scroll,
   translucent over the mp4 underlay AND the soundcloud/youtube embed below. The
   backdrop blur softens whatever scrolls under it (incl. the iframe, which the
   composited backdrop reaches); the dark tint keeps the text crisp even where a
   blur isn't supported. Centred to the content column, matching the rows below. */
.tm-topbar-sticky {
  position: sticky; top: 0; z-index: 50;
  max-width: var(--maxw); margin: 0 auto; padding: 14px 20px;
  background: transparent; border-bottom: 1px solid transparent;
  /* a plain colour fade only — NO backdrop-filter. A blur over the playing mp4 +
     iframe repaints every scroll frame and is what made scrolling feel heavy; a
     translucent tint gives the same "slight opacity" look for ~free, buttery. */
  transition: background .18s ease, border-color .18s ease;
  will-change: background;
}
/* only once scrolled does the tint fade in (at the very top the bar is invisible
   chrome over the mp4; scrolling brings content under it, so it needs a legible
   backing). Toggled from results.js on scroll. */
.tm-topbar-sticky.is-stuck {
  background: rgba(9, 9, 9, 0.72);
  border-bottom-color: rgba(255, 255, 255, 0.06);
}
.tm-bracket { color: var(--grey-2); cursor: pointer; }
.tm-bracket:hover { color: var(--accent); }
.tm-bracket::before { content: "["; }
.tm-bracket::after { content: "]"; }

/* the results tail (prefs + footer): mirrors the ENGINE's content column
   (.container is max-width 800 + 40px padding, rows indent 2px) so the checkbox
   starts exactly where the row titles do, at every viewport width */
.tm-results-tail { max-width: 800px; margin: 0 auto; padding: 0 42px; }

/* with no load-more between the list and the footer, pull the tail up into the
   list's rhythm (the frame keeps ~38px of internal bottom padding) */
#tm-loadmore-wrap[hidden] + .tm-results-tail { margin-top: -20px; }

/* the quiet results footer */
.tm-results-foot {
  text-align: center; font-family: var(--font-ui); font-size: .75rem;
  color: var(--grey-4); opacity: .75; padding: 18px 0 24px;
}

/* mobile scan orb: a JS-stepped 8×8 sprite sheet (same vp9-alpha source) replaces
   the video on touch screens — iOS "reduce motion" freezes video autoplay AND
   animated images, but plain background-position stepping is untouchable.
   NOTE: the span also carries .tm-record-video (for sizing), whose display:block
   rule comes later in this file — these selectors must out-rank it. */
span.tm-orb-sprite {
  display: none;
  background: url("/static/media/loading_sprite.webp") 0 0 no-repeat;
  background-size: 1500% 1000%;    /* 15×10 grid = 150 frames, matching the 15fps video */
}
@media (pointer: coarse) {
  /* covers BOTH orbs: the scan-loading orb and the record surface's glass */
  .tm-record-btn video.tm-record-video { display: none; }
  .tm-record-btn span.tm-orb-sprite { display: block; }
}
/* no-WebM desktops (Safari) can't play the VP9 orb and fall through to an alpha-less
   HEVC that renders black — route them to the same JS-stepped sprite as touch does
   (html.tm-no-webm is set by ui.js). WebM-capable desktops keep the <video>. */
html.tm-no-webm .tm-record-btn video.tm-record-video { display: none; }
html.tm-no-webm .tm-record-btn span.tm-orb-sprite { display: block; }

/* results save control: [save] → [custom] [title] [saved] → [ name… ] */
#tm-save-slot { display: inline-flex; align-items: center; gap: 10px; }
.tm-save-edit { color: var(--accent); }
.tm-save-input {
  background: transparent; border: none; outline: none; padding: 0;
  /* opens as a bare caret hugged by the brackets; JS grows it per character */
  color: var(--accent); font: inherit; width: 1ch; max-width: 24ch; caret-color: var(--accent);
}

/* ---------- list rows (saved searches / tracks) ---------- */
.tm-list { display: flex; flex-direction: column; }
.tm-list-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 10px 2px; border-bottom: 1px solid var(--frost-sep);
}
.tm-list-row .label { color: var(--grey-1); overflow-wrap: anywhere; }
.tm-list-row .actions { display: flex; gap: 8px; flex: 0 0 auto; align-items: center; }
/* open / del as small boxy chips (the circular store icons keep their own style) */
.tm-list-row .actions a:not(.tm-store):not(.tm-yt-play):not(.tm-similar),
.tm-list-row .actions .tm-del-yes, .tm-list-row .actions .tm-del-no {
  color: var(--grey-2); text-decoration: none;
  border: 1px solid var(--line); border-radius: var(--radius-chip);
  padding: 3px 9px; font-size: .76rem; transition: border-color .12s, color .12s;
}
.tm-list-row .actions a:not(.tm-store):not(.tm-yt-play):not(.tm-similar):hover { border-color: var(--accent); color: var(--accent); }

/* show similar: the magnifier circle matching the result rows' (grey fill, black
   glass — the colour inverse of the black wantlist eye) */
.tm-list-row .actions .tm-similar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--grey-1); color: #000; border: 1px solid #000;
  text-decoration: none; transition: background .12s, border-color .12s;
}
.tm-list-row .actions .tm-similar svg { width: 15px; height: 15px; display: block; fill: currentColor; }
.tm-list-row .actions .tm-similar:hover { background: var(--accent); }

/* ---------- saved-track store icons + in-place embed ---------- */
.tm-list-row .actions .tm-store,
.tm-list-row .actions .tm-yt-play {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; font-size: .8rem; font-weight: bold;
  text-decoration: none;
}
.tm-list-row .actions .tm-store[title="bandcamp"] { background: #629aa9; color: #fff; }
.tm-list-row .actions .tm-store[title="discogs"] { background: #999; color: #000; }
/* real-favicon store icons (small, 26px): the art is the background so the
   anchor stays empty; the split duo keeps BOTH halves clickable via clip-path
   (discogs bottom-left, bandcamp top-right — the icon's own diagonal seam) */
.tm-list-row .actions .tm-store-fav { background-color: transparent; background-size: cover; background-position: center; }
.tm-list-row .actions .tm-store-duo { position: relative; flex: 0 0 auto; width: 26px; height: 26px; }
.tm-list-row .actions .tm-store-duo a {
  position: absolute; inset: 0; border-radius: 50%;
  background: url("/static/media/favicons/discogs-bandcamp.png") center/cover no-repeat;
}
.tm-list-row .actions .tm-store-duo .duo-d { clip-path: polygon(0 0, 100% 100%, 0 100%); }
.tm-list-row .actions .tm-store-duo .duo-b { clip-path: polygon(0 0, 100% 0, 100% 100%); }
/* the preview pill = the small grey youtube favicon; saved.js may paint a
   short "…" over it while resolving, so the art lives in the background */
.tm-list-row .actions .tm-yt-play {
  background: transparent url("/static/media/favicons/youtube-grey.png") center/cover no-repeat;
  color: #fff; font-size: .7rem;
}
.tm-list-row .actions .tm-store:hover, .tm-list-row .actions .tm-yt-play:hover,
.tm-list-row .actions .tm-store-duo a:hover { filter: brightness(1.15); text-decoration: none; }
.tm-confirm { color: var(--grey-2); }
.tm-confirm a { color: var(--accent); }
.tm-yt-embed { padding: 8px 0 12px; }

/* ---------- saved-track rows: results-style open/close ---------- */
/* the block wraps the row + its dropdown; the hairline separator moves here so it
   sits below the details, not between a row and its own dropdown */
.tm-track-block { border-bottom: 1px solid var(--frost-sep); }
/* pin the icon cluster to the FIRST line (like the results rows): when an open row's
   title wraps to 2+ lines, the icons stay put at the top instead of sliding down to
   the vertical centre. */
.tm-track-block .tm-track-row { border-bottom: none; cursor: pointer; align-items: flex-start; }
.tm-track-block .tm-track-row .actions { margin-top: 1px; }

/* marketplace summary circle: a visual only (the real links live in the dropdown),
   so a single combined icon never has to carry two separate links */
.tm-track-row .actions .tm-store-sum {
  display: inline-flex; flex: 0 0 auto;
  width: 26px; height: 26px; border-radius: 50%;
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
.tm-track-row .actions .tm-store-duo-sum { background-image: url("/static/media/favicons/discogs-bandcamp.png"); }

/* show-similar / wantlist / delete stay collapsed until the row opens, then slide +
   fade in — the same motion as the results rows */
.tm-track-row .actions .tm-reveal {
  width: 0 !important; min-width: 0 !important;
  margin-left: -8px;                 /* cancel the .actions gap while collapsed */
  padding-left: 0; padding-right: 0;
  border-width: 0 !important;
  opacity: 0; overflow: hidden; pointer-events: none;
  transition: width .14s ease, opacity .14s ease, margin .14s ease, border-width .14s ease;
}
.tm-track-row.is-open .actions .tm-reveal {
  width: 26px !important; min-width: 26px !important;
  margin-left: 0; border-width: 1px !important;
  opacity: 1; pointer-events: auto;
  transition: width .12s ease, opacity .12s ease, margin .12s ease;
}

/* wantlist eye (black circle, grey glyph — colour-inverse of the similar magnifier) */
.tm-track-row .actions .tm-want {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; min-width: 26px; border-radius: 50%;
  background: #000; color: var(--grey-1); border: 1px solid var(--grey-1);
  padding: 0; cursor: pointer;
}
.tm-track-row .actions .tm-want svg { width: 15px; height: 15px; display: block; fill: currentColor; }
.tm-track-row .actions .tm-want.saved { background: var(--grey-1); color: #111; border-color: var(--grey-1); cursor: default; }
.tm-track-row .actions .tm-want.want-error { background: #b54a4a; border-color: #b54a4a; }

/* delete — the "+" favourite slot from results, inverted to a "−" (same neutral
   grey as the results "+" button; no alarming red) */
.tm-track-row .actions .tm-del {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; min-width: 26px; border-radius: 50%;
  background: #888; color: #fff; border: 1px solid #888;
  font-size: 1.05rem; font-weight: bold; line-height: 1; padding: 0;
  cursor: pointer; text-decoration: none;
}
.tm-track-row .actions .tm-del:hover { background: #999; border-color: #999; }

/* shared feed: "+" to copy a friend's find into your own favourites (same neutral
   "+" as results; reveals on open like the other row actions) */
.tm-track-row .actions .tm-fav-copy {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; min-width: 26px; border-radius: 50%;
  background: #888; color: #fff; border: 1px solid #888;
  font-size: 1.05rem; font-weight: bold; line-height: 1; padding: 0; cursor: pointer;
}
.tm-track-row .actions .tm-fav-copy:hover { background: #999; border-color: #999; }
.tm-track-row .actions .tm-fav-copy.saved { background: var(--grey-1); color: #111; border-color: var(--grey-1); cursor: default; }
/* private digging mode: the copy-to-favourites success is a pale-green lock, not a
   tick — a reminder the copied favourite was saved privately (out of the shared feed) */
.tm-track-row .actions .tm-fav-copy.saved.tm-priv-saved { background: #bfe6c8; color: #14532d; border-color: #bfe6c8; }
.tm-track-row .actions .tm-fav-copy.tm-priv-saved svg { width: 15px; height: 15px; display: block; fill: currentColor; }

/* while confirming a delete, hide every other icon so a long title can't get
   squashed on mobile — the row shows only the short "you sure?" prompt */
.tm-track-row.confirming .actions > :not(.tm-confirm) { display: none !important; }
.tm-track-row .tm-confirm { white-space: nowrap; }

/* the dropdown: discogs + bandcamp links — presented like the normal results
   lists (lowercase headers, lowercase link text in the discogs/bandcamp blue) */
.tm-track-details { padding: 2px 2px 12px; }
/* line-height:normal (this surface inherits 1.5 from the body): without it the
   heading + its link each add ~half a line of leading, opening a ~7px gap between
   e.g. "bandcamp" and its link — the results iframe hugs at ~2px, so match it */
.tm-track-details h4 {
  margin: 10px 0 1px; font-size: .8rem; font-weight: 400; line-height: normal;
  text-transform: lowercase; letter-spacing: 0; color: var(--grey-1);
}
.tm-track-details ul { list-style: none; margin: 0; padding: 0; line-height: normal; }
.tm-track-details li { padding: 0; }
.tm-track-details a { color: var(--link); text-decoration: none; text-transform: lowercase; font-size: .85rem; line-height: normal; overflow-wrap: anywhere; }
.tm-track-details a:hover { text-decoration: underline; }

/* resident advisor: on-demand gig search button + neutral grey result links (kept
   apart from the blue discogs/bandcamp links), matching the results-iframe styling.
   inline-flex (not flex) so the trigger hugs the "resident advisor" text + icon —
   otherwise the full-width line is hoverable/clickable well past the link. */
.tm-track-details .ra-head { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.tm-track-details .ra-head.ra-done { cursor: default; }
.tm-track-details .ra-head:not(.ra-done):hover { text-decoration: underline; }
.tm-ra-btn { -webkit-appearance: none; appearance: none; background: none; border: 0; padding: 0; margin: 0; cursor: pointer; color: #888; line-height: 0; display: inline-flex; align-items: center; }
.tm-ra-btn:hover { color: #bbb; }
.tm-ra-btn svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.tm-ra-btn.is-loading { cursor: default; }
.tm-ra-btn.is-loading svg { display: none; }
.tm-ra-btn.is-loading::after { content: ""; display: inline-block; width: 11px; height: 11px; border: 2px solid #444; border-top-color: #bbb; border-radius: 50%; animation: tmRaSpin .7s linear infinite; }
@keyframes tmRaSpin { to { transform: rotate(360deg); } }
.tm-track-details ul.ra-list li.ra-hidden { display: none; }
.tm-track-details ul.ra-list li.ra-more-row { margin-top: 2px; }
/* RA event links inherit the default link cyan (same as discogs/bandcamp) */
.tm-track-details ul.ra-list a.ra-more { color: #888; cursor: pointer; }
.tm-track-details .ra-none { color: #777; font-size: .85rem; text-transform: lowercase; margin: 2px 0 0; }

/* ---------- checkbox ---------- */
.tm-check { display: inline-flex; align-items: flex-start; gap: 10px; cursor: pointer; color: var(--grey-2); font-style: normal; }
.tm-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.tm-check .box {
  width: 16px; height: 16px; flex: 0 0 auto; margin-top: 2px;
  border: 1px solid var(--line); border-radius: 3px; background: rgba(0,0,0,.35);
  display: inline-flex; align-items: center; justify-content: center;
}
.tm-check input:checked + .box { border-color: var(--accent); color: var(--accent); }
.tm-check input:checked + .box::after { content: "✓"; font-size: .92rem; font-style: normal; }
.tm-check input:focus-visible + .box { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- select / dropdown ---------- */
.tm-select-wrap { position: relative; display: inline-block; width: 100%; }
.tm-select { appearance: none; -webkit-appearance: none; padding-right: 40px; cursor: pointer; }
.tm-select-wrap::after {
  content: "⌄"; position: absolute; right: 16px; top: 46%; transform: translateY(-50%);
  color: var(--grey-2); pointer-events: none;
}

/* ---------- range slider ---------- */
.tm-range-row { display: flex; align-items: center; gap: 12px; color: var(--grey-2); }
.tm-range { -webkit-appearance: none; appearance: none; height: 8px; flex: 1; border-radius: 999px;
  background: #3a3a3a; outline: none; }
.tm-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 22px;
  border-radius: 3px; background: var(--accent); cursor: pointer; }
.tm-range::-moz-range-thumb { width: 16px; height: 22px; border: none; border-radius: 3px;
  background: var(--accent); cursor: pointer; }

/* ---------- modal / popup ---------- */
.tm-overlay {
  position: fixed; inset: 0; z-index: 50;
  display: none; align-items: center; justify-content: center; padding: 20px;
  background: rgba(15, 15, 15, .72);
}
.tm-overlay.open { display: flex; }
.tm-modal {
  width: 100%; max-width: 480px;
  /* never taller than the viewport (the overlay adds 20px padding top+bottom):
     a tall popup like "how it works" scrolls INSIDE instead of overflowing and
     losing its top off-screen — especially on mobile where it filled the screen. */
  max-height: calc(100svh - 40px); overflow-y: auto;
  /* frosted popup: a denser tint over the dimmed scrim (fixed overlay, so the blur
     is cheap — it never repaints on scroll). Keeps form text crisp. */
  background: var(--frost-bg-modal);
  -webkit-backdrop-filter: var(--frost-blur);
  backdrop-filter: var(--frost-blur);
  border: 1px solid var(--frost-border); border-radius: var(--radius-chip);
  box-shadow: var(--frost-rim), 0 12px 40px rgba(0, 0, 0, .6);
  padding: 0 24px 24px; color: var(--grey-1);
}
/* win98 window title bar: a raised dark strip bleeding to the modal edges. Pinned
   to the top so the title stays put while a long popup body scrolls under it. */
.tm-modal h2 {
  position: sticky; top: 0; z-index: 1;
  margin: 0 -24px 18px; padding: 8px 14px;
  background: var(--btn-face); box-shadow: var(--bevel-out);
  border-bottom: 1px solid #000;
  color: var(--accent); font-size: .95rem; text-align: left; letter-spacing: .03em;
  font-family: var(--font-ui);
}
/* ✕ in the title bar (all modals, via the ui.modal macro). Anchored to the sticky
   h2 so it stays put while a long body scrolls; the h2 is position:sticky, which
   makes it the positioning context. Right padding on the h2 keeps long titles
   from running under it. */
.tm-modal h2 { padding-right: 40px; }
.tm-modal-x {
  position: absolute; top: 50%; right: 8px; transform: translateY(-50%);
  width: 22px; height: 22px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--btn-face); box-shadow: var(--bevel-out);
  border: none; color: var(--grey-2); cursor: pointer;
  font-size: .7rem; line-height: 1; font-family: var(--font-ui);
}
.tm-modal-x:hover { color: var(--accent); }
.tm-modal-x:active { box-shadow: var(--bevel-in); }
.tm-modal p { color: var(--grey-2); }
.tm-modal ol { color: var(--grey-2); padding-left: 18px; }
.tm-modal-actions { display: flex; gap: 12px; margin-top: 18px; justify-content: flex-end; }
/* v1 "welcome back" note inside the auth modal (returning trackmatch v1 user) */
.tm-v1-welcome {
  margin-top: 4px; padding: 12px 14px;
  border: 1px solid var(--accent); border-radius: var(--radius-chip);
  background: rgba(0, 0, 0, .25); font-size: .85rem; line-height: 1.5;
}
.tm-v1-welcome p { margin: 0 0 8px; color: var(--grey-1); }
.tm-v1-welcome p:first-child { color: var(--accent); }
.tm-v1-welcome p:last-child { margin-bottom: 0; }
.tm-v1-welcome a { color: var(--accent); }
/* the in-panel subscribe offer (30 days free) — quiet tick list, no bullets */
.tm-v1-offer-list { list-style: none; padding: 0; margin: 0 0 8px; color: var(--grey-1); line-height: 1.8; }

/* ---------- error + loading screens ---------- */
.tm-error-screen, .tm-loading-screen {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 28px; min-height: 100svh; text-align: center; padding: 24px;
}
/* loading = the record page's glass orb, slightly smaller; clicking cancels.
   The inner box mirrors the record surface so the ✕ pins near the orb (not the
   far viewport corner). */
.tm-loading-inner {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 28px; width: min(360px, 88vw);
}
.tm-loading-screen .tm-record-exit { position: absolute; top: 44px; right: 0; }
.tm-loading-orb { flex: 0 0 auto; }
.tm-loading-orb .tm-record-video { width: 120px; height: 120px; }
.tm-loading-orb { width: 152px; height: 152px; }
/* scans / tracklist / spotify searches: the loading orb floats free — no glass
   button framing (border, sheen, hover ring). It STAYS clickable (stop/restart,
   results.js); only the chrome goes. The LISTEN orb keeps its glass, so at a
   glance a framed orb = listening, a bare orb = scanning/searching. */
.tm-record-btn.tm-loading-orb,
.tm-record-btn.tm-loading-orb:hover,
.tm-record-btn.tm-loading-orb:active {
  background: none; border: none; box-shadow: none; transform: none;
}
.tm-error-screen .msg { color: var(--grey-1); font-style: normal; font-size: 1.1rem; }
.tm-loading-gif { width: 120px; height: auto; }

/* similar-popup loading orb: the loading/listen screens' alpha orb at popup scale,
   stripped of the glass-button chrome (it's a decoration here, not a control).
   Doubled class out-specifies .tm-record-btn's own sizing/glass further down. */
.tm-record-btn.tm-similar-orb, .tm-record-btn.tm-similar-orb:hover {
  width: 96px; height: 96px; cursor: default; pointer-events: none;
  background: none; border: none; box-shadow: none;
}
.tm-similar-orb .tm-record-video { width: 84px; height: 84px; }

/* live scan-log: a single-line terminal readout under the orb — each new find
   replaces the previous one, monospace + centred (an application-load style).
   ABSOLUTELY positioned below the orb so its (variable) height NEVER moves the
   centred orb — a long wrapping title grows downward only. font-size is rem so the
   app-wide text-size preference (--tm-font-scale on <html>) scales it. */
.tm-loading-screen .scan-log {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  margin-top: 14px; width: min(560px, 92vw); min-height: 3.4em;
  display: flex; flex-direction: column; align-items: center;
  font-family: var(--font-mono); font-size: .96rem; line-height: 1.4;
}
.tm-loading-screen .scan-log-inner {
  display: flex; flex-direction: column; align-items: center; gap: 0; max-width: 100%;
}
.tm-loading-screen .scan-log-line {
  color: var(--grey-2); text-align: center; max-width: 100%;
  white-space: normal; overflow-wrap: anywhere;
}
.tm-loading-screen .scan-log-line.found { color: var(--accent); }

/* fading cycle of three caption texts, above the gif (verbatim timings) */
.fade-text {
  position: relative; display: flex; justify-content: center; align-items: center;
  text-align: center; font-size: 1rem; color: #a6a6a6; line-height: 1.6em;
  overflow: hidden; margin: 0 auto; min-height: 3em; width: 100%; max-width: 340px;
}
/* messages are cycled by JS (results.js) — CSS animation-delay proved unreliable
   on iOS (reduce-motion froze it on one message); the fade itself stays CSS */
.fade-text span { position: absolute; width: 100%; opacity: 0; transition: opacity 1.6s ease; }
.fade-text span.on { opacity: 1; }

/* ---------- hero / USP strip ---------- */
.tm-hero { text-align: center; }
.tm-hero-line { color: var(--grey-2); max-width: 520px; margin: 0 auto; }
.tm-hero-line .hl { color: var(--accent); }
.tm-hero .tm-toggle { color: var(--grey-3); }

/* ---------- celebratory result banner (15/15 found · discogs ✓ · bandcamp ✓) ---------- */
.tm-celebrate {
  text-align: center; color: var(--accent);
  padding: 6px 0 2px; letter-spacing: .03em;
}
.tm-celebrate .ok { color: var(--grey-1); }
.tm-celebrate .tick { color: var(--accent); }

/* ---------- load more, shaped exactly like a result track row ---------- */
/* Pull the wrap up into the list's rhythm: the result frame keeps ~38px of bottom
   padding inside it, so without this the button floats well below the last row and
   reads as a separate control.
   Because that pull overlaps the button onto the (transparent) bottom of the results
   iframe, on TOUCH devices the tap used to route into the iframe instead of the
   button (an iframe is a separate browsing context — desktop mouse hit-testing was
   fine, mobile wasn't). Promoting the wrap onto its own compositor layer
   (translateZ) makes touch hit-testing prefer the button. */
#tm-loadmore-wrap { margin-top: -34px; position: relative; z-index: 1; transform: translateZ(0); }
.tm-loadmore-row {
  width: 100%; text-align: center;
  /* match the in-frame .track-row: #c7c7c7 border, sharp 2px corners, no fill */
  border: 1px solid var(--border); border-radius: var(--radius-chip);
  background: transparent; color: var(--grey-1);
  padding: 12px 10px; cursor: pointer; font: inherit; text-transform: lowercase;
}
.tm-loadmore-row:hover { border-color: var(--accent); color: var(--accent); }
.tm-loadmore-row:disabled { opacity: .6; cursor: default; }
.tm-loadmore-row .spin {
  display: inline-block; width: 12px; height: 12px; margin-left: 8px;
  border: 2px solid var(--grey-4); border-top-color: var(--accent);
  border-radius: 50%; vertical-align: middle; animation: tmSpin .8s linear infinite;
}
@keyframes tmSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .tm-loadmore-row .spin { animation: none; } }

/* ---------- dev/test scan-strategy bar (top of dashboard) ---------- */
.tm-scan-testbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 0 16px; padding: 8px 12px;
  border: 1px dashed #3a3a3a; border-radius: var(--radius-chip);
  background: #0c0c0c; color: var(--grey-2);
  font-size: .8rem; text-transform: lowercase;
}
.tm-scan-testbar .tm-stb-tag {
  color: var(--accent); border: 1px solid #2a2a2a; border-radius: var(--radius-chip);
  padding: 2px 7px; letter-spacing: .04em;
}
.tm-scan-testbar select,
.tm-scan-testbar input {
  background: #141414; color: var(--grey-1); border: 1px solid #2a2a2a;
  border-radius: var(--radius-chip); padding: 4px 6px; font: inherit;
}
.tm-scan-testbar input[type="number"] { width: 56px; text-align: right; }
.tm-scan-testbar .tm-stb-fields[hidden] { display: none; }

/* scan-test results panel (dashboard, below the test bar) */
.tm-scan-results {
  margin: 0 0 16px; border: 1px dashed #3a3a3a; border-radius: var(--radius-chip);
  background: #0c0c0c; font-size: .78rem; text-transform: lowercase;
}
.tm-scan-results .tm-sr-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 7px 10px; border-bottom: 1px solid #1c1c1c; color: var(--grey-2);
}
.tm-scan-results .tm-sr-title { color: var(--accent); }
.tm-scan-results .tm-sr-btn {
  background: #141414; color: var(--grey-2); border: 1px solid #2a2a2a;
  border-radius: var(--radius-chip); padding: 3px 9px; margin-left: 6px;
  font: inherit; text-transform: lowercase; cursor: pointer;
}
.tm-scan-results .tm-sr-btn:hover { color: var(--accent); border-color: var(--accent); }
.tm-scan-results .tm-sr-scroll { max-height: 280px; overflow: auto; }
.tm-sr-table { width: 100%; border-collapse: collapse; color: var(--grey-1); }
.tm-sr-table th, .tm-sr-table td {
  text-align: left; padding: 6px 10px; border-bottom: 1px solid #161616;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 240px;
}
.tm-sr-table th { color: var(--grey-3); font-weight: 400; position: sticky; top: 0; background: #0c0c0c; }
.tm-sr-table th.num, .tm-sr-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.tm-sr-table td.method b { color: var(--accent); font-weight: 400; }
.tm-sr-empty { padding: 10px; color: var(--grey-3); text-align: center; }

/* the scan cost/calls readout on the results page (test-bar A/B) */
.tm-scan-readout {
  margin: 6px auto 0; max-width: var(--maxw);
  font-size: .78rem; color: var(--grey-2); text-align: center; text-transform: lowercase;
}
.tm-scan-readout b { color: var(--accent); font-weight: 400; }

/* ---------- disposable-email field shake (signup guard) ---------- */
@keyframes tmShake { 0%,100%{transform:translateX(0)} 20%,60%{transform:translateX(-6px)} 40%,80%{transform:translateX(6px)} }
.tm-shake { animation: tmShake .4s ease; border-color: var(--error) !important; }
@media (prefers-reduced-motion: reduce) { .tm-shake { animation: none; } }

/* ============================================================ win98 function tabs */
/* tabs fill the panel width (equal thirds); same padding/font as the .tm-btn family
   so match|record|feed and upload.txt|search|saved|more read as one control system. */
.tm-tabs { display: flex; gap: 3px; position: relative; z-index: 2; }
.tm-tab {
  flex: 1 1 0; min-width: 0;            /* fill the width, equal share */
  font-family: var(--font-ui); font-size: .85rem; text-transform: lowercase; letter-spacing: .02em;
  cursor: pointer; color: var(--fg); text-align: center;   /* white, matching the .tm-btn text below */
  background: var(--frost-bg-tab);     /* lighter frost tint, matching the panel */
  -webkit-backdrop-filter: var(--frost-blur);
  backdrop-filter: var(--frost-blur);
  box-shadow: none;
  border: 1px solid var(--frost-border);
  border-radius: var(--radius-chip) var(--radius-chip) 0 0;
  padding: 10px 12px; margin-bottom: -1px;     /* overlap the panel's top border */
  transition: color .1s ease, border-color .1s ease;
}
.tm-tab:hover { color: var(--accent); }
.tm-tab.active {
  color: var(--accent); z-index: 3;
  border-color: var(--accent);
  border-bottom-color: transparent;   /* open into the panel below */
}
/* the panel: a frosted "smoked glass" surface — the bg animation still drifts
   through the tint (one panel per viewport, so the blur is cheap here). */
.tm-tabpanel {
  border: 1px solid var(--frost-border);
  background: var(--frost-bg);
  -webkit-backdrop-filter: var(--frost-blur);
  backdrop-filter: var(--frost-blur);
  box-shadow: var(--frost-rim);
  border-radius: 0 var(--radius-chip) var(--radius-chip) var(--radius-chip);
  padding: 16px;
}
.tm-tabsec[hidden] { display: none; }

/* tracklist field with a flush upload | search toolbar attached to its bottom */
.tm-textarea-wrap { display: flex; flex-direction: column; }
.tm-textarea-wrap .tm-textarea {
  border-bottom-left-radius: 0; border-bottom-right-radius: 0; min-height: 200px;
}
.tm-textarea-actions { display: grid; grid-template-columns: 1fr 1fr; }
.tm-textarea-actions > .tm-upload, .tm-textarea-actions > .tm-btn {
  border-radius: 0; min-height: 40px; padding-top: 10px; padding-bottom: 10px;
  border-top: none;   /* slimmer: matches the match/record/feed tab height */
}
.tm-textarea-actions > :first-child { border-bottom-left-radius: var(--radius-chip); border-right: none; }
.tm-textarea-actions > :last-child { border-bottom-right-radius: var(--radius-chip); }

/* mutual-exclusion: filling one input dims + locks the other */
.tm-disabled-soft { opacity: .4; }
.tm-input:disabled, .tm-textarea:disabled { opacity: .4; cursor: not-allowed; }

/* ============================================================ record (in-page) */
.tm-record {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; min-height: 300px; padding: 18px 0; text-align: center;
}
/* passive inline record panel — shown as a tab section when record is the DEFAULT
   view: a centred glass orb + "tap to listen" INSIDE the dashboard (chrome + bg
   intact, nothing dims). Height tracks the match/feed panels so switching tabs
   never jumps; tapping the orb hands off to the active takeover (listen.js). */
.tm-record-inline {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; min-height: 340px; text-align: center;
}
/* the record control: a frosted-glass circular button (shazam-style) that still
   shows the bg animation through it; the glass-icon video screen-blends so its
   black drops out — a png-style cutout floating inside the glass. */
.tm-record-btn {
  width: 190px; height: 190px; border-radius: 50%; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  /* clear glass: mostly see-through (the bg animation reads through it) with a
     soft top-left sheen + a bright rim so it still feels like a button. */
  background: linear-gradient(150deg, rgba(255, 255, 255, .14), rgba(255, 255, 255, .03) 46%, rgba(255, 255, 255, 0) 70%);
  border: 1px solid rgba(255, 255, 255, .30);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, .35), inset 0 0 22px rgba(255, 255, 255, .06), 0 4px 18px rgba(0, 0, 0, .22);
  outline: none; transition: border-color .15s ease, box-shadow .15s ease;
}
.tm-record-btn:hover { border-color: rgba(79, 195, 247, .55); box-shadow: inset 0 1px 1px rgba(255, 255, 255, .35), 0 0 22px rgba(79, 195, 247, .22), 0 4px 18px rgba(0, 0, 0, .22); }
.tm-record-btn:focus, .tm-record-btn:focus-visible { outline: none; }
.tm-record-btn:active { transform: translateY(1px); }
/* real-alpha video — no blend mode (that caused the paused->playing black flash) */
.tm-record-video {
  width: 150px; height: 150px; object-fit: contain; display: block; pointer-events: none;
  /* circle-clip the video box: if a device can't render the alpha channel (or
     autoplay is blocked and a poster frame shows), no square edges ever appear */
  border-radius: 50%;
}
.tm-record-status { color: var(--grey-1); font-style: normal; max-width: 320px; }
/* keep the orb's centre rock-steady through a listen: reserve the status line so
   a longer message ("microphone access denied") can't re-centre the column, and
   pin the occasional try-again chip to the bottom so it's out of the centring
   flow entirely — the orb never moves once pressed, on any state. */
.tm-record-surface .tm-record-status { min-height: 1.4em; display: flex; align-items: center; justify-content: center; }
.tm-record-surface #tm-listen-actions { position: absolute; left: 0; right: 0; bottom: 4px; margin: 0; }

/* ============================================================ feed (soundcloud likes + youtube uploads) */
.tm-feed { min-height: 300px; }
/* source sub-tabs: a slim segmented pair right under the dashboard tabs — the
   main tabs' voice (frost tint, boxy, accent active) at a subordinate size */
.tm-feed-subtabs { display: flex; gap: 3px; margin-bottom: 12px; }
.tm-subtab {
  flex: 1 1 0; min-width: 0;
  font-family: var(--font-ui); font-size: .78rem; text-transform: lowercase; letter-spacing: .02em;
  cursor: pointer; color: var(--grey-2); text-align: center;
  background: var(--frost-bg-tab);
  border: 1px solid var(--frost-border);
  border-radius: var(--radius-chip);
  padding: 6px 10px;
  transition: color .1s ease, border-color .1s ease;
}
.tm-subtab:hover { color: var(--accent); }
.tm-subtab.active { color: var(--accent); border-color: var(--accent); }
/* unseen badge on the shared subtab: a small cyan count of new friend favourites */
.tm-subtab-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; margin-left: 5px; padding: 0 4px;
  border-radius: 9px; background: var(--accent); color: #05171d;
  font-size: .66rem; font-weight: 700; line-height: 1; vertical-align: middle;
}
.tm-feed-setup { display: flex; flex-direction: column; gap: 12px; min-height: 300px; justify-content: center; }
.tm-feed-loading {
  color: var(--grey-3); font-style: normal; min-height: 300px;
  display: flex; align-items: center; justify-content: center;
}
.tm-feed-list { display: flex; flex-direction: column; gap: 14px; max-height: 440px; overflow-y: auto; padding-right: 4px; }
.tm-feed-item { display: flex; flex-direction: column; gap: 6px; }
.tm-feed-title {
  color: var(--grey-1); font-size: .84rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tm-feed-embed {
  width: 100%; max-width: 100%; height: 120px; border: 1px solid #0a0a0a; box-shadow: var(--bevel-in);
  border-radius: var(--radius-chip); background: #111;
}
/* youtube uploads are video-shaped: the same framed box at 16:9 */
.tm-feed-embed-yt { height: auto; aspect-ratio: 16 / 9; }
.tm-tabpanel, .tm-tabsec, .tm-feed, .tm-feed-list { max-width: 100%; box-sizing: border-box; }

/* saved library: the list scrolls past a certain length so 'change default | dash'
   stay pinned + visible below the panel. */
.tm-saved-scroll { max-height: 48vh; overflow-y: auto; padding-right: 4px; }
.tm-feed-scan { width: 100%; }
.tm-feed-more { width: 100%; margin-top: 2px; }   /* reveals the next 10 older likes */
/* quiet footer under the feed: same linklike voice as "how it works" —
   [change username|channel] returns to the input, [change default] pins the
   current sub-tab as the one the feed opens on */
.tm-feed-foot { display: flex; justify-content: center; gap: 16px; margin: 4px 0 2px; flex: none; }
/* suggested channels under each input: one tap saves + loads that channel */
.tm-feed-suggest {
  display: flex; flex-wrap: wrap; gap: 4px 12px; justify-content: center;
  align-items: baseline; margin-top: 2px;
}
.tm-feed-suggest-k { color: var(--grey-4); font-size: .78rem; flex-basis: 100%; text-align: center; }

/* ============================================================ shared tab (social) */
/* setup / choose-username screen */
.tm-share-setup, .tm-share-account { display: flex; flex-direction: column; gap: 12px; min-height: 300px; justify-content: center; }
.tm-share-current { color: var(--grey-3); font-size: .82rem; margin-top: 6px; }
.tm-share-current .tm-share-uname { color: var(--accent); }
/* following/followers counts under the username: cyan, clickable → popup lists */
.tm-share-follows { margin-top: 4px; }
.tm-follows-n { color: var(--accent); font-size: .82rem; }
.tm-follows-n:hover { text-decoration: underline; }
.tm-follows-modal { max-width: 340px; }
.tm-follows-list { display: flex; flex-direction: column; gap: 6px; }
.tm-follows-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 6px 2px; border-bottom: 1px solid var(--line);
}
.tm-follows-row:last-child { border-bottom: none; }
.tm-follows-backbtn { padding: 3px 10px; font-size: .75rem; }
.tm-follows-back { font-size: .78rem; }
/* the same line repeated below the loaded-feed footer, centred */
.tm-share-current-foot { text-align: center; margin-top: 10px; }
.tm-status.tm-share-ok { color: var(--accent); }
.tm-status.tm-share-bad { color: var(--error); }
/* each shared favourite: a small "username | date [| ♥ n]" header above a reused row */
.tm-shared-item { margin-bottom: 6px; }
.tm-shared-head { color: var(--grey-3); font-size: .8rem; padding: 2px 2px 0; }
.tm-shared-head .tm-shared-by { color: var(--accent); cursor: pointer; }
.tm-shared-head .tm-shared-by:hover { text-decoration: underline; }
.tm-shared-head .tm-shared-by.tm-unfollow-armed { color: var(--error); }
/* hearts: the header counter (greyed, shown only when > 0)… */
.tm-heart-count { color: var(--grey-3); font-size: .8rem; white-space: nowrap; }
.tm-heart-count b { font-weight: 400; }
/* …and the heart button itself — far left of the open row's icon set. Idle =
   the wantlist eye's quiet look (black circle, grey glyph); liked = filled. */
.tm-track-row .actions .tm-heart {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; min-width: 26px; border-radius: 50%;
  background: #000; color: var(--grey-1); border: 1px solid var(--grey-1);
  padding: 0; cursor: pointer;
}
.tm-track-row .actions .tm-heart svg { width: 15px; height: 15px; display: block; fill: currentColor; }
.tm-track-row .actions .tm-heart.liked { background: var(--grey-1); color: #111; border-color: var(--grey-1); }
/* private-favourite lock — saved-tracks only, far left of the open row's icons
   (mirrors the shared feed's heart slot). Quiet black circle + grey glyph, matching
   the heart/wantlist look; tap to unlock (share) a private-period favourite. */
.tm-track-row .actions .tm-lock {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; min-width: 26px; border-radius: 50%;
  background: #000; color: var(--grey-1); border: 1px solid var(--grey-1);
  padding: 0; cursor: pointer;
}
.tm-track-row .actions .tm-lock svg { width: 15px; height: 15px; display: block; fill: currentColor; }
.tm-track-row .actions .tm-lock:hover { color: var(--accent); border-color: var(--accent); }
/* the follow-a-user input rows (setup / empty state / footer slot) */
.tm-share-followrow { margin-top: 4px; }
.tm-share-followstatus { text-align: center; }
.tm-share-followslot { margin-bottom: 4px; }
/* "found @ …" source line inside the open row */
/* padding:0 (not 0 2px) so it sits flush-left with the discogs/bandcamp/ra
   headings, which have no padding (the 2px lived only here, causing the indent) */
.tm-shared-src { margin: 8px 0 2px; padding: 0; font-size: .82rem; overflow-wrap: anywhere; }
.tm-shared-src-lead { color: var(--grey-4); }
.tm-shared-src-link, .tm-shared-src-seed { color: var(--link); text-decoration: none; }
.tm-shared-src-link:hover { text-decoration: underline; }
/* the channel links themselves read as links: app-accent cyan (the greyed
   linklike default is for quiet controls like "change default") */
.tm-feed-suggest .tm-linklike { color: var(--accent); }
.tm-feed-suggest .tm-linklike:hover { text-decoration: underline; }

/* desktop dashboard: one stable height — the match tab matches the feed tab's
   loaded height instead of jumping when you switch, and the passive record panel
   matches the match tab (432px total) so no tab switch ever changes the frame */
@media (min-width: 601px) {
  .tm-dashboard .tm-textarea-wrap .tm-textarea { min-height: 344px; }
  .tm-dashboard .tm-record-inline { min-height: 432px; }
}

/* ---------- responsive ---------- */
/* ---------- landing: tagline + the two free-entry CTAs ---------- */
.tm-tagline { color: var(--grey-1); text-align: center; max-width: 560px; margin: 6px auto 0; }
.tm-tagline-sub { display: block; color: var(--grey-3); }
/* the source→result→shops strip: the app's row-icon language in miniature.
   Grey circular badges (like the track-row controls) with hover titles. */
.tm-tagline-icons {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
  color: var(--accent);
}
.tm-tico {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #c7c7c7;
  color: #111;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: default;
}
.tm-tico svg { width: 12px; height: 12px; fill: currentColor; display: block; }
.tm-tico:hover { background: var(--accent); }
/* real-colour service favicons in the same 22px round slots: no grey fill (the
   favicon's own circle IS the badge); hover keeps its cue as an accent ring */
.tm-tico-img, .tm-tico-img:hover { background: transparent; }
.tm-tico-img img { width: 22px; height: 22px; border-radius: 50%; display: block; }
.tm-tico-img:hover { box-shadow: 0 0 0 1px var(--accent); }
.tm-tagline-flow { letter-spacing: .04em; white-space: nowrap; }
/* the strip is one icon wider each side now (spotify in, ra out): tighten the
   gaps on narrow phones so the single line never wraps */
@media (max-width: 420px) { .tm-tagline-icons { gap: 5px; } }
/* the strip's centre column: "track ids" over "+ spotify"; the flanking arrows
   centre on the pair, so they point at the seam between the two lines */
.tm-tagline-mid { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; }
.tm-tagline-plus { display: inline-flex; align-items: center; gap: 3px; color: var(--accent); font-size: .8rem; }
.tm-tico.tm-tico-sm { width: 16px; height: 16px; }
.tm-tico-sm img { width: 16px !important; height: 16px !important; }
/* the front-door wordmark sits at the tagline's size (a flat, minimal lockup) */
.tm-wordmark-fd { font-size: 1rem; letter-spacing: .3px; }
/* secondary row under the CTAs: log in | more info | sign up — three symmetric
   outline chips (the "more" panel's style) spanning the CTA pair's width */
.tm-fd-account { margin-top: 12px; }
.tm-fd-btns { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
.tm-fd-btns .tm-chip { width: 100%; text-align: center; }
.tm-fd-account button.is-open { color: var(--accent); border-color: var(--accent); }

.tm-cta-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 4px; }
.tm-cta {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 16px 12px; cursor: pointer;
  background: var(--btn-face); color: var(--grey-1);
  border: 1px solid var(--line); border-radius: var(--radius-chip);
  box-shadow: var(--bevel-out);
  font-family: var(--font-ui); text-transform: lowercase;
}
.tm-cta:hover { border-color: var(--accent); }
.tm-cta:active { transform: translateY(1px); }
.tm-cta-title { font-size: 1.02rem; color: var(--fg); }
.tm-cta:hover .tm-cta-title { color: var(--accent); }
.tm-cta-sub { font-size: .78rem; color: var(--grey-3); }
.tm-cta.is-active { border-color: var(--accent); }
.tm-cta.is-active .tm-cta-title { color: var(--accent); }
.tm-cta-note { text-align: center; color: var(--grey-4); font-size: .8rem; margin: 8px 0 0; }

/* front-door simple scan field (revealed by "scan a mix") — full CTA-pair width */
.tm-fd-scan { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; animation: tmFdReveal .22s ease; }
.tm-fd-scan .tm-input { width: 100%; }
@keyframes tmFdReveal { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* the two top-level UI screens fade in/out as the record takeover opens/closes */
.tm-fadeable { transition: opacity .22s ease; }

/* record-orb press/exit: the orb PERSISTS while everything around it — header,
   tabs, panel frame, buttons, status line — fades out (enter) or back in (exit).
   The transitions live on the BASE elements so both directions animate: adding
   .tm-dash-vanish fades the chrome away, removing it fades the chrome back. */
.tm-dashboard .tm-dash-head, .tm-dashboard .tm-tabs, .tm-dashboard .tm-btn-pair,
.tm-dashboard .tm-input-status, .tm-dashboard .tm-scan-testbar,
.tm-dashboard .tm-scan-results, .tm-dashboard .tm-record-inline .tm-record-status {
  transition: opacity .26s ease;
}
.tm-dashboard .tm-tabpanel { transition: border-color .26s ease; }
.tm-dash-vanish .tm-dash-head, .tm-dash-vanish .tm-tabs, .tm-dash-vanish .tm-btn-pair,
.tm-dash-vanish .tm-input-status, .tm-dash-vanish .tm-scan-testbar,
.tm-dash-vanish .tm-scan-results, .tm-dash-vanish .tm-record-inline .tm-record-status {
  opacity: 0;
}
.tm-dash-vanish .tm-tabpanel { border-color: transparent; }

/* dashboard header — trackmatch/welcome on the left, sign-up/upgrade/subscribed on the right */
/* top-aligned: the chip/badge lines up with the TOP line (welcome: email) */
.tm-dash-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
/* two-line identity block (welcome + plan caps) — stacks to the button's height */
.tm-dash-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tm-dash-caps {
  /* labels match the "welcome:" grey; only the values are blue */
  font-family: var(--font-ui); font-size: .72rem; color: var(--grey-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tm-dash-caps .hl { color: var(--accent); }
.tm-wordmark-sm { color: var(--fg); font-size: 1rem; letter-spacing: .5px; }
.tm-wordmark-sm:hover { text-decoration: none; color: var(--accent); }
/* left side for a signed-in user: "welcome: <email>", truncated so a long email can't
   push the action off the row */
/* welcome line + the private-digging lock: a flex row so a long email truncates
   with an ellipsis while the lock stays visible (never clipped) */
.tm-welcome-row { display: flex; align-items: center; gap: 5px; min-width: 0; }
.tm-welcome-inline { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--grey-2); font-family: var(--font-ui); font-size: 1rem; }
.tm-welcome-inline .hl { color: var(--accent); }
/* private digging mode indicator — a small GREY lock next to the email (the green
   lock is reserved for the favourite-add success state on the results page) */
.tm-priv-indicator { flex: none; display: inline-flex; align-items: center; color: var(--grey-3); }
.tm-priv-indicator svg { width: 13px; height: 13px; display: block; fill: currentColor; }
/* subscribed badge: boxed like the upgrade chip but slimmer, blue text, non-interactive */
.tm-subscribed {
  flex: none; color: var(--accent); font-family: var(--font-ui); font-size: .85rem;
  white-space: nowrap; border: 1px solid var(--line); border-radius: var(--radius-chip);
  padding: 4px 8px; cursor: default;
}

/* pre-search input feedback strip (top edge of the dashboard / under the fd url) */
.tm-input-status { text-align: center; font-family: var(--font-ui); font-size: .82rem;
  margin: -6px 0 12px; color: var(--grey-3); }
.tm-input-status.ok { color: var(--accent); }
.tm-input-status.warn { color: var(--error); }
.tm-fd-scan .tm-input-status { margin: 8px 0 0; }

/* ---------- record takeover surface (shared front-door + dashboard) ---------- */
.tm-record-surface {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; min-height: 340px; padding: 28px 0; text-align: center;
  transition: opacity .22s ease;
}
.tm-record-exit {
  position: absolute; top: 0; right: 0; width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--line); border-radius: 50%;
  color: var(--grey-3); cursor: pointer; font-size: .9rem; line-height: 1;
  font-family: var(--font-ui); transition: color .15s ease, border-color .15s ease;
}
.tm-record-exit:hover { color: var(--accent); border-color: var(--accent); }

/* ---------- "try it once" block inside the upgrade popup ---------- */
#tm-upgrade-taste { margin: 2px 0 12px; }
.tm-taste-sub { color: var(--grey-3); font-size: .78rem; margin-top: 7px; }
/* quiet "or" divider between the taste door and the subscription pitch */
.tm-taste-or {
  display: flex; align-items: center; gap: 12px; margin: 14px 0 2px;
  color: var(--grey-4); font-size: .75rem;
}
.tm-taste-or::before, .tm-taste-or::after { content: ""; flex: 1; border-top: 1px solid #222; }

/* ---------- how it works ---------- */
/* dashboard popup: instructional — the three tools in one line each, then the
   row-icon legend. A new user reads it in twenty seconds and knows the app. */
.tm-ht { display: flex; flex-direction: column; text-align: left; }
.tm-ht-row { display: flex; gap: 14px; padding: 12px 2px; border-bottom: 1px solid #1c1c1c; align-items: baseline; }
.tm-ht-k { color: var(--accent); flex: 0 0 56px; }
.tm-ht-row p { margin: 0; color: var(--grey-1); font-size: .85rem; line-height: 1.6; }
.tm-ht-sub { color: var(--grey-3); font-size: .8rem; margin: 14px 0 4px; }
.tm-ht-leg { display: flex; align-items: center; gap: 12px; padding: 6px 2px; }
.tm-ht-leg p { margin: 0; color: var(--grey-1); font-size: .82rem; }
.tm-ht-leg .ic {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: center/cover no-repeat; font-weight: bold; line-height: 1;
}
.tm-ht-leg .ic svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.tm-ht-leg .ic-bd { background-image: url("/static/media/favicons/discogs-bandcamp.png"); }
.tm-ht-leg .ic-yt { background-image: url("/static/media/favicons/youtube-grey.png"); }
.tm-ht-leg .ic-sim { background: var(--grey-1); color: #000; border: 1px solid #000; }
.tm-ht-leg .ic-want { background: #000; color: var(--grey-1); border: 1px solid var(--grey-1); }
.tm-ht-leg .ic-fav { background: #888; color: #fff; font-size: 15px; }
.tm-ht-leg .ic-ra { background-image: url("/static/media/favicons/ra.png"); }
/* the little magnifier drawn INLINE in the ra legend line — the same grey glyph
   as the in-row 'resident advisor' search button, so the sentence points at the
   actual control the user will tap */
.tm-ht-ra-search {
  display: inline-flex; vertical-align: -2px; width: 14px; height: 14px; margin: 0 1px;
  color: #888;
}
.tm-ht-ra-search svg { width: 14px; height: 14px; fill: currentColor; display: block; }
/* "(api needed)" opens the discogs token popup right from the legend */
.tm-ht-api { color: var(--grey-3); text-decoration: underline; }
.tm-ht-api:hover { color: var(--accent); }

/* welcome disclosure: the value proposition as three inviting numbered steps */
.tm-hiw { max-width: 460px; margin: 0 auto; text-align: left; }
.tm-hiw-step { display: flex; gap: 12px; align-items: flex-start; padding: 8px 0; }
.tm-hiw-step .n {
  flex: none; width: 22px; height: 22px; border-radius: 50%; margin-top: 2px;
  border: 1px solid var(--accent); color: var(--accent);
  display: inline-flex; align-items: center; justify-content: center; font-size: .8rem;
}
.tm-hiw-step p { margin: 0; color: var(--grey-1); font-size: .88rem; line-height: 1.65; }
.tm-hiw-step b { color: var(--fg); font-weight: 400; }
.tm-hiw-foot { color: var(--grey-4); font-size: .78rem; text-align: center; margin: 10px 0 0; }

/* ---------- trial wall: free vs subscribed comparison + two doors ---------- */
.tm-wall-modal { max-width: 460px; max-height: calc(100svh - 36px); overflow-y: auto; }
.tm-wall-sub { color: var(--grey-2); text-align: center; margin: 0 0 14px; font-size: .9rem; }
.tm-wall-foot { text-align: center; font-size: .76rem; margin: 8px 2px 0; }
/* the note beneath the shared comparison chart (mix scan + discogs/bandcamp
   coverage) — one style everywhere the macro renders */
.tm-compare-note { text-align: center; font-size: .76rem; margin: 8px 2px 0; }
.tm-wall-cta { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 16px; }  /* symmetric doors */
.tm-wall-cta .tm-btn { width: 100%; }
.tm-wall-cta .tm-btn.solid { font-size: 1rem; }       /* emphasise subscribe */
.tm-wall-later { text-align: center; margin-top: 12px; }
.tm-linklike {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--grey-3); font-family: var(--font-ui); font-size: .82rem; text-transform: lowercase;
}
.tm-linklike:hover { color: var(--accent); }

/* emphasise the "subscribed" column in any compare matrix (.tm-compare) */
.tm-compare th.col-sub { color: var(--accent); background: rgba(79, 195, 247, .10); }
.tm-compare td:nth-child(3) { color: var(--fg); background: rgba(79, 195, 247, .06); }
.tm-compare td:nth-child(3) b { color: var(--accent); font-weight: 400; }

.tm-discogs-nudge { color: var(--accent); cursor: pointer; }

/* ---------- landing "more info": matrix · preview · benchmark ---------- */
.tm-li { text-align: left; }

.tm-li-table { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.tm-li-table table { width: 100%; border-collapse: collapse; }
.tm-li-table th, .tm-li-table td { border-bottom: 1px solid #2a2a2a; padding: 9px 12px; font-size: .82rem; text-align: left; }
.tm-li-table tr:last-child td { border-bottom: 0; }
.tm-li-table th { color: var(--fg); background: #0a0a0a; font-weight: 400; }
.tm-li-table td { color: var(--fg); }
.tm-li-table .col-plan { width: 46%; }
.tm-li-table .col-free, .tm-li-table .col-sub { width: 27%; text-align: center; }
.tm-li-table td:nth-child(2), .tm-li-table td:nth-child(3) { text-align: center; }
.tm-li-table .yes { color: var(--accent); font-weight: bold; }
.tm-li-table .no { color: var(--grey-2); }
.tm-li-foot { color: var(--grey-4); font-size: .74rem; margin: 8px 2px 0; text-align: center; }

.tm-li-mission { color: var(--grey-2); text-align: center; font-size: .9rem; margin: 0 auto 16px; max-width: 460px; }

/* interactive result preview (mirrors the real results page, scoped to the landing) */
.tm-li-preview { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; margin-top: 14px; text-align: left; }
.tm-li-preview-head { color: var(--fg); text-align: center; font-size: .9rem; margin-bottom: 10px; }
.tm-li-preview-foot { color: var(--grey-3); font-size: .78rem; text-align: center; margin: 12px 0 0; }

/* rows mirror the results pages' borderless design: quiet hairline-separated
   lines, not boxes; an open row hands its hairline to the block below it */
.tm-li-preview .result-block { margin: 0; }
.tm-li-preview .track-row {
  border: 0; border-bottom: 1px solid #1c1c1c; border-radius: 0; padding: 10px 2px;
  display: flex; align-items: center; justify-content: space-between; cursor: pointer; gap: 8px;
}
.tm-li-preview .result-block.tm-open .track-row { border-bottom-color: transparent; }
.tm-li-preview .result-block.tm-open { border-bottom: 1px solid #1c1c1c; }
.tm-li-preview .track-row .title { flex: 1; min-width: 0; color: var(--grey-1); font-size: .82rem; }
.tm-li-preview .preview-wrap { display: flex; align-items: center; gap: 6px; flex: none; }

/* marketplace badge = the split discogs|bandcamp favicon (letters hidden) */
.tm-li-preview .icon-bd {
  flex: none; width: 24px; height: 24px; border-radius: 50%; overflow: hidden; display: inline-flex;
  background: url("/static/media/favicons/discogs-bandcamp.png") center/cover no-repeat;
}
.tm-li-preview .icon-bd .bd-b, .tm-li-preview .icon-bd .bd-d { visibility: hidden; flex: 1; }

.tm-li-preview .preview-btn, .tm-li-preview .want-btn, .tm-li-preview .fav-btn,
.tm-li-preview .similar-btn {
  flex: none; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; min-width: 24px; padding: 0; border: none; border-radius: 50%;
  background: var(--grey-3); color: #fff; cursor: pointer; font-size: 15px; font-weight: bold;
  font-family: var(--font-ui); line-height: 1;
}
/* the magnifier matches the app's row control: grey fill, black glass */
.tm-li-preview .similar-btn { background: var(--grey-1); color: #000; border: 1px solid #000; }
.tm-li-preview .similar-btn svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.tm-li-preview .preview-btn svg { width: 13px; height: 13px; fill: currentColor; display: block; }
/* idle preview = the grey youtube favicon (its white triangle is the play
   glyph); the playing state below swaps back to the grey eq-dots look */
.tm-li-preview .preview-btn {
  background: transparent url("/static/media/favicons/youtube-grey.png") center/cover no-repeat;
}
.tm-li-preview .preview-btn svg { display: none; }
.tm-li-preview .result-block.is-playing .preview-btn { background: var(--grey-3); }

/* now-playing dotted wave (shown while a preview is open) */
.tm-li-preview .preview-btn .tm-eq { display: none; align-items: center; justify-content: center; gap: 2.5px; }
.tm-li-preview .preview-btn .tm-eq i { width: 3px; height: 3px; border-radius: 50%; background: currentColor; }
.tm-li-preview .result-block.is-playing .preview-btn svg { display: none; }
.tm-li-preview .result-block.is-playing .preview-btn .tm-eq { display: inline-flex; }
@keyframes tmEqDot { 0%, 100% { transform: translateY(3px); } 50% { transform: translateY(-3px); } }
.tm-li-preview .result-block.is-playing .preview-btn .tm-eq i:nth-child(1) { animation: tmEqDot 1.12s ease-in-out infinite; }
.tm-li-preview .result-block.is-playing .preview-btn .tm-eq i:nth-child(2) { animation: tmEqDot 1.12s ease-in-out infinite .17s; }
.tm-li-preview .result-block.is-playing .preview-btn .tm-eq i:nth-child(3) { animation: tmEqDot 1.12s ease-in-out infinite .34s; }

/* magnifier + wantlist eye + favourite "+" only show once a row is open */
.tm-li-preview .want-btn, .tm-li-preview .fav-btn, .tm-li-preview .similar-btn { display: none; }
.tm-li-preview .result-block.tm-open .want-btn, .tm-li-preview .result-block.tm-open .fav-btn,
.tm-li-preview .result-block.tm-open .similar-btn { display: inline-flex; }
/* the feature-explainer line (fav / wantlist taps teach what they do) */
.tm-li-preview-msg { color: var(--grey-3); font-size: .78rem; text-align: center; margin: 10px 0 0; }
.tm-li-preview .want-btn { background: #000; color: var(--grey-1); border: 1px solid var(--grey-1); }
.tm-li-preview .want-btn svg { width: 14px; height: 14px; fill: currentColor; display: block; }
.tm-li-preview .want-btn.saved { background: var(--grey-1); color: #111; cursor: default; }
.tm-li-preview .fav-btn.saved { background: var(--grey-1); color: #111; cursor: default; }

.tm-li-preview .tm-youtube { margin-top: 6px; }
.tm-li-preview .tm-youtube iframe { display: block; border: 0; border-radius: var(--radius-chip); }
.tm-li-preview .tm-details { margin-top: 8px; }
.tm-li-preview .tm-details h3 { margin: 6px 0 2px; font-size: .8rem; color: var(--grey-2); font-weight: 400; }
.tm-li-preview .tm-details ul { list-style: none; padding: 0; margin: 0 0 6px; }
.tm-li-preview .tm-details a { color: var(--link); font-size: .8rem; }

/* the preview's LIVE resident advisor lookup — same voice as the in-app row:
   the whole header line is the trigger, the little magnifier is the tell */
.tm-li-preview .tm-pv-ra-head { display: flex; align-items: center; gap: 5px; cursor: pointer; padding: 3px 0; }
.tm-li-preview .tm-pv-ra-head.is-done { cursor: default; }
.tm-li-preview .tm-pv-ra-head:not(.is-done):hover { text-decoration: underline; }
.tm-li-preview .tm-pv-ra-btn { display: inline-flex; align-items: center; color: var(--grey-3); line-height: 0; }
.tm-li-preview .tm-pv-ra-btn svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.tm-li-preview .tm-pv-ra-head[data-busy="1"] .tm-pv-ra-btn svg { display: none; }
.tm-li-preview .tm-pv-ra-head[data-busy="1"] .tm-pv-ra-btn::after {
  content: ""; display: inline-block; width: 11px; height: 11px;
  border: 2px solid #444; border-top-color: var(--grey-2); border-radius: 50%;
  animation: tmPvRaSpin .7s linear infinite;
}
@keyframes tmPvRaSpin { to { transform: rotate(360deg); } }
.tm-li-preview .tm-pv-ra-out .tm-pv-ra-more { color: var(--grey-3); }
.tm-li-preview .tm-pv-ra-out .tm-pv-ra-none { color: var(--grey-4); font-size: .8rem; margin: 2px 0 6px; }
.tm-li-preview .tm-pv-ra-out ul { list-style: none; padding: 0; margin: 0 0 6px; }
.tm-li-preview .tm-pv-ra-out a { font-size: .8rem; }

.tm-li-bench { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 16px; text-align: center; }
.tm-li-bench-item { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 8px; }
.tm-li-bench-item.is-best { border-color: var(--accent); }
.tm-li-bench-item .name { color: var(--grey-2); font-size: .8rem; margin-bottom: 8px; }
.tm-li-bench-item.is-best .name { color: var(--fg); }
.tm-li-bench-item .stat { color: var(--grey-3); font-size: .72rem; line-height: 1.6; }
.tm-li-bench-item.is-best .stat { color: var(--grey-1); }
.tm-li-bench-item .stat b { color: var(--accent); font-weight: 400; }
.tm-li-bench-cap { color: var(--grey-4); font-size: .72rem; text-align: center; margin-top: 8px; letter-spacing: .5px; }

/* ---- desktop: every app-page tab state is the SAME height, so switching
   search / listen / feed, or going to the saved library, never resizes the panel.
   466px is the natural height of the search / listen tabs (textarea-driven); the
   short states (feed's pre-connect prompt, a near-empty saved list) flex-fill up to
   it. Mirrors how the phone layout flex-fills to the full screen (below). ---- */
@media (min-width: 641px) {
  /* every app-page tab state is the same, viewport-adaptive height (466px on a
     normal window so feed/saved match the dashboard, shrinking on short windows).
     A FIXED height (not min-height) means the saved list scrolls INSIDE the panel
     instead of growing it and pushing the dash / change-default footer off screen. */
  .tm-shell.tm-app .tm-tabpanel { height: min(466px, calc(100svh - 190px)); display: flex; flex-direction: column; }
  .tm-shell.tm-app .tm-tabsec:not([hidden]) { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  .tm-shell.tm-app .tm-textarea-wrap { flex: 1 1 auto; }
  .tm-shell.tm-app .tm-saved-scroll { flex: 1 1 auto; max-height: none; min-height: 0; }
  /* the feed's source panel joins the flex chain and its list scrolls INSIDE the
     fixed-height tab panel (the old fixed 440px cap out-sized the panel and bled
     over the buttons below) — same treatment as the saved list above */
  .tm-shell.tm-app .tm-feed-src:not([hidden]) { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  .tm-shell.tm-app .tm-feed-src > .tm-feed-setup,
  .tm-shell.tm-app .tm-feed-src > .tm-share-setup,
  .tm-shell.tm-app .tm-feed-src > .tm-share-account,
  .tm-shell.tm-app .tm-feed-src > .tm-feed-loading { flex: 1 1 auto; min-height: 0; }
  .tm-shell.tm-app .tm-feed-list { flex: 1 1 auto; max-height: none; min-height: 0; }
}

@media (max-width: 640px) {
  .tm-card-grid { grid-template-columns: 1fr; }
  .tm-input, .tm-textarea, .tm-btn, .tm-select, .tm-upload { font-size: .95rem; padding: 12px 14px; }
  .tm-shell { padding: 28px 14px; }

  /* ---- app pages fill the phone: slim vertical padding, the dashboard / saved
     library stretch so the tab panel takes the free height (the feed already
     did this by content; match + saved now match it) ---- */
  .tm-vcenter { padding: 10px 0; }
  /* FIXED height (not min-height) so a long saved list scrolls INSIDE the shell
     instead of growing it and pushing the dash / change-default footer off screen. */
  .tm-vcenter > .tm-shell.tm-app { display: flex; flex-direction: column; height: calc(100svh - 20px); }
  .tm-shell.tm-app > #tm-frontdoor { margin: auto 0; }          /* front door stays centred */
  .tm-shell.tm-app #tm-dashboard:not([hidden]) { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .tm-shell.tm-app .tm-tabpanel { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  .tm-shell.tm-app .tm-tabsec:not([hidden]) { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  .tm-shell.tm-app .tm-textarea-wrap { flex: 1 1 auto; }
  .tm-shell.tm-app .tm-textarea-wrap .tm-textarea { flex: 1 1 auto; min-height: 140px; }
  /* min-height:0 lets the scroll area shrink below its content so it scrolls */
  .tm-shell.tm-app .tm-saved-scroll { max-height: none; flex: 1 1 auto; min-height: 0; }
  /* feed panel: same containment as the saved list (see the desktop block above) */
  .tm-shell.tm-app .tm-feed-src:not([hidden]) { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  .tm-shell.tm-app .tm-feed-src > .tm-feed-setup,
  .tm-shell.tm-app .tm-feed-src > .tm-share-setup,
  .tm-shell.tm-app .tm-feed-src > .tm-share-account,
  .tm-shell.tm-app .tm-feed-src > .tm-feed-loading { flex: 1 1 auto; min-height: 0; }
  .tm-shell.tm-app .tm-feed-list { max-height: none; flex: 1 1 auto; min-height: 0; }

  /* save control: centred vertical stack, [title] first — right under the finger
     that just pressed save — then [custom], then [saved] */
  #tm-save-slot { flex-direction: column; align-items: center; gap: 8px; }
  #tm-save-usetitle { order: 1; }
  #tm-save-custom { order: 2; }
  #tm-save-goto { order: 3; }

  /* results tail matches the engine's MOBILE column (measured: title x = 18px) */
  .tm-results-tail { padding: 0 18px; }

  /* results title: two lines max, then an ellipsis */
  .tm-topbar-title {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* the record surface centres in the stretched app shell (it was pinned to the
     top once the shell became a full-height flex column) */
  .tm-shell.tm-app > .tm-record-surface { margin: auto 0; }

  /* settings: one narrower shared select width so label + control always fit the
     modal on a phone AND the two dropdowns' edges stay in one aligned column */
  .tm-set-main .tm-select-wrap { flex: 0 0 150px; width: 150px; }
  .tm-tab { padding: 8px 16px; }
  .tm-li-bench { grid-template-columns: repeat(2, 1fr); }
  .tm-li-table th, .tm-li-table td { padding: 7px 7px; font-size: .76rem; }
}

/* ---------- the deploy curtain (admin-toggled maintenance overlay) ---------- */
/* sits above every overlay/modal (they top out at z-index 60) so nothing on the
   dashboard is reachable underneath while an update ships */
.tm-maintenance {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(0, 0, 0, .94);
  display: flex; align-items: center; justify-content: center;
  padding: 24px; text-align: center;
}
.tm-maintenance-msg { color: var(--grey-1); font-size: 15px; letter-spacing: .04em; }
