* { margin: 0; padding: 0; box-sizing: border-box; }

/* AnimeCha (cha = tea) palette: tea-garden dark surfaces, matcha secondary
   accent, and the three verdicts as tea colors — 🍵 savored matcha green
   (the tea you drank and loved), 🌿 steeping amber (the brew working in the
   pot), ✂️ pruned clay (the kyusu). Flipped 2026-07-06 on user direction. */
:root {
  --bg: #0e130f;
  /* the garden table at dusk: a matcha-green pool lit by a warm golden-brew
     lamp drifting off the upper-right, with cool pine-teal evening air
     pooling lower-left. Layered + asymmetric so it reads as natural light,
     not a flat olive wash. One token so boot/landing/app never seam. */
  --table-bg:
    radial-gradient(ellipse 82% 58% at 64% 24%, rgba(233, 181, 112, 0.07) 0%, transparent 58%),
    radial-gradient(ellipse 68% 54% at 34% 84%, rgba(58, 122, 110, 0.06) 0%, transparent 60%),
    radial-gradient(ellipse 132% 112% at 50% 44%, #213524 0%, rgba(22, 35, 23, 0.5) 48%, transparent 80%),
    radial-gradient(ellipse at 50% 46%, #131d15 0%, #0e130f 72%, var(--bg) 100%);
  --surface: #181f19;
  --surface-2: #222b23;
  --text: #e9ede8;
  --text-dim: #9cab9d;
  --accent: #e5a04b;   /* golden brew */
  --accent-2: #8fd48c; /* matcha */
  --like: #6fbf73;     /* 🍵 savored */
  --skip: #c96a52;     /* ✂️ pruned */
  --watch: #e8a34c;    /* 🌿 steeping */
  /* the same verdict hues as raw channels, for glows/washes/tints at any
     alpha: rgba(var(--like-rgb), 0.3). ALWAYS derive verdict-tinted effects
     from these tokens — never restate the numbers. */
  --like-rgb: 111, 191, 115;
  --skip-rgb: 201, 106, 82;
  --watch-rgb: 232, 163, 76;
  --accent-rgb: 229, 160, 75;
  --accent-2-rgb: 143, 212, 140;
  --radius: 16px;
  /* the deck footprint — card stack AND desktop piles share these exact
     dimensions (spacing between them assumes it). Also used INSTEAD of
     width/height:100% inside the pile buttons: Safari resolves percentage
     heights on <button> children against an indefinite anonymous inner box
     (→ 0, piles collapse to a line), explicit lengths dodge that. */
  --deck-w: min(80vw, 330px);
  --deck-h: min(56vh, 490px);
}

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  overflow: hidden;
}

/* scrollbars follow the site's dark palette everywhere */
* { scrollbar-width: thin; scrollbar-color: #2b352c transparent; }
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #2b352c; border-radius: 5px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: #384a3a; }
/* in the details modal the scrollbar must not read as a border — hairline thumb, no track */
.panel-inner.modal { scrollbar-width: thin; scrollbar-color: rgba(233, 237, 232, 0.12) transparent; }
.panel-inner.modal::-webkit-scrollbar { width: 5px; }
.panel-inner.modal::-webkit-scrollbar-thumb { background: rgba(233, 237, 232, 0.12); border: none; border-radius: 3px; }

.hidden { display: none !important; }
.dim { color: var(--text-dim); }
.small { font-size: 0.8rem; }
h3 { font-size: 0.95rem; margin: 18px 0 8px; }
a { color: var(--accent-2); }
:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; border-radius: 4px; }

/* ============ boot placeholder ============
   A mid-session refresh shows this instead of flashing the homescreen:
   a blank card breathing on the stage, lit by a slowly-cycling pastel
   rainbow (the card-glow treatment, promoted to a loading state). */
.boot {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--table-bg);
}
/* the inline pre-route script stamps <html class="pre-app"> BEFORE first
   paint — boot shows and the landing never gets a frame */
html.pre-app #boot { display: flex !important; }
html.pre-app #landing { display: none !important; }
.boot-stack {
  position: relative;
  width: min(80vw, 330px);
  height: min(56vh, 490px);
  min-height: 280px;
}
.boot-stack::before {
  content: "";
  position: absolute;
  inset: -16px -26px;
  border-radius: 40px;
  background: linear-gradient(120deg, #f2b8c6, #f6dcb0, #c4e8c0, #b9d7f2, #dcc4f4, #f2b8c6);
  filter: blur(44px) saturate(1.15);
  opacity: 0.45;
  animation: boot-hue 5s linear infinite;
}
.boot-card {
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  /* reads as a CARD: clearly lighter than the table, rim catch-light on top */
  background: linear-gradient(165deg, #2f3b31, #1d251e);
  border: 1px solid #40503f;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 30px rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: boot-breathe 2.4s ease-in-out infinite;
}
.boot-card::after { content: "🍵"; font-size: 2.4rem; opacity: 0.35; }
@keyframes boot-hue { to { filter: blur(44px) saturate(1.15) hue-rotate(360deg); } }
@keyframes boot-breathe { 50% { transform: scale(1.014); } }

/* ============ landing ============ */
.landing {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--table-bg);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.landing.leaving { opacity: 0; transform: scale(1.06); pointer-events: none; }
.landing-inner { text-align: center; max-width: 640px; width: 94vw; padding: 24px; }
.logo { font-size: 2.2rem; }
.landing-sessions { position: absolute; top: 14px; right: 16px; z-index: 2; }
.tagline { color: var(--text-dim); margin: 10px 0 30px; }

.search-form { display: flex; flex-direction: column; gap: 16px; position: relative; }
/* ambient brew-glow behind the search vessel — the landing's own stack-glow:
   a soft golden halo that says "this is where the tea gets made" */
.search-form::before {
  content: "";
  position: absolute;
  inset: -46px -60px auto;
  height: 160px;
  background: radial-gradient(closest-side, rgba(var(--accent-rgb), 0.2), transparent 70%);
  filter: blur(26px);
  z-index: -1;
  pointer-events: none;
}
/* the search VESSEL: one glass capsule holding the mark, the input and the
   Brew button — nothing can misalign because the capsule is the only thing
   with edges. Lit like the cards: top rim highlight, deep drop shadow,
   golden warm-up on focus. */
.search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 9px 9px 22px;
  background: linear-gradient(180deg, rgba(40, 50, 41, 0.9), rgba(22, 29, 23, 0.94));
  border: 1px solid #3a473b;
  border-radius: 999px; /* a true pill — the hero control of the landing */
  box-shadow: inset 0 1px 0 rgba(233, 237, 232, 0.07),
    inset 0 -14px 24px rgba(0, 0, 0, 0.18),
    0 18px 44px rgba(0, 0, 0, 0.4);
  transition: border-color 0.2s, box-shadow 0.25s;
}
.search-row:focus-within {
  border-color: rgba(var(--accent-rgb), 0.5);
  box-shadow: inset 0 1px 0 rgba(233, 237, 232, 0.08),
    inset 0 -14px 24px rgba(0, 0, 0, 0.18),
    0 0 0 1px rgba(var(--accent-rgb), 0.2),
    0 0 52px rgba(var(--accent-rgb), 0.2),
    0 18px 44px rgba(0, 0, 0, 0.4);
}
/* the steeping mark: quiet 🔍 anchoring the reading direction of the pill */
.search-mark { flex-shrink: 0; font-size: 1.05rem; opacity: 0.45; pointer-events: none; }
.search-row:focus-within .search-mark { opacity: 0.8; }
/* input[type="text"] to OUT-RANK the global text-input skin (same
   specificity, later in the file — it was re-imposing its border, surface
   and 8px top margin inside the pill) */
.search-row input[type="text"] {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  color: var(--text);
  margin: 0;
  padding: 14px 10px 14px 0;
  font-size: 1rem;
  outline: none;
}
.search-row input::placeholder { color: var(--text-dim); opacity: 0.75; }
/* the VESSEL carries the focus affordance (focus-within glow) — the global
   :focus-visible outline would double-border the glass from inside */
.search-row input[type="text"]:focus-visible { outline: none; }
/* the Brew button: a golden ceramic pill inside the vessel — lit from above
   (inset highlight), heavy at the base (inset shade), haloed like the piles.
   Hover blooms the glow; press sinks it. Skeuomorphic per the house style.
   Scoped as .search-row .search-go: the generic .primary-btn skin is defined
   LATER in this file, and at equal specificity its 10px radius/13px padding
   silently squared this pill off. */
.search-row .search-go {
  margin-top: 0;
  padding: 13px 28px;
  border-radius: 999px;
  flex-shrink: 0;
  background: linear-gradient(180deg, #f4ba70, var(--accent) 55%, #c8843a);
  color: #241505;
  font-weight: 700;
  font-size: 0.95rem;
  white-space: nowrap;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
  box-shadow: 0 2px 14px rgba(var(--accent-rgb), 0.3),
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -2px 3px rgba(0, 0, 0, 0.22);
  transition: box-shadow 0.2s, filter 0.2s, transform 0.12s;
}
.search-row .search-go:hover:not(:disabled) {
  filter: brightness(1.07);
  box-shadow: 0 2px 30px rgba(var(--accent-rgb), 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.45),
    inset 0 -2px 3px rgba(0, 0, 0, 0.22);
}
.search-row .search-go:active:not(:disabled) { transform: translateY(1px); }
.search-row .search-go:disabled { cursor: default; filter: saturate(0.65) brightness(0.92); }
.search-row .search-go .spinner { border-color: rgba(36, 21, 5, 0.25); border-top-color: #241505; }
.search-examples { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.example-chip {
  background: none;
  border: 1px solid #354236;
  color: var(--text-dim);
  border-radius: 16px;
  padding: 6px 13px;
  font-size: 0.78rem;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.example-chip:hover { border-color: var(--accent-2); color: var(--accent-2); }
.ghost-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 0.88rem;
  cursor: pointer;
  margin-top: 26px;
  text-decoration: underline dotted;
  text-underline-offset: 4px;
}
.ghost-btn:hover { color: var(--text); }
.ai-status { font-size: 0.72rem; color: var(--text-dim); margin-top: 20px; }
.ai-status b { color: var(--accent-2); font-weight: 600; }

/* ============ intro ============ */
.intro {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(10, 12, 18, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
}
.intro-inner {
  background: var(--surface);
  border-radius: 20px;
  padding: 28px;
  max-width: 520px;
  width: 94vw;
  max-height: 92vh;
  overflow-y: auto;
}
.intro-inner h2 { margin-bottom: 18px; }
.intro-rows { display: flex; flex-direction: column; gap: 16px; }
.intro-row { display: flex; gap: 16px; align-items: center; }
.intro-text { font-size: 0.85rem; line-height: 1.45; color: var(--text-dim); }
.intro-text b { color: var(--text); }
.intro-demo {
  flex: 0 0 84px;
  height: 74px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  border-radius: 12px;
  overflow: hidden;
}
.demo-card {
  width: 34px;
  height: 48px;
  background: linear-gradient(160deg, var(--accent), var(--accent-2));
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
}
.demo-left .demo-card { animation: demo-left 2s ease-in-out infinite; }
.demo-right .demo-card { animation: demo-right 2s ease-in-out infinite; }
.demo-up .demo-card { animation: demo-up 2s ease-in-out infinite; }
@keyframes demo-left  { 0%, 20% { transform: none; opacity: 1; } 60%, 100% { transform: translateX(-56px) rotate(-14deg); opacity: 0; } }
@keyframes demo-right { 0%, 20% { transform: none; opacity: 1; } 60%, 100% { transform: translateX(56px) rotate(14deg); opacity: 0; } }
@keyframes demo-up    { 0%, 20% { transform: none; opacity: 1; } 60%, 100% { transform: translateY(-48px); opacity: 0; } }
.intro-note { font-size: 0.75rem; color: var(--text-dim); margin: 18px 0 14px; }

/* ============ topbar ============ */
.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 16px;
  height: 52px;
}
.logo-sm { font-size: 1rem; font-weight: 700; cursor: pointer; }
.topbar-actions { display: flex; gap: 6px; flex-wrap: wrap; }
/* ♻ resteep badge — the on-deck trace of a LIVE resteep, tucked under the
   deck counter. Steep-verdict tinted (the pile it borrows from), the undo
   CTA inline; the whole pill is the button. Hidden unless resteeping. */
.resteep-badge {
  pointer-events: auto;
  cursor: pointer;
  font-size: 0.7rem;
  color: rgba(var(--watch-rgb), 0.9);
  background: rgba(34, 43, 35, 0.85);
  border: 1px solid rgba(var(--watch-rgb), 0.35);
  border-radius: 12px;
  padding: 3px 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60vw;
}
.resteep-badge b { color: var(--text); font-weight: 600; margin-left: 4px; }
.resteep-badge:hover { border-color: var(--watch); color: var(--watch); }
.chip-btn {
  background: var(--surface-2);
  color: var(--text);
  border: none;
  border-radius: 18px;
  padding: 7px 12px;
  font-size: 0.8rem;
  cursor: pointer;
}
.chip-btn:hover { background: #2f3446; }
.chip-btn.danger { color: #ff8080; }
/* round chip: a chip-btn compressed to a circle, for single-glyph topbar
   actions (🔍 Search) — same surface and hover as its pill siblings */
.chip-btn.round {
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  font-size: 0.85rem;
  line-height: 1;
}
/* the key legend at the bottom draws its keys as actual keycaps */
.hint kbd {
  display: inline-block;
  min-width: 21px;
  padding: 2px 5px 3px;
  border-radius: 5px;
  background: linear-gradient(180deg, #2b342c, #1c231d);
  border: 1px solid #3a463b;
  border-bottom-width: 3px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09);
  color: var(--text);
  font-family: inherit;
  font-size: 0.68rem;
  line-height: 1;
  text-align: center;
  vertical-align: 1px;
}
.btn-count {
  background: var(--accent);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  border-radius: 9px;
  padding: 1px 6px;
  margin-left: 6px;
  vertical-align: 1px;
}

/* ============ stage: deck is fixed dead-center of the screen ============ */
.app { height: 100%; }

/* the garden table: a quiet mossy center glow. On .app (not .stage) so it
   fills the WHOLE page — on .stage it started below the topbar and its
   brighter top edge drew a hard seam at the 52px line. */
.app, .table-view {
  background: var(--table-bg);
}
.stage { position: relative; height: calc(100% - 52px); }

/* opening the table view: stacks fly up off the table… */
.pile, .card-stack, .controls, .hint, .deck-count {
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.app.to-table .pile-left  { transform: translate(-60%, -170%) rotate(-4deg); opacity: 0; }
.app.to-table .pile-right { transform: translate(-40%, -170%) rotate(4deg); opacity: 0; }
.app.to-table .pile-top   { transform: translate(-50%, -190%); opacity: 0; }
.app.to-table .card-stack { transform: translate(-50%, -170%) rotate(2deg); opacity: 0; }
.app.to-table .controls   { transform: translateX(-50%) translateY(60px); opacity: 0; }
.app.to-table .hint, .app.to-table .deck-count { opacity: 0; }
.pile {
  background: none;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  z-index: 5;
  transition: transform 0.15s, color 0.15s;
  /* the top card is draggable back to the deck (attachPileDrag) — claim the
     touch gesture so dragging a pile never scrolls the page on mobile */
  touch-action: none;
}
.pile:hover { transform: scale(1.06); color: var(--text); }
.pile-top { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); }
.pile-top:hover { transform: translateX(-50%) scale(1.06); }
.pile-left { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); }
.pile-left:hover { transform: translateY(-50%) scale(1.06); }
.pile-right { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); }
.pile-right:hover { transform: translateY(-50%) scale(1.06); }
.pile-cards { position: relative; width: 46px; height: 62px; display: block; }
.pile-cards img {
  position: absolute;
  left: 0; /* buttons center inline content; absolute imgs must not inherit that static position */
  top: 0;
  width: 42px;
  height: 58px;
  object-fit: cover;
  border-radius: 5px;
  border: 1px solid #354236;
  background: var(--surface-2);
  /* kill the browser's native image-drag: without this, grabbing a pile card
     starts an OS drag-and-drop of the <img> (the ghost thumbnail), which fires
     pointercancel and kills our return-drag mid-gesture */
  -webkit-user-drag: none;
  user-select: none;
}
.pile-cards img:nth-child(1) { transform: rotate(-7deg) translate(-4px, 2px); }
.pile-cards img:nth-child(2) { transform: rotate(4deg) translate(3px, 0); }
.pile-cards img:nth-child(3) { transform: rotate(-1deg); }
.pile.empty .pile-cards { border: 1.5px dashed #354236; border-radius: 6px; }
/* pile titles: faded bold wordmarks in each pile's verdict hue (no pill
   chrome — the ink sits straight on the felt, like lettering on the table);
   the count rides along as a tag pill (the .genre-chip look, verdict-tinted) */
.pile-label {
  font-size: 1.08rem;      /* 1.5× the old 0.72rem */
  font-weight: 600;        /* not heavy — it should whisper, not shout */
  letter-spacing: 2.4px;   /* tracking scaled 1.5× too, so the spacing is preserved */
  text-transform: uppercase;
  white-space: nowrap;
  /* no drop shadow: the lettering sits flat on the felt, only just brighter */
  transition: color 0.2s ease;
}
/* at rest the wordmark barely rises above the felt as a faint green — readable
   but nearly submerged; hovering the pile lifts that pile's verdict hue to full */
.pile-label { color: rgba(var(--accent-2-rgb), 0.22); }
.pile-left:hover  .pile-label { color: var(--skip); }
.pile-right:hover .pile-label { color: var(--like); }
.pile-top:hover   .pile-label { color: var(--watch); }
.pile-label b {
  /* no border, no shadow — just a quiet filled count pill riding the wordmark */
  background: var(--surface-2);
  border-radius: 15px;
  padding: 2px 9px;
  margin-left: 7px;
  font-size: 0.68em;
  font-weight: 700;
  letter-spacing: 0.5px;
  vertical-align: 2px;
}

/* ============ card deck ============ */
.card-stack {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: var(--deck-w);
  height: var(--deck-h);
  min-height: 280px;
  z-index: 7; /* deck and flying cards pass over the side piles */
}
/* deck look: straight-stacked card edges peeking out below */
.card-stack::before, .card-stack::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--surface-2);
  border-radius: var(--radius);
  z-index: -3;
}
.card-stack::before { transform: translateY(30px) scale(0.905); opacity: 0.6; }
.card-stack::after { transform: translateY(38px) scale(0.87); opacity: 0.35; z-index: -4; }

/* ambient light behind the top card — the details player's halo, deck-sized.
   A fresh element per deal, so the fade-in replays on every new card. */
.stack-glow {
  position: absolute;
  inset: -16px -26px;
  z-index: -5; /* pure backdrop: behind cards, deck edges AND the top card all
                  paint above the halo — at 0 its blur washed over the card
                  backs (behind-1/-2 sit at -1/-2) and dulled them */
  border-radius: 40px;
  background: center/cover no-repeat;
  filter: blur(42px) saturate(1.6);
  opacity: 0;
  animation: glow-in 0.9s ease 0.15s forwards;
  pointer-events: none;
}
@keyframes glow-in { to { opacity: 0.5; } }

.card {
  position: absolute;
  inset: 0;
  /* transparent: the poster layer carries the backdrop — an own background
     leaks as a light ring through the 1px clip-path inset at the edges */
  background: transparent;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);
  cursor: grab;
  touch-action: none;
  user-select: none;
  transition: transform 0.25s ease, opacity 0.25s ease;
}
/* credit-card rim: a 2px beveled edge all around — bright catch-light on top,
   falling off down the sides. Sits under the bottom gradient (which owns the
   lower edge) and never intercepts input. */
.card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 6;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.32),
    inset 2px 0 0 rgba(255, 255, 255, 0.14),
    inset -2px 0 0 rgba(255, 255, 255, 0.11);
  /* no bottom rim line — the text gradient owns the lower edge */
}
.card.dragging { transition: none; cursor: grabbing; }
/* behind cards cast NO shadow, and their offset stays INSIDE the scale inset:
   translateY(11px) used to poke a 2.5px bright sliver below the top card —
   the notorious "bottom border". The stack look comes from the ::before/::after
   deck edges, not from these. */
.card.behind-1 { transform: translateY(8px) scale(0.965); z-index: -1; filter: brightness(0.72); box-shadow: none; }
.card.behind-2 { transform: translateY(14px) scale(0.935); z-index: -2; filter: brightness(0.5); box-shadow: none; }

/* poster fills the ENTIRE card; text overlays a gradient at the bottom.
   Children overscan the rounded clip by 1px — antialiasing otherwise leaves a
   hairline of page background between the content and the card edge. */
.card-img {
  position: absolute;
  inset: -1px;
  background-size: cover;
  background-position: center 20%;
  background-color: var(--surface-2);
  /* clip-path (not overflow) is what actually clips COMPOSITED video layers
     pixel-exactly on scaled displays — overflow+radius intermittently
     misaligns its mask there and paints the phantom edge band */
  clip-path: inset(1px round var(--radius));
}

/* behind cards render as neat card backs — a tidy stack, no distracting posters */
.card-back {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: none;
  align-items: center;
  justify-content: center;
  font-size: 2.6rem;
  opacity: 1;
  background: var(--surface-2);
  background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.025) 0 10px, transparent 10px 20px);
  box-shadow: inset 0 0 0 8px var(--surface); /* no 1px outline — it reads as a stray border under the top card */
  border-radius: var(--radius);
}
.card.behind-1 .card-back, .card.behind-2 .card-back { display: flex; transition: opacity 0.22s ease; }
.card.behind-1 .card-back { filter: none; }
/* dragging the top card peeks the next poster (it's already loaded underneath) */
.card-stack.peeking .card.behind-1 .card-back { opacity: 0; }

/* top-card clip: poster first, cross-fade in once the embed is actually up.
   The holder clips the video on its OWN composited layer (radius + translateZ):
   letting the card's overflow do it intermittently misaligns the rounded mask
   against the video layer on scaled displays — the phantom "border". */
.card-video {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--radius);
  transform: translateZ(0);
  opacity: 0;
  transition: opacity 0.5s ease;
}
.card-video.playing { opacity: 1; }
.card-video iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  height: 135%; /* tall overscan: YouTube's title bar, watermark and letterbox
                   bars all land OUTSIDE the visible card — pure footage only */
  width: 270%; /* 16:9 video cover-cropped into a 2:3 card */
  border: 0;
  pointer-events: none; /* drags & taps go to the card, not YouTube UI */
  opacity: 1;
  z-index: 1;
  transition: opacity 0.35s ease;
}
/* the preloading frame is INVISIBLE — without this rule it paints on top of
   the audible front frame and the picture belongs to the wrong clip */
.card-video iframe.standby { opacity: 0; z-index: 0; }
/* YouTube's centered pause/replay icons can't be styled away — when the front
   player reports paused/ended the whole frame drops to the poster instead */
.card-video.halted iframe { opacity: 0; transition-duration: 0.18s; }
/* YouTube's "confirm you're not a bot" wall: the walled player renders but
   never reports, and its own sign-in button dead-ends inside the iframe.
   The playhead guard stamps .signin-needed after repeated lifeless reboots —
   this link is the only card chrome that shows WITHOUT hover (it is the
   card's content at that point) and stays clickable mid-poster. */
.signin-note {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: none;
  font-size: 0.66rem;
  text-decoration: none;
  pointer-events: auto;
}
.card-video.signin-needed .signin-note { display: inline-flex; }

/* the clip pill IS a .badge (same pill vocabulary as the meta row below),
   this class only positions it over the video */
.video-note {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  font-size: 0.62rem;
}
/* one cohesive vertical sound pill (player-style): speaker on top, and on
   hover the capsule extends downward revealing a vertical slider — one panel,
   the pointer never leaves it */
.sound-ctl {
  position: absolute;
  top: 10px; /* always anchored top-right, badge or no badge */
  right: 10px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: rgba(14, 19, 15, 0.85);
  border-radius: 18px;
  padding: 3px;
}
.mute-btn {
  width: 27px;
  height: 27px;
  border: none;
  border-radius: 50%;
  background: none;
  font-size: 0.85rem;
  cursor: pointer;
  flex-shrink: 0;
}
.mute-btn:hover { background: rgba(255, 255, 255, 0.12); }

/* the vertical slider unrolls below the icon, inside the same capsule.
   ABSOLUTE-centered, not flex-centered: the slider is wider than the capsule
   (74px pre-rotation vs 27px), and Safari "safe"-aligns overflowing flex
   items to the start — the rotated slider hung ~24px right of the capsule.
   left/top 50% + translate centers it in every engine. */
.vol-wrap {
  position: relative;
  height: 0;
  width: 27px;
  overflow: hidden;
  transition: height 0.2s ease;
}
.card-video.sound-on .sound-ctl:hover .vol-wrap,
.card-video.sound-on .vol-wrap:focus-within { height: 86px; }
.vol-slider {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 74px;  /* rotated: becomes the slider's length */
  height: 20px;
  margin: 0;
  padding: 0;
  transform: translate(-50%, -50%) rotate(-90deg); /* min at the bottom, like a real player */
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
}
/* The 3px track LINE is painted centred inside a FULL-HEIGHT (20px) track box, so
   it lands on the input's EXACT centre after the -90° rotation. A thin track
   placed by margin-top rendered off-centre in WebKit — the long-standing "volume
   slider not centered" bug. --vol-fill (set in JS) is the level gradient; as a
   centred background band it fills bottom→up once rotated. Thumb centres via
   margin-top (20-12)/2. */
.vol-slider::-webkit-slider-runnable-track {
  height: 20px;
  background: var(--vol-fill, linear-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.4))) center / 100% 3px no-repeat;
}
.vol-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  margin-top: 4px;
  border-radius: 50%;
  background: #fff;
}
.vol-slider::-moz-range-track {
  height: 20px;
  background: var(--vol-fill, linear-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.4))) center / 100% 3px no-repeat;
}
.vol-slider::-moz-range-thumb { width: 12px; height: 12px; border: none; border-radius: 50%; background: #fff; }
/* taste pick wears whatever pill its row wears: .badge on cards,
   .genre-chip in the details head — only the gold tint is its own */
.genre-chip.taste-pick-chip {
  color: var(--accent);
  border-color: rgba(var(--accent-rgb), 0.45);
  box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.15);
}
/* card chrome (clip label, sound pill) appears on hover only */
.video-note, .sound-ctl { opacity: 0; transition: opacity 0.2s; pointer-events: none; }
.card:hover .video-note { opacity: 1; }
.card:hover .sound-ctl { opacity: 1; pointer-events: auto; }
/* drop-zone halos: an ambient oval behind each pile, same treatment as the
   stack-glow (big blur + saturate, ~0.5 resting opacity) but in the pile's
   verdict hue. attachDrag drives opacity 0→max as the card nears the edge;
   decide() flashes it as the card lands. Two radial layers = a brighter core
   inside a deeper wash of the same hue, so it reads lit, not flat. */
.drop-glow {
  position: fixed;
  z-index: 3; /* over the felt, under the piles (4+) and the deck (7) */
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  filter: blur(46px) saturate(1.35);
  transition: opacity 0.3s ease; /* per-frame drag updates chase smoothly */
}
.drop-glow-left, .drop-glow-right {
  top: 50%;
  width: min(60vw, 720px);
  height: 110vh;
}
.drop-glow-left {
  left: 0;
  transform: translate(-40%, -50%);
  background:
    radial-gradient(42% 36% at 58% 50%, rgba(255, 158, 120, 0.55), transparent 72%),
    radial-gradient(closest-side, rgba(var(--skip-rgb), 0.85), transparent 74%);
}
.drop-glow-right {
  right: 0;
  transform: translate(40%, -50%);
  background:
    radial-gradient(42% 36% at 42% 50%, rgba(255, 205, 130, 0.55), transparent 72%),
    radial-gradient(closest-side, rgba(var(--like-rgb), 0.85), transparent 74%);
}
.drop-glow-top {
  top: 0;
  left: 50%;
  width: min(96vw, 1100px);
  height: min(62vh, 560px);
  transform: translate(-50%, -40%);
  background:
    radial-gradient(38% 40% at 50% 58%, rgba(190, 240, 170, 0.5), transparent 72%),
    radial-gradient(closest-side, rgba(var(--watch-rgb), 0.85), transparent 74%);
}

/* landing melt: as a swiped card flies into its pile (see decide()), its chrome
   — title, badges, blurb — fades FIRST, leaving the bare poster to tilt and
   settle onto the pile instead of the whole card blinking off at removal. */
.card.flying .card-body { transition: opacity 0.2s ease; opacity: 0; }

/* the center stack lit as a drop target while a pile card is dragged over it */
.card-stack.drop-target {
  box-shadow:
    0 0 0 3px rgba(var(--accent-rgb), 0.55),
    0 0 46px rgba(var(--accent-rgb), 0.4);
  border-radius: var(--radius);
  transition: box-shadow 0.15s ease;
}

.card-body {
  position: absolute;
  /* FULL card + 2px overscan, self-clipped like .card-img: the box has NO
     edge crossing the card face (its old mid-card top edge rasterized as a
     phantom 1px scanline at fractional zoom/scaling), and it overhangs the
     video by 2px on every side so the player can never peek out from under
     the gradient. Text stays bottom-anchored via flex. */
  inset: -2px;
  clip-path: inset(2px round var(--radius));
  z-index: 7;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 17px 15px; /* the old 15px/13px + the 2px overscan */
  /* full-card box must never eat input meant for the card or the sound pill —
     everything inside is plain text anyway */
  pointer-events: none;
  /* TWO layers, both reaching alpha 0 WELL below the box top (no edge seam
     possible — the box top is off-card):
     1. a tall, VERY gradual linear fade that keeps the title readable even
        over a pure-white video frame,
     2. the edge-anchored radial vignette on top for the curved shape.
     Same painted geometry as the old bottom-anchored box (~51% of the card),
     stops remapped to full-card coordinates. The tail eases through tiny
     alphas instead of stopping dead at the old box top — a slope discontinuity
     there reads as a faint line across the card (Mach band). */
  background:
    radial-gradient(150% 87% at 50% 28%,
      transparent 40%,
      rgba(10, 14, 11, 0.03) 52%,
      rgba(10, 14, 11, 0.1) 62%,
      rgba(10, 14, 11, 0.25) 72%,
      rgba(10, 14, 11, 0.45) 82%,
      rgba(10, 14, 11, 0.65) 91%,
      rgba(10, 14, 11, 0.8) 98%),
    linear-gradient(to top,
      rgba(10, 14, 11, 0.98) 0%,
      rgba(10, 14, 11, 0.9) 9%,
      rgba(10, 14, 11, 0.72) 18%,
      rgba(10, 14, 11, 0.5) 27%,
      rgba(10, 14, 11, 0.3) 34%,
      rgba(10, 14, 11, 0.16) 39%,
      rgba(10, 14, 11, 0.08) 43%,
      rgba(10, 14, 11, 0.03) 46.5%,
      rgba(10, 14, 11, 0.01) 49%,
      rgba(10, 14, 11, 0.004) 55%,
      transparent 63%);
}
.card-title { font-size: 1.12rem; font-weight: 700; line-height: 1.25; text-shadow: 0 1px 4px rgba(0,0,0,0.6); }
.card-title-en {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-top: 1px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card-title-en:empty { display: none; }
/* full-frame hero clip at the top of the details modal (subtitles visible) */
#detailsHero {
  margin: -20px -20px 16px;
  border-radius: 20px 20px 0 0;
  overflow: hidden;
  background: #000 center/contain no-repeat; /* poster shows while the embed loads —
    contain, not cover: a portrait poster cover-cropped into 16:9 loses most of
    the picture and reads as the player "clipping" the content */
  flex-shrink: 0;
}
#detailsHero iframe,
#detailsHero video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  opacity: 0;
  transition: opacity 0.35s ease;
}
#detailsHero iframe.on,
#detailsHero video.on { opacity: 1; }
#detailsHero video { object-fit: contain; background: #000; } /* raw cuts come in odd ratios */
/* idle placeholder: before any clip cues, the frame holds the show's cover art
   at the player's exact 16:9 size, so the page never jumps when the clip loads
   in over it. A soft ▶ signals a clip is on the way (inert — it's just paint). */
#detailsHero.hero-idle {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000 var(--hero-poster) center/cover no-repeat; /* fill the frame
    (player-shaped), matching the clip tiles + ambient glow — not pillarboxed */
}
#detailsHero.hero-idle::after {
  content: "▶";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 2.4rem;
  color: rgba(255, 255, 255, 0.8);
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.65);
  pointer-events: none;
}

.card-meta { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; font-size: 0.74rem; }
/* one-sentence AI blurb (local model server only) — collapses when absent.
   The model is asked for ≤30 words ≈ 5 lines at card width, so the clamp
   sits at 5: an obedient blurb NEVER truncates (clamp is a max — short
   blurbs reserve no space). Over-budget ones clamp, and HOVERING the text
   unclamps in place (max-height tween — line-clamp itself can't animate).
   The blurb is the ONE card-body child that takes pointer events (the body
   itself stays pointer-events:none): hover must land on the text, and
   pointerdown still BUBBLES to the card, so dragging from the blurb works. */
.card-summary {
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--text-dim);
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: 7.25em; /* 5 × line-height — the animatable face of the clamp */
  transition: max-height 0.25s ease;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card-summary.ready { margin-top: 8px; animation: fade-in 0.3s ease; pointer-events: auto; }
/* mid-drag the pointer may sweep the text — the card must not reshape then */
.card:not(.dragging) .card-summary.ready:hover { -webkit-line-clamp: 99; max-height: 16em; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.badge { background: rgba(14, 19, 15, 0.85); border-radius: 6px; padding: 3px 8px; color: var(--text-dim); }
.badge.score { background: var(--accent); color: #fff; font-weight: 700; }
.badge.taste-pick { color: var(--accent); border: 1px solid rgba(var(--accent-rgb), 0.45); box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.18); }
.badge.tag { border: 1px solid #354236; color: var(--accent-2); background: rgba(14, 19, 15, 0.5); }
/* ============ controls ============ */
/* navigation buttons live right under the center card — they belong to it */
.controls {
  position: fixed;
  top: calc(50% + min(28vh, 245px) + 30px); /* breathing room under the card */
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 12px;
  align-items: center;
  z-index: 6;
}
/* card aesthetic: a FROSTED-GLASS cap — a translucent light fill over a blurred
   backdrop, top catch-light on the rim; verdict color stays a tinted ring + glow */
.ctrl-btn {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--text);
  /* SMOKED FROSTED LENS: the smooth soft-lens feel (a gentle even crown sheen +
     a soft warm inner shadow rounding it down, NO hard rim or specular hotspot),
     but a mostly see-through smoked fill so the dark table reads through it — a
     frosted glass disc, not a milky-white one. The coloured LED border + glow
     ring it. NOTHING moves. Hover brightens + glows; :active presses in. */
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.075), rgba(255, 255, 255, 0.03) 52%, rgba(255, 232, 200, 0.045));
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  backdrop-filter: blur(16px) saturate(1.5);
  border: 1px solid #354236;
  --face:
    inset 0 2px 4px rgba(255, 255, 255, 0.15),       /* soft even crown sheen */
    inset 0 -12px 16px -7px rgba(0, 0, 0, 0.2),      /* soft base shadow → the lens rounds down */
    inset 0 0 0 1px rgba(255, 255, 255, 0.05);       /* faint light rim */
  --grgb: 53, 66, 54;   /* LED / glow colour — neutral for the ringless buttons */
  --ga: 0;              /* glow-ring strength */
  --ua: 0;              /* coloured UNDERGLOW strength — lit (verdict) buttons only */
  box-shadow:
    var(--face),
    0 0 14px rgba(var(--grgb), var(--ga)),        /* ambient light ring */
    0 7px 16px rgba(var(--grgb), var(--ua)),      /* faint coloured underglow, spilling below */
    0 2px 8px rgba(0, 0, 0, 0.15);                /* reduced drop shadow (halved again) */
  transition: box-shadow 0.14s ease, filter 0.14s ease, border-color 0.14s, background 0.14s;
}
.ctrl-btn.like  { border-color: var(--like);  --grgb: var(--like-rgb);  --ga: 0.32; --ua: 0.11; }
.ctrl-btn.skip  { border-color: var(--skip);  --grgb: var(--skip-rgb);  --ga: 0.32; --ua: 0.11; }
.ctrl-btn.watch { border-color: var(--watch); --grgb: var(--watch-rgb); --ga: 0.32; --ua: 0.11; }
.ctrl-btn.undo, .ctrl-btn.grid { width: 42px; height: 42px; font-size: 0.95rem; color: var(--text-dim); }
/* 9-square grid, drawn (no clean unicode glyph). The other ctrl buttons
   centre their emoji by line metrics; an svg rides the BASELINE there and
   lands ~1.5px high — flex-centre this one button instead. */
.ctrl-btn.grid { display: inline-flex; align-items: center; justify-content: center; }
.ctrl-btn.grid svg { width: 15px; height: 15px; opacity: 0.9; display: block; }
/* hover: the lens + LED ring brighten, gently */
.ctrl-btn:hover { filter: brightness(1.12); --ga: 0.5; }
/* click: the lens presses in — the sheen sinks, a soft shadow pools up top */
.ctrl-btn:active {
  --face:
    inset 0 3px 7px rgba(0, 0, 0, 0.3),
    inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.hint {
  position: absolute;
  bottom: 12px;
  left: 0;
  right: 0;
  font-size: 0.72rem;
  color: var(--text-dim);
  z-index: 6;
  text-align: center;
}

/* subtle deck-size counter tucked under the stack's bottom edge */
.deck-count {
  position: fixed;
  left: 50%;
  top: calc(50% + min(28vh, 245px) + 96px); /* below the nav buttons */
  transform: translateX(-50%);
  display: flex;
  flex-direction: column; /* the ♻ resteep badge stacks neatly UNDER the count */
  align-items: center;
  gap: 5px;
  white-space: nowrap;
  font-size: 0.7rem;
  color: var(--text-dim);
  opacity: 0.8;
  z-index: 3;
  pointer-events: none; /* the badge re-enables its own */
}

/* status note (loading / empty deck): a compact centered plaque, not a
   card-sized void — the empty deck also drops the stack chrome via
   .card-stack.deck-empty and marks the spot with the piles' dashed
   "nothing here" outline */
.status-msg {
  position: absolute;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: var(--text-dim);
  max-width: 300px;
  line-height: 1.6;
  padding: 22px 28px;
  border: 1.5px dashed #354236;
  border-radius: 16px;
  background: rgba(0, 0, 0, 0.16);
  font-size: 0.92rem;
  z-index: 4;
}
.status-msg .status-icon { display: block; font-size: 1.7rem; margin-bottom: 6px; opacity: 0.85; }
.card-stack.deck-empty::before, .card-stack.deck-empty::after { display: none; }

/* ============ panels ============ */
.panel {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 100;
  display: flex;
  justify-content: flex-end;
}
.panel-inner {
  width: min(94vw, 420px);
  background: var(--surface);
  height: 100%;
  padding: 20px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  animation: slide-in 0.22s ease;
}
.panel-inner.wide { width: min(96vw, 560px); }
@keyframes slide-in { from { transform: translateX(40px); opacity: 0; } to { transform: none; opacity: 1; } }
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.close-btn { background: none; border: none; color: var(--text-dim); font-size: 1.2rem; cursor: pointer; }
.panel-footer { display: flex; gap: 10px; margin-top: 16px; }

.field-label { display: block; margin: 16px 0 8px; font-size: 0.85rem; color: var(--text-dim); }
.field-label b { color: var(--accent-2); }
.chips { display: flex; flex-wrap: wrap; gap: 7px; }
.genre-chip {
  background: var(--surface-2);
  border: 1px solid transparent;
  color: var(--text-dim);
  border-radius: 15px;
  padding: 5px 11px;
  font-size: 0.78rem;
  cursor: pointer;
}
.genre-chip.active { background: rgba(255, 92, 138, 0.14); border-color: var(--accent); color: var(--accent); }
.genre-chip.pref { background: rgba(92, 200, 255, 0.12); border-color: var(--accent-2); color: var(--accent-2); }
.genre-chip .chip-x { opacity: 0.55; } /* quiet remove affordance inside tag chips */
.genre-chip .chip-x:hover { opacity: 1; }
/* panel sliders only — attribute selectors outrank classes, so keep this scoped
   (an unscoped version once stretched the card's volume slider to full width) */
.panel-inner input[type="range"] { width: 100%; accent-color: var(--accent); margin-bottom: 6px; }
input[type="text"] {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid #354236;
  border-radius: 8px;
  color: var(--text);
  padding: 9px 11px;
  font-size: 0.85rem;
  margin-top: 8px;
  outline: none;
}
select {
  width: 100%;
  background: var(--surface-2);
  color: var(--text);
  border: none;
  border-radius: 8px;
  padding: 10px;
  font-size: 0.9rem;
}
.primary-btn {
  margin-top: 14px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 10px;
  padding: 13px;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}
.match-count { margin-top: 16px; font-size: 0.82rem; color: var(--accent-2); text-align: center; }

/* AI-busy cue chip — the tags row's "still thinking" state (parse or the
   meaning judge in flight); breathes so "working" reads at a glance */
.genre-chip.cue-pending { border-style: dashed; animation: cue-pulse 1.5s ease-in-out infinite; }
@keyframes cue-pulse { 50% { opacity: 0.4; } }
/* cues the meaning judge hasn't confirmed yet: visibly provisional */
.genre-chip.cue-unverified { opacity: 0.45; border-style: dotted; }

/* 🔗 Share panel — the name and its Rename confirm share one row (renames
   apply on the button, not per keystroke); "make a new share" is a quiet
   full-width action under the primary Copy */
.share-name-row { display: flex; gap: 8px; align-items: stretch; }
.share-name-row input { flex: 1; min-width: 0; }
.share-name-row .chip-btn { flex-shrink: 0; margin-top: 8px; }
.share-new-btn { display: block; width: 100%; margin-top: 10px; padding: 11px; }

/* ♻ resteep — the steeped-subset language: both the ⚙ Filters chip
   (.steep-filter) and the Steeping-table banner (.resteep-banner) wear the
   pile's own steeping green so "these ARE your steeped titles" reads at a
   glance. The chip fans real posters like the table piles do (.sf-stack). */
.steep-filter, .resteep-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  border-radius: 14px;
  border: 1px solid rgba(var(--watch-rgb), 0.5);
  background: rgba(var(--watch-rgb), 0.08);
  box-shadow: 0 0 14px rgba(var(--watch-rgb), 0.14);
}
.steep-filter { margin: 6px 0 14px; }
.steep-filter b, .resteep-banner b { color: var(--watch); }
/* two columns: the poster stack with its return button beneath (cards move
   back onto the pile — card language, not undo), text takes the rest */
.sf-left { display: flex; flex-direction: column; align-items: center; gap: 7px; flex-shrink: 0; }
.sf-left .sf-x { white-space: nowrap; }
.sf-stack { display: flex; flex-shrink: 0; }
.sf-stack img {
  width: 34px;
  height: 48px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid rgba(233, 237, 232, 0.25);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
}
.sf-stack img:not(:first-child) { margin-left: -14px; }
.sf-stack img:nth-child(1) { transform: rotate(-6deg); }
.sf-stack img:nth-child(2) { transform: rotate(-2deg); }
.sf-stack img:nth-child(3) { transform: rotate(3deg); }
.sf-stack img:nth-child(4) { transform: rotate(7deg); z-index: 1; }
.sf-text, .resteep-banner .rs-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.sf-x { flex-shrink: 0; }
.resteep-banner { margin: 6px 22px 4px; }
.resteep-banner .primary-btn { margin-top: 0; flex-shrink: 0; padding: 11px 16px; }
/* ⚙ Filters search row — the landing search, transplanted */
.filter-search { display: flex; gap: 8px; align-items: stretch; }
.filter-search input { flex: 1; margin-top: 0; }
.filter-search .primary-btn { margin-top: 0; padding: 9px 14px; font-size: 0.85rem; flex-shrink: 0; }
/* the search-governed zone: the search bar sits ABOVE this box — it governs
   the box, it isn't part of it (the bar's own label says so; the box carries
   no legend of its own). ↻ Sift (re)fills EVERYTHING inside from the query;
   deck-neutral until "Apply & rebuild deck", the panel's one final trigger.
   Full-width and flush left like every other panel row — the border alone
   marks the boundary. */
.search-scope {
  border: 1px solid rgba(var(--accent-rgb), 0.35);
  background: rgba(var(--accent-rgb), 0.04);
  border-radius: 14px;
  padding: 0 14px 14px;
  margin-top: 8px;
}
.search-scope > .field-label:first-child { margin-top: 12px; }
/* Apply stays the hero; Reset sits beside it as the quiet broom */
.filter-actions { display: flex; gap: 10px; align-items: stretch; margin-top: 14px; }
.filter-actions .primary-btn { flex: 1; margin-top: 0; }
.filter-actions .chip-btn { flex-shrink: 0; }

/* one slider, two thumbs */
.dual-range { position: relative; height: 28px; }
.dual-range::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 12px;
  height: 4px;
  background: var(--surface-2);
  border-radius: 2px;
}
.dr-fill {
  position: absolute;
  top: 12px;
  height: 4px;
  background: var(--accent);
  border-radius: 2px;
  pointer-events: none;
}
.dual-range input[type="range"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 28px;
  margin: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  pointer-events: none;
}
.dual-range input[type="range"]::-webkit-slider-runnable-track { background: transparent; height: 28px; }
.dual-range input[type="range"]::-moz-range-track { background: transparent; }
.dual-range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  pointer-events: auto;
  width: 18px;
  height: 18px;
  margin-top: 5px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--accent);
  cursor: grab;
}
.dual-range input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--accent);
  cursor: grab;
}

.mini-btn {
  background: var(--bg);
  border: 1px solid #354236;
  color: var(--text-dim);
  border-radius: 6px;
  padding: 3px 8px;
  font-size: 0.7rem;
  cursor: pointer;
}
.mini-btn:hover { color: var(--text); border-color: var(--accent-2); }
.mini-btn.danger:hover { color: #ff8080; border-color: #ff8080; }
.wl-empty { color: var(--text-dim); font-size: 0.9rem; text-align: center; margin-top: 40px; }

/* filter history: one compact card per saved filter run — the filters
   themselves lead as mini chips (the app's chip language, shrunk), pile
   tally + date beneath, actions pinned to the right edge. */
.hist-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.hist-card {
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: 10px;
  padding: 8px 11px;
  display: flex;
  align-items: center;
  gap: 12px;
}
/* the run whose filters are live right now wears the accent edge */
.hist-card.current { border-color: rgba(var(--accent-rgb), 0.4); background: rgba(var(--accent-rgb), 0.06); }
.hist-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* mini filter chips: same tints as the ⚙ Filters pills, at record scale */
.hist-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.hist-chip {
  font-size: 0.68rem;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid transparent;
  color: var(--text-dim);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hist-chip.query { color: var(--text); font-weight: 600; }
.hist-chip.active { background: rgba(255, 92, 138, 0.14); border-color: rgba(255, 92, 138, 0.45); color: var(--accent); }
.hist-chip.avoid { background: rgba(var(--skip-rgb), 0.10); border-color: rgba(var(--skip-rgb), 0.5); color: var(--skip); }
.hist-chip.pref { background: rgba(92, 200, 255, 0.12); border-color: rgba(92, 200, 255, 0.4); color: var(--accent-2); }
.hist-badge {
  font-size: 0.62rem; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--accent); border: 1px solid rgba(var(--accent-rgb), 0.4);
  border-radius: 999px; padding: 1px 7px; flex: none;
}
.hist-meta { display: flex; align-items: center; gap: 12px; font-size: 0.74rem; color: var(--text-dim); }
.hist-meta .hist-when { margin-left: auto; }
.hist-actions { display: flex; align-items: center; gap: 4px; flex: none; }
.hist-replace { font-weight: 600; border-color: rgba(var(--accent-rgb), 0.5); color: var(--accent); white-space: nowrap; }
.hist-replace:hover { background: rgba(var(--accent-rgb), 0.12); color: var(--accent); border-color: var(--accent); }
/* forget: a quiet ✕ — dropping a record never touches a swipe, so it earns
   no confirm dialog and no heavy button, just a red warning on hover */
.hist-x {
  width: 26px; height: 26px; flex: none;
  background: none; border: none; border-radius: 50%;
  color: var(--text-dim); font-size: 0.78rem; line-height: 1;
  cursor: pointer; opacity: 0.65;
}
.hist-x:hover { opacity: 1; color: #ff8080; background: rgba(255, 128, 128, 0.1); }
.hist-empty { padding: 10px 0; }

/* library */
/* tastes panel: stat cards on top, tight labeled sections below */
.insights { display: flex; flex-direction: column; gap: 20px; margin-top: 14px; }
.taste-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
/* mini rate histograms: one thin savored bar over one pruned bar per bucket */
.taste-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: -8px; }
.chart-card { background: var(--surface-2); border-radius: 10px; padding: 10px 12px; }
.chart-card > b {
  display: block;
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  margin-bottom: 8px;
}
.rate-row { display: grid; grid-template-columns: 46px 1fr 32px; gap: 8px; align-items: center; margin-top: 6px; font-size: 0.7rem; }
.rate-row:first-of-type { margin-top: 0; }
.rate-label { color: var(--text-dim); white-space: nowrap; }
.rate-track { display: flex; flex-direction: column; gap: 2px; }
.rate-bar { display: block; height: 5px; border-radius: 3px; min-width: 2px; }
.rate-bar.like { background: var(--like); }
.rate-bar.skip { background: var(--skip); opacity: 0.7; }
.rate-pct { text-align: right; }
/* evidence drop-down under the gravitate/prune chips */
.tag-detail { cursor: pointer; font: inherit; }
.tag-evidence {
  margin-top: 8px;
  font-size: 0.74rem;
  line-height: 1.55;
  color: var(--text-dim);
  background: var(--surface-2);
  border-radius: 8px;
  padding: 8px 10px;
}
.tag-evidence div { margin-top: 3px; }
.insight > b {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--text-dim);
  margin-bottom: 7px;
}
.insight .small.dim { display: block; margin-top: 6px; }
.insight .queue-list { display: flex; flex-direction: column; gap: 4px; }
.insight .queue-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
/* deck-impact rows: source title on line one (never truncated — it's the
   point), effect on a dim second line, same rhythm as the trace rows */
.impact-list { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.impact-row { font-size: 0.82rem; background: var(--surface-2); border-radius: 6px; padding: 5px 8px; }
.impact-row .dim.small { display: block; margin-top: 1px; line-height: 1.35; }

/* 💻 Computer: the active-filters spotlight — the panel's headline block */
.lib-filters {
  background: var(--surface-2);
  border: 1px solid rgba(var(--accent-rgb), 0.35);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 14px;
}
.lib-filters h3 { margin: 0 0 8px; }
.lib-filters .genre-chip { cursor: default; }
.lib-query { font-size: 0.95rem; margin-bottom: 8px; }
.lib-filters .chips { margin-bottom: 8px; }
/* weak word-cue chip: dashed = uncertain, matcha tone = AI-derived */
.genre-chip.weak {
  border: 1px dashed rgba(143, 212, 140, 0.55);
  color: var(--text-dim);
}
.genre-chip.weak .dim { opacity: 0.7; }
/* BORDER LANGUAGE: solid = a 100%-confidence command the user typed or
   toggled; dashed = a weak AI suggestion (evidence, never a command).
   .avoid alone is the HARD exclusion command (carriers never deal) — solid,
   tinted like its .active/.pref siblings; .weak.avoid is the soft ⛔ cue
   (predicate negation) — dashed, same design as the green .weak cues. */
.genre-chip.avoid {
  background: rgba(var(--skip-rgb), 0.10);
  border: 1px solid rgba(var(--skip-rgb), 0.6);
  color: var(--skip);
}
.genre-chip.weak.avoid {
  background: transparent;
  border: 1px dashed rgba(var(--skip-rgb), 0.55);
  color: var(--skip);
}
/* fixed-width state slot at a chip's left edge: the • / 📌 / ⛔ marker swaps
   in place without shifting the label (emoji run wider than a dot). Chips
   are inline-flex so dot, label, weights and ✕ all center on one axis —
   emoji and text baselines never agree on their own. */
.genre-chip { display: inline-flex; align-items: center; gap: 5px; }
.genre-chip .chip-dot { width: 1.35em; margin-left: -3px; text-align: center; flex: none; }

/* local services status strip: each row is TWO lines — a header (dot · name ·
   right-aligned state pill) and, beneath it, the role plus a dynamic detail
   line (models / hint / upstreams). Nothing competes for the same line, so
   long model names and endpoints no longer squish or clip. */
.svc-list { display: flex; flex-direction: column; gap: 8px; }
.svc-row {
  background: var(--surface-2);
  border-radius: 8px;
  padding: 9px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.svc-top { display: flex; align-items: center; gap: 8px; }
.svc-name { font-size: 0.86rem; }
.svc-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(233, 237, 232, 0.2); flex: none; }
.svc-dot.on { background: var(--accent-2); box-shadow: 0 0 6px rgba(143, 212, 140, 0.5); }
.svc-dot.off { background: rgba(var(--skip-rgb), 0.5); }
.svc-dot.warm { background: var(--accent); box-shadow: 0 0 6px rgba(var(--accent-rgb), 0.5); }
/* state pill: a short word (online/ready/offline…) tinted by liveness */
.svc-state {
  margin-left: auto; flex: none;
  font-size: 0.66rem; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase;
  border-radius: 999px; padding: 2px 9px;
  color: var(--text-dim); background: rgba(233, 237, 232, 0.08);
}
.svc-state.on { color: var(--accent-2); background: rgba(143, 212, 140, 0.14); }
.svc-state.off { color: #ff9a86; background: rgba(var(--skip-rgb), 0.14); }
.svc-state.warm { color: var(--accent); background: rgba(var(--accent-rgb), 0.14); }
.svc-role { line-height: 1.4; }
.svc-detail { line-height: 1.4; opacity: 0.85; }
.svc-detail:empty { display: none; }

.lib-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.lib-stat {
  background: var(--surface-2);
  border-radius: 10px;
  padding: 12px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.lib-stat b { font-size: 1.3rem; }
.lib-stat span { font-size: 0.68rem; color: var(--text-dim); }
.queue-list { display: flex; flex-direction: column; gap: 4px; max-height: 40vh; overflow-y: auto; }
.queue-row { font-size: 0.82rem; }

/* ============ details drawer ============ */
.details-body { font-size: 0.9rem; }
.det-title-en {
  display: block;
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--text-dim);
  margin-top: 2px;
}
/* right-column title (wide layout only — narrow keeps the shared header's h2) */
.det-titlebox { display: none; font-size: 1.3rem; line-height: 1.3; margin: 0 0 12px; }
/* every section links back to where its data lives */
.src-link { font-size: 0.72rem; font-weight: 400; color: var(--text-dim); white-space: nowrap; }
.src-link:hover { color: var(--accent-2); }
/* ▶ watch link — the one det-link that leads somewhere to sit down and
   WATCH: golden-brew tint so the action of the row reads at a glance */
.det-links .watch-link { color: var(--accent); font-weight: 600; }
.det-links .watch-link:hover { color: var(--accent); text-shadow: 0 0 12px rgba(var(--accent-rgb), 0.55); }
.det-section h3 .src-link { float: right; margin-top: 2px; }
.syn-actions { display: flex; align-items: center; gap: 12px; }
.syn-actions .src-link { margin-left: auto; }
/* review digest: recurring aspects, colored by which side brings them up */
.rev-digest { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 2px 0 10px; }
.digest-chip {
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 0.74rem;
  font-family: inherit;
  cursor: default;
  background: var(--surface-2);
  border: 1px solid transparent;
  color: var(--text-dim);
}
.digest-chip.pos { border-color: rgba(var(--like-rgb), 0.45); color: var(--like); }
.digest-chip.neg { border-color: rgba(var(--skip-rgb), 0.5); color: var(--skip); }
.digest-chip.mix { border-color: rgba(233, 237, 232, 0.2); }
/* talk = raised by many, judged by few — accent-washed, claiming no verdict */
.digest-chip.talk { border-color: rgba(var(--accent-rgb), 0.35); color: var(--accent); }
.digest-chip b { font-weight: 650; }
.digest-chip .dc-v { opacity: 0.72; }
/* chips carrying data-re double as filters: clicking scopes the comment list
   to reviews that mention the topic; .active glows in the chip's verdict hue */
.digest-chip[data-re] { cursor: pointer; }
.digest-chip[data-re]:hover { filter: brightness(1.18); }
.digest-chip.active { background: rgba(233, 237, 232, 0.12); box-shadow: 0 0 9px rgba(233, 237, 232, 0.12); }
.digest-chip.active.pos { background: rgba(var(--like-rgb), 0.14); box-shadow: 0 0 9px rgba(var(--like-rgb), 0.28); }
.digest-chip.active.neg { background: rgba(var(--skip-rgb), 0.14); box-shadow: 0 0 9px rgba(var(--skip-rgb), 0.28); }
.digest-chip.active.talk { background: rgba(var(--accent-rgb), 0.14); box-shadow: 0 0 9px rgba(var(--accent-rgb), 0.28); }
.rev-filter-note { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; color: var(--text-dim); }
.rev-filter-note b { color: var(--text); }
/* topic match inside a filtered excerpt — a light accent wash, not a shout */
mark.cmt-hl { background: rgba(var(--accent-rgb), 0.18); color: inherit; border-radius: 4px; padding: 0 2px; }
/* engagement view (replaces the vote histogram) */
.eng-bar {
  display: flex;
  height: 12px;
  border-radius: 6px;
  overflow: hidden;
  margin-top: 8px;
  background: var(--surface-2);
}
.eng-seg { display: block; min-width: 2px; }
.eng-seg.done, .eng-dot.done { background: var(--like); }
.eng-seg.now, .eng-dot.now { background: var(--accent-2); }
.eng-seg.hold, .eng-dot.hold { background: #8a9a8b; }
.eng-seg.drop, .eng-dot.drop { background: var(--skip); }
.eng-seg.plan, .eng-dot.plan { background: rgba(233, 237, 232, 0.22); }
.eng-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 6px; }
.eng-legend span { display: inline-flex; align-items: center; gap: 4px; }
.eng-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
/* two-line rows, same rhythm as the trace breakdown: verdict chip on line
   one, the plain-language explanation on its own line under it */
.eng-chips { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }
.eng-row {
  background: var(--surface-2);
  border-radius: 6px;
  padding: 6px 8px;
}
.eng-row .digest-chip { background: none; border: none; padding: 0; font-size: 0.78rem; }
.eng-row > .dim.small { display: block; margin-top: 2px; line-height: 1.35; }
/* letter avatar when a review has no profile image (scraped reviews, deleted users) */
.cmt-avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  border: 1px solid #354236;
  color: var(--text-dim);
  font-weight: 700;
  font-size: 0.9rem;
}

/* flex-start: the poster is height:auto — a stretch would deform its ratio */
.det-head { display: flex; gap: 14px; align-items: flex-start; }
/* the poster shows WHOLE, never cropped: width is fixed, height follows the
   image's own ratio ("auto 225/319" only reserves MAL's standard poster
   shape while loading — once loaded the intrinsic ratio wins) */
.det-head img { width: 82px; height: auto; aspect-ratio: auto 225 / 319; border-radius: 8px; }
/* the meta run (TV · 12 eps · 2019 · Finished) rides INSIDE the score line;
   votes/rank moved to the score's hover title */
.det-score { font-size: 1.4rem; font-weight: 800; color: var(--accent); }
.det-score .dim { font-size: 0.72rem; font-weight: 400; }
/* review-site links reuse .src-link — same voice as every other ↗ link */
.det-links { display: flex; flex-wrap: wrap; gap: 3px 14px; margin-top: 10px; }
/* genres wear the same pill vocabulary as the rest of the app, just smaller */
.det-head .det-genres { margin-top: 8px; gap: 5px; }
.det-genres .genre-chip { padding: 3px 9px; font-size: 0.68rem; }

/* Unified tag pill: the compact details-pane look, reused VERBATIM in ⚙ Filters
   so a tag reads identically in both places — same size, same state emoji
   (＋ off · • boost · 📌 required · ⛔ avoid). The state tint comes from the
   shared .active/.pref/.avoid rules above. */
.tag-pill { padding: 3px 9px; font-size: 0.68rem; }
/* tag pills wear NO border — state reads from the fill + text colour alone.
   📌 required is GREEN (the savored "want it" hue), ⛔ avoided is the pruned red. */
.tag-pill, .tag-pill.active, .tag-pill.avoid { border-color: transparent; }
.tag-pill.active { background: rgba(var(--like-rgb), 0.18); color: var(--like); }
.tag-pill.avoid { background: rgba(var(--skip-rgb), 0.16); color: var(--skip); }
/* ONE shape in every state: the ✕ slot is always reserved (fixed width) even
   when empty on a neutral pill, so nothing reflows as a tag toggles. The empty
   slot lets clicks fall through to the body (→ add). */
.tag-pill .chip-x { width: 0.85em; text-align: center; flex: none; opacity: 0.5; }
.tag-pill .chip-x:hover { opacity: 1; }
.tag-pill .chip-x:empty { pointer-events: none; }
/* details-pane & common-grid tags are live controls: clickable, gentle hover
   lift so a neutral (＋) pill still reads as "you can add me". Plain pills
   (themes we don't rank on) stay inert. */
.det-genres .tag-pill { cursor: pointer; }
.tag-pill:hover { filter: brightness(1.14); }
.det-tag-plain { cursor: default; opacity: 0.8; }
/* the "＋N more" expander: its wrapper is display:contents so the revealed tags
   flow as normal pills in the same row, not a nested box */
.det-tags-extra { display: contents; }
/* the "＋N more (K matched)" control FOLDS the show's long tail of tags — it's a
   button, not a tag, so it wears a distinct accent-dashed capsule (never the
   solid/tinted pill chrome) to read plainly as "expand me". */
.det-tags-more {
  cursor: pointer;
  padding: 3px 11px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: rgba(var(--accent-rgb), 0.95);
  background: rgba(var(--accent-rgb), 0.09);
  border: 1px dashed rgba(var(--accent-rgb), 0.55);
  border-radius: 15px;
  opacity: 1;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.det-tags-more:hover { background: rgba(var(--accent-rgb), 0.18); border-color: var(--accent); }
.det-synopsis { font-size: 0.82rem; line-height: 1.5; color: var(--text-dim); margin-top: 12px; white-space: pre-line; /* keep MAL's paragraph breaks */ }
.det-synopsis.clamped {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
#synMore { margin-top: 6px; }
.det-section { margin-top: 18px; }

/* reviews: comment-section look */
.rev-tabs { display: flex; gap: 6px; margin-bottom: 4px; }
.rev-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-dim);
  padding: 7px 10px;
  font-size: 0.82rem;
  cursor: pointer;
}
.rev-tab:hover { color: var(--text); }
.rev-tab.active { color: var(--text); border-bottom-color: var(--accent); }

.comment { display: flex; gap: 11px; padding: 12px 2px; border-bottom: 1px solid #232838; }
.comment:last-child { border-bottom: none; }
.cmt-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--surface-2);
  flex-shrink: 0;
}
.cmt-body { flex: 1; min-width: 0; }
.cmt-head { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; font-size: 0.8rem; }
.cmt-user { color: var(--text); font-weight: 600; text-decoration: none; }
.cmt-user:hover { color: var(--accent-2); }
.cmt-verdict { font-size: 0.72rem; border-radius: 10px; padding: 1px 8px; background: var(--surface-2); }
/* recommended reads green like the digest's "praised", mixed gets the amber */
.cmt-verdict.v-rec { color: var(--like); }
.cmt-verdict.v-mixed { color: var(--watch); }
.cmt-verdict.v-notrec { color: var(--skip); }
.cmt-votes { margin-left: auto; color: var(--accent-2); font-size: 0.78rem; }
.comment p { font-size: 0.79rem; line-height: 1.5; color: var(--text-dim); margin-top: 4px; }
.rev-more { margin-top: 10px; width: 100%; padding: 8px; }

/* ============ loading: skeletons & spinner ============ */
.skel {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-radius: 8px;
}
.skel::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.07), transparent);
  animation: shimmer 1.3s infinite;
}
@keyframes shimmer { 100% { transform: translateX(100%); } }
.skel-line { margin: 8px 0; }

.spinner {
  display: inline-block;
  vertical-align: -3px;
  width: 16px;
  height: 16px;
  border: 2px solid var(--surface-2);
  border-top-color: var(--accent-2);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { 100% { transform: rotate(360deg); } }

/* ============ centered featured modal (details) ============ */
.panel.center { justify-content: center; align-items: center; }
.panel-inner.modal {
  height: auto;
  max-height: 90vh;
  width: min(96vw, 700px);
  border-radius: 20px;
  animation: pop-in 0.22s ease;
}
@keyframes pop-in { from { transform: scale(0.95); opacity: 0; } to { transform: none; opacity: 1; } }

/* pile verdicts at the top of the details' right column — the deck's own
   circle controls (.ctrl-btn), compacted. The pile the title already sits
   in renders dulled + inert (.held): the row doubles as a state readout. */
.det-verdicts {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 8px;
}
.det-verdicts .ctrl-btn { width: 40px; height: 40px; font-size: 1rem; flex-shrink: 0; }
.det-verdicts .ctrl-btn.held {
  opacity: 0.38;
  cursor: default;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.det-verdicts .ctrl-btn.held:hover { transform: none; }
.det-verdict-note { margin-left: 2px; }

/* ============ details modal: two-column layout on big desktops ============
   Left: big player with clips right under it. Right: title, meta, synopsis,
   why-this-card, vote distribution, reviews. display:contents promotes the
   wrappers into the modal's grid so the hero (a sibling of the body) joins
   the columns; the shared header collapses to just its close button and the
   title re-renders at the top of the right column (.det-titlebox). */
@media (min-width: 1120px) {
  .panel-inner.modal {
    position: relative;
    width: min(96vw, 1440px);
    display: grid;
    grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
    grid-template-rows: auto auto 1fr;
    column-gap: 32px;
    align-content: start;
  }
  .panel-inner.modal #detailsHero {
    grid-column: 1;
    grid-row: 2;
    margin: 6px 0 20px;
    border-radius: 14px;
    overflow: visible;      /* the ambient glow must leak past the box… */
    background: none;       /* …and the poster moves onto the glow layer */
    position: relative;
    z-index: 0;
  }
  .panel-inner.modal #detailsHero iframe,
  .panel-inner.modal #detailsHero video { border-radius: 14px; } /* rounding without overflow-clipping the player */
  /* idle placeholder in the modal layout: the crisp cover art fills the framed
     16:9 box (the blurred ::before halo still glows behind it as usual) */
  .panel-inner.modal #detailsHero.hero-idle {
    background: var(--hero-poster) center/cover no-repeat;
  }
  /* ambient light: the show's poster, blurred wide, glowing out from behind
     the player — the YouTube-style halo, no video pixel access needed */
  .panel-inner.modal #detailsHero::before {
    content: "";
    position: absolute;
    inset: -4px -22px -18px;
    z-index: -1;
    border-radius: 26px;
    background: var(--hero-poster, radial-gradient(closest-side, rgba(var(--accent-rgb), 0.4), transparent)) center/cover no-repeat;
    filter: blur(42px) saturate(1.6);
    opacity: 0.55;
    pointer-events: none;
  }
  .panel-inner.modal .panel-header { display: contents; }
  .panel-inner.modal .panel-header h2 { display: none; }
  /* the whole lead block — title, score/meta/links, synopsis — crowns the
     left column above the player; the right column is pure community data */
  .panel-inner.modal .det-lead { grid-column: 1; grid-row: 1; align-self: start; min-width: 0; }
  /* title + translation sit to the RIGHT of the poster; the poster's margin
     from the modal top equals its margin from the left (the 20px padding) */
  .panel-inner.modal .det-titlebox { display: block; margin: 0 0 8px; }
  .panel-inner.modal .det-lead .det-head { margin-top: 0; }
  /* the poster keeps its OWN ratio (the old absolute height-follows-text
     trick cropped the artwork once the meta block got short) and is sized so
     its full height ≈ the title→links text block — smaller, whole, uncropped */
  .panel-inner.modal .det-lead .det-head img { width: 104px; }
  .panel-inner.modal .details-body,
  .panel-inner.modal .det-cols { display: contents; }
  .panel-inner.modal .det-main { grid-column: 1; grid-row: 3; align-self: start; min-width: 0; }
  .panel-inner.modal .det-side { grid-column: 2; grid-row: 1 / -1; align-self: start; min-width: 0; }
  .panel-inner.modal .close-btn { position: absolute; top: 14px; right: 18px; z-index: 5; }
}


/* clips */
.clip-kind {
  background: var(--bg);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 0.68rem;
  margin-right: 4px;
}

/* thumbnail grid for playable clips */
.clip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin-top: 6px;
}

/* same-franchise badges: cover + name + metadata, in ONE oldest-first row you
   scroll through horizontally. Each badge (bar the current title) reopens the
   pane for that entry. */
.franchise-strip {
  display: flex; gap: 8px; margin-top: 6px;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 6px; /* breathing room under the badges for the scrollbar */
  scroll-snap-type: x proximity;
}
.franchise-badge {
  display: flex; align-items: center; gap: 9px;
  flex: 0 0 220px; min-width: 0; scroll-snap-align: start;
  /* same border treatment as the clip thumbnails above: 2px, transparent at
     rest, hover to the same slate, selected (current) to the accent-2 green */
  background: var(--surface-2); border: 2px solid transparent; border-radius: 10px;
  padding: 6px 11px 6px 6px; cursor: pointer; text-align: left;
  transition: border-color 0.15s, filter 0.12s;
}
.franchise-badge:hover { border-color: #3a4160; filter: brightness(1.08); }
/* the title you're viewing: content dulled but the selected border kept crisp,
   so its spot in the timeline reads at a glance (mirrors a clip's .playing) */
.franchise-badge.current { cursor: default; border-color: var(--accent-2); }
.franchise-badge.current:hover { filter: none; border-color: var(--accent-2); }
.franchise-badge.current .fr-cover,
.franchise-badge.current .fr-text,
.franchise-badge.current .fr-score { opacity: 0.5; }
.fr-cover { width: 42px; height: 60px; object-fit: cover; border-radius: 5px; flex: none; background: var(--bg); }
.fr-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
/* clamp long titles to two lines so a badge keeps its fixed height */
.fr-name {
  font-size: 0.8rem; font-weight: 600; line-height: 1.25; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.fr-meta { font-size: 0.68rem; }
.fr-score { align-self: flex-start; flex: none; font-size: 0.72rem; color: var(--accent); white-space: nowrap; }
.clip-thumb { background: none; border: none; padding: 0; cursor: pointer; text-align: left; }
.clip-img {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid transparent;
  background: var(--surface-2);
  transition: border-color 0.15s;
}
.clip-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.clip-thumb:hover .clip-img { border-color: #3a4160; }
.clip-thumb.playing .clip-img { border-color: var(--accent-2); }
.clip-img .clip-kind {
  position: absolute;
  top: 6px;
  left: 6px;
  margin: 0;
  background: rgba(15, 17, 23, 0.85);
  color: var(--accent-2);
}
.clip-dur {
  position: absolute;
  bottom: 6px;
  right: 6px;
  background: rgba(15, 17, 23, 0.85);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 0.68rem;
  color: var(--text);
}
.clip-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: #fff;
  background: rgba(0, 0, 0, 0.28);
  opacity: 0;
  transition: opacity 0.15s;
}
.clip-thumb:hover .clip-play, .clip-thumb:focus-visible .clip-play { opacity: 1; }
.clip-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 5px;
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text-dim);
}

/* recommendation trace */
.trace-details summary {
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 700;
  list-style: none;
  padding: 4px 0;
}
.trace-details > summary::-webkit-details-marker { display: none; }
.trace-details > summary::before { content: "▸ "; color: var(--accent-2); }
.trace-details[open] > summary::before { content: "▾ "; }
.trace { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
/* same-kind rows folded behind one expandable summary line */
.trace-group summary {
  list-style: none;
  cursor: pointer;
  /* flex, not grid: Chrome's summary + grid auto-placement misplaces the
     first item — flex is deterministic here */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 10px;
}
.trace-group summary .trace-val { margin-left: auto; }
.trace-group summary .trace-note { flex-basis: 100%; }
.trace-group summary::-webkit-details-marker { display: none; }
.trace-group summary::marker { content: none; }
.trace-group summary .trace-label::before { content: "\25b8 "; color: var(--accent-2); }
.trace-group[open] summary .trace-label::before { content: "\25be "; }
.trace-group-body { display: flex; flex-direction: column; gap: 3px; margin: 3px 0 5px 14px; }
.trace-group-body .trace-row { background: rgba(34, 43, 35, 0.55); }
/* label + value on line one (long titles ellipsize), note on its own full-
   width line — a squeezed third column wrapped one word per line */
.trace-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 10px;
  align-items: baseline;
  font-size: 0.8rem;
  padding: 4px 8px;
  border-radius: 6px;
  background: var(--surface-2);
}
.trace-row:last-child { background: rgba(143, 212, 140, 0.08); border: 1px solid #35513a; }
.trace-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.trace-note { grid-column: 1 / -1; line-height: 1.35; }
.trace-val { font-variant-numeric: tabular-nums; }
.trace-val.pos { color: var(--like); }
.trace-val.neg { color: var(--skip); }
.src-tag {
  background: var(--surface-2);
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 0.65rem;
  color: var(--accent-2);
}

/* ============ taste constellation graph ============ */
.graph-view {
  position: fixed;
  inset: 0;
  z-index: 58; /* under the panels/details (they open on top of it) */
  display: flex;
  flex-direction: column;
  background: var(--table-bg);
  animation: table-in 0.3s ease;
}
.graph-top {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 12px 18px;
  flex-wrap: wrap;
}
.graph-top > b { font-size: 1rem; white-space: nowrap; }
.graph-top .small { max-width: 900px; }
.graph-count { margin-left: auto; white-space: nowrap; }
.graph-stage {
  flex: 1;
  position: relative;
  overflow: hidden;
  cursor: grab;
  touch-action: none;
}
.graph-stage:active { cursor: grabbing; }
.graph-world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.graph-edges { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: visible; }
.graph-node {
  position: absolute;
  left: -14px;
  top: -20px;
  width: 28px;
  height: 40px;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
  transition: width 0.12s, height 0.12s;
}
.graph-node img { width: 100%; height: 100%; object-fit: cover; display: block; }
.graph-node:hover { z-index: 5; width: 34px; height: 48px; }
.graph-node.gk-l { box-shadow: 0 0 0 2px var(--like), 0 0 14px rgba(var(--like-rgb), 0.35); }
.graph-node.gk-r { box-shadow: 0 0 0 2px var(--skip); opacity: 0.75; }
.graph-node.gk-u { box-shadow: 0 0 0 2px #7fa06a; }

/* ============ table view: cards spread out on the table ============ */
.table-view {
  position: fixed;
  inset: 0;
  z-index: 60;
  /* background: the shared wood-table texture, defined with .stage above */
  display: flex;
  flex-direction: column;
  animation: table-in 0.3s ease;
}
@keyframes table-in { from { opacity: 0; transform: scale(0.985); } }
.table-view.leaving { animation: table-out 0.24s ease forwards; }
@keyframes table-out { to { opacity: 0; transform: scale(0.985); } }

/* full-width return zone above everything — NOT a bar: the button surface is
   invisible (pure page background), and the affordance is a soft light
   breathing behind the label. On hover it glows brighter and rises — the
   direction the table flies when you leave. */
.table-back {
  position: relative;
  display: block;
  width: 100%;
  padding: 15px 0 13px;
  border: none;
  background: none;
  cursor: pointer;
  font-size: 0.85rem;
  letter-spacing: 0.3px;
  color: var(--text-dim);
  transition: color 0.25s ease, text-shadow 0.25s ease;
  z-index: 1;
}
.table-back::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -55px;    /* the light source lives ABOVE the screen… */
  height: 160px; /* …so the viewport top slices it: a screen-wide bleed-in glow */
  background: radial-gradient(55% 100% at 50% 0%,
    rgba(143, 212, 140, 0.34),
    rgba(143, 212, 140, 0.10) 55%,
    transparent 80%);
  pointer-events: none;
  z-index: -1;
  animation: back-breathe 3.6s ease-in-out infinite;
}
@keyframes back-breathe {
  0%, 100% { opacity: 0.6; }
  50%      { opacity: 1; }
}
.table-back:hover {
  color: var(--accent-2);
  text-shadow: 0 0 14px rgba(143, 212, 140, 0.55);
}
.table-back:hover::before {
  background: radial-gradient(55% 100% at 50% 0%,
    rgba(143, 212, 140, 0.58),
    rgba(143, 212, 140, 0.20) 55%,
    transparent 80%);
  animation: back-glow-pull 0.9s ease-in-out infinite alternate;
}
/* the glow gets pulled up past the screen edge — where clicking takes you */
@keyframes back-glow-pull {
  from { opacity: 0.85; transform: translateY(0); }
  to   { opacity: 1;    transform: translateY(-14px); }
}

.table-top {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap; /* sessions drop to their own row before tabs ever squeeze */
  padding: 6px 20px 6px;
}
.table-decks { display: flex; gap: 8px; flex-wrap: wrap; animation: decks-up 0.35s ease; }
@keyframes decks-up { from { transform: translateY(50px); opacity: 0; } }
.table-deck {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--surface);
  border: 1px solid #2c3143;
  color: var(--text-dim);
  border-radius: 12px;
  padding: 7px 12px;
  font-size: 0.8rem;
  cursor: pointer;
}
.table-deck:hover { color: var(--text); }
.table-deck.active { border-color: var(--accent); color: var(--text); }
.table-deck b { color: var(--accent-2); }
.mini-deck { position: relative; width: 16px; height: 22px; display: inline-block; }
.mini-deck i {
  position: absolute;
  inset: 0;
  background: var(--surface-2);
  border: 1px solid #3a4160;
  border-radius: 3px;
}
.mini-deck i:nth-child(2) { transform: translateY(-2px); }
.mini-deck i:nth-child(3) { transform: translateY(-4px); }

.table-blurb { padding: 2px 22px 8px; }
.table-grid {
  flex: 1;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 16px 14px;
  align-content: start;
  padding: 8px 22px 40px;
}
/* session chips: the deck tabs' shape, one visual step quieter (smaller,
   dimmer) — the four tabs are the page's primary nav, sessions its filter.
   The strip stays ONE calm row: renderTableSessions shows the current +
   newest few and folds the long tail behind a "+N older" disclosure chip;
   the group still wraps and drops to its own row on narrow screens. */
.table-decks { flex-shrink: 0; } /* the tabs never squeeze */
.table-sessions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: 1 1 280px;
  min-width: 0;
  animation: decks-up 0.35s ease;
}
.table-sessions:empty { display: none; }
.table-sessions::before { /* divider travels (and wraps) with the group */
  content: "";
  width: 1px;
  height: 28px;
  background: #354236;
  flex-shrink: 0;
}
.sess-chip { white-space: nowrap; padding: 6px 10px; font-size: 0.74rem; }
.sess-chip.active { border-color: var(--accent-2); }
/* the fold: reads as an affordance, not as another session */
.sess-more { border-style: dashed; color: var(--text-dim); }
.sess-more:hover { border-color: var(--accent-2); }

.tile {
  position: relative;
  cursor: pointer;
  animation: tile-in 0.32s ease backwards; /* per-tile delay set inline */
  transition: opacity 0.25s; /* dulling fades BOTH ways on session clicks */
}
/* previous-session entries read as "archive": dimmed poster + 🗂 corner tag */
.tile.prev-sess .tile-img { filter: saturate(0.55) brightness(0.8); }
/* a session is selected: everything OUTSIDE it dulls out, order untouched */
.tile.dulled { opacity: 0.35; }
.tile.dulled:hover, .tile.dulled:focus-visible { opacity: 0.95; }
.tile-sess {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 2;
  font-size: 0.66rem;
  background: rgba(14, 19, 15, 0.78);
  border-radius: 6px;
  padding: 2px 6px;
  pointer-events: none;
}
/* …and the cards slide DOWN out of the lifted stack into their tile spots */
@keyframes tile-in { from { opacity: 0; transform: translateY(-22px); } }
.tile-img {
  display: block;
  aspect-ratio: 2 / 3;
  border-radius: 10px;
  background-size: cover;
  background-position: center top;
  background-color: var(--surface-2);
  border: 1px solid #2c3143;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  transition: transform 0.15s, box-shadow 0.15s;
}
.tile:hover .tile-img, .tile:focus-visible .tile-img {
  transform: translateY(-5px);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.5);
}
.tile-title {
  display: block;
  margin-top: 6px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile-sub { display: block; font-size: 0.7rem; }
.tile-actions {
  position: absolute;
  top: 8px;
  right: 8px;
  display: none;
  flex-direction: column;
  gap: 4px;
  z-index: 2;
}
.tile:hover .tile-actions, .tile:focus-within .tile-actions { display: flex; }
.tile-actions .mini-btn { background: rgba(15, 17, 23, 0.88); }

/* ============ 🔍 search view: the table-view language, opened from the
   topbar circle — the bar waits dead-center (blooming open, focused), then
   docks to the top the moment results land and tiles deal in below ============ */
.search-view .gs-hub {
  display: flex;
  justify-content: center;
  padding: 0 22px;
  margin-top: calc(50vh - 150px);
  transition: margin-top 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.search-view.docked .gs-hub { margin-top: 0; }
.gs-hub input {
  width: min(640px, 92vw);
  background: var(--surface-2);
  border: 1px solid #354236;
  border-radius: 27px;
  color: var(--text);
  padding: 15px 24px;
  font-size: 1.02rem;
  text-align: center;
  outline: none;
  animation: gs-expand 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
  transition: border-color 0.15s, box-shadow 0.3s;
}
/* the topbar's circle blooming into the full bar */
@keyframes gs-expand { from { width: 54px; opacity: 0; } }
.gs-hub input:focus {
  border-color: var(--accent-2);
  box-shadow: 0 0 22px rgba(var(--accent-2-rgb), 0.16);
}
.search-view .table-blurb { text-align: center; }
/* search tiles already brewed: verdict badge (via .tile-sess) + dimmed poster */
.tile.in-pile .tile-img { filter: saturate(0.6) brightness(0.78); }

.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-2);
  color: var(--text);
  padding: 10px 18px;
  border-radius: 20px;
  font-size: 0.85rem;
  z-index: 300;
  max-width: 90vw;
  animation: toast-in 0.2s ease;
}
@keyframes toast-in { from { opacity: 0; transform: translateX(-50%) translateY(10px); } to { opacity: 1; transform: translateX(-50%); } }

/* desktop: ALL piles are full card-sized stacks, mostly tucked off-screen.
   They share the deck's fixed viewport coordinates so spacing is exact.
   Hover slides a pile in a touch and fans the cards — "there's more here". */
@media (min-width: 761px) {
  .pile-left, .pile-right, .pile-top {
    position: fixed;
    width: var(--deck-w);
    height: var(--deck-h);
    z-index: 4;
    display: block;
    /* opacity MUST transition too (and lag behind), or .to-table makes the
       pile vanish instantly mid-fan — the deck flies up still fanned open */
    transition: transform 0.34s ease, opacity 0.24s ease 0.14s;
  }
  /* all anchored the same way for guaranteed symmetry; half of each side pile
     stays on-screen so its floating title fits within the card's sliver */
  .pile-left  { top: 50%; left: 0;    right: auto; transform: translate(-50%, -50%) rotate(-2deg); }
  .pile-right { top: 50%; left: 100%; right: auto; transform: translate(-50%, -50%) rotate(2deg); }
  .pile-top   { top: 0;   left: 50%;  right: auto; transform: translate(-50%, -74%) rotate(1deg); }
  .pile-left:hover  { transform: translate(-48%, -50%) rotate(-1deg); }
  .pile-right:hover { transform: translate(-52%, -50%) rotate(1deg); }
  .pile-top:hover   { transform: translate(-50%, -64%) rotate(0.5deg); }

  /* NOT width/height:100% — Safari collapses %-heights inside <button>
     (see --deck-w/--deck-h note in :root); same explicit size as the pile */
  .pile-left .pile-cards, .pile-right .pile-cards, .pile-top .pile-cards { width: var(--deck-w); height: var(--deck-h); }
  .pile-left .pile-cards img, .pile-right .pile-cards img, .pile-top .pile-cards img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--radius);
    /* No drop shadow and no covering wash: each card is dimmed INDIVIDUALLY
       (brightness) so the side decks recede — there's no overlay rectangle, so
       no sharp border to bleed. A hair-thin rim keeps each poster reading as a
       distinct card. Hover brings the stack up to full brightness. */
    border: 1px solid rgba(233, 237, 232, 0.08);
    filter: brightness(0.46);
    transition: transform 0.28s ease, filter 0.25s ease;
  }
  .pile-left:hover .pile-cards img, .pile-right:hover .pile-cards img,
  .pile-top:hover .pile-cards img { filter: brightness(1); }
  .pile-left .pile-cards img:nth-child(1), .pile-top .pile-cards img:nth-child(1) { transform: rotate(-3deg); }
  .pile-left .pile-cards img:nth-child(2), .pile-top .pile-cards img:nth-child(2) { transform: rotate(2deg); }
  .pile-right .pile-cards img:nth-child(1) { transform: rotate(3deg); }
  .pile-right .pile-cards img:nth-child(2) { transform: rotate(-2deg); }
  .pile-left .pile-cards img:nth-child(3), .pile-right .pile-cards img:nth-child(3),
  .pile-top .pile-cards img:nth-child(3) { transform: rotate(0deg); }
  /* (the old dark-wash ::before overlay is gone — it drew a hard rounded-rect
     edge that read as a border; the per-card brightness dim above replaces it) */

  /* the fan, a smidge */
  .pile-left:hover .pile-cards img:nth-child(1) { transform: rotate(-8deg) translateY(-8px); }
  .pile-left:hover .pile-cards img:nth-child(2) { transform: rotate(4deg) translateY(-3px); }
  .pile-right:hover .pile-cards img:nth-child(1) { transform: rotate(8deg) translateY(-8px); }
  .pile-right:hover .pile-cards img:nth-child(2) { transform: rotate(-4deg) translateY(-3px); }
  .pile-top:hover .pile-cards img:nth-child(1) { transform: rotate(-7deg) translateX(-8px); }
  .pile-top:hover .pile-cards img:nth-child(2) { transform: rotate(4deg) translateX(5px); }

  /* clicked pile bursts wide open on its way to becoming the table grid */
  .pile.expanding { z-index: 30; }
  .pile.expanding .pile-cards img { transition-duration: 0.2s; }
  .pile.expanding .pile-cards img:nth-child(1) { transform: rotate(-38deg) translate(-150px, -70px) scale(1.1); }
  .pile.expanding .pile-cards img:nth-child(2) { transform: rotate(30deg) translate(145px, -48px) scale(1.1); }
  .pile.expanding .pile-cards img:nth-child(3) { transform: rotate(-6deg) translateY(-110px) scale(1.22); }

  /* pile titles float just OFF the stack — above the side piles, below the
     top one (its body hangs off-screen) — centered on the visible sliver,
     never wider than the card, with clear air between title and deck */
  .pile-left .pile-label, .pile-right .pile-label, .pile-top .pile-label {
    position: absolute;
    z-index: 3;
    font-size: 1.58rem;    /* 1.5× the old 1.05rem */
    letter-spacing: 1.8px; /* tracking scaled 1.5× to preserve the spacing */
  }
  /* the side piles hang half off-screen: the sliver's midpoint sits 25% in
     from the screen edge — nudged 3% inward to cancel the horizontal shift
     the pile's ±2deg rotation gives a label floating this far above center */
  .pile-left .pile-label { bottom: calc(100% + 20px); left: 78%; transform: translateX(-50%); }
  .pile-right .pile-label { bottom: calc(100% + 20px); left: 22%; transform: translateX(-50%); }
  .pile-top .pile-label { top: calc(100% + 16px); left: 50%; transform: translateX(-50%); }

  .pile-left.empty .pile-cards, .pile-right.empty .pile-cards, .pile-top.empty .pile-cards {
    border: 2px dashed #2c3143;
    border-radius: var(--radius);
    background: rgba(26, 29, 39, 0.5);
    opacity: 0.8;
  }
  /* empty piles say what belongs in them, on the visible sliver */
  .pile-left.empty .pile-cards::after, .pile-right.empty .pile-cards::after {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 34%;
    font-size: 0.74rem;
    line-height: 1.4;
    color: var(--text-dim);
  }
  .pile-left.empty .pile-cards::after { content: "← swipe left to prune (not your cup)"; right: 10px; text-align: right; }
  .pile-right.empty .pile-cards::after { content: "swipe right when you savored it (seen & loved) →"; left: 10px; text-align: left; }
  .pile-top.empty .pile-cards::after {
    content: "↑ swipe up to steep (want to watch)";
    position: absolute;
    bottom: 44px;
    left: 50%;
    transform: translateX(-50%);
    white-space: nowrap;
    font-size: 0.74rem;
    color: var(--text-dim);
  }
}

/* small screens: all three piles across the top so nothing overlaps the deck */
@media (max-width: 760px) {
  .stage { padding: 4px 12px 12px; }
  .pile-left { left: 10px; top: 0; transform: none; }
  .pile-left:hover { transform: scale(1.06); }
  .pile-right { right: 10px; top: 0; transform: none; }
  .pile-right:hover { transform: scale(1.06); }
  .pile-cards { width: 36px; height: 48px; }
  .pile-cards img { width: 32px; height: 44px; }
  .topbar-actions .chip-btn { padding: 6px 9px; font-size: 0.72rem; }
  /* the pill stays a pill on phones — input just gets less room */
  .search-row { padding-left: 16px; gap: 8px; }
  .search-row input { font-size: 0.9rem; }
  .search-row .search-go { padding: 11px 18px; font-size: 0.88rem; }
}


/* subtle per-review stance marker in filtered comments (What people are saying) */
.cmt-lean{font-size:.72rem;font-weight:700;margin-right:1px;line-height:1;opacity:.85}
.cmt-lean.pos{color:var(--like)}
.cmt-lean.neg{color:var(--skip)}
.cmt-lean.mix{color:var(--accent)}

/* engine "reviewer says the show lacks this" marker in the filtered comment list */
.cmt-lean.absent{color:#9aa0a6;opacity:.8}

/* keyword-heuristic digest chips (no engine backing, e.g. "clichés & tropes") */
.digest-chip.dig-heuristic{border-style:dashed}
.digest-chip.dig-heuristic::before{content:"≈";opacity:.6;margin-right:3px;font-weight:400}

/* ⭐ boost pill state (state.js TAG_STATES.boost — soft-include; sits between
   📌 required and ⛔ avoid in the body-click cycle). Gold family = accent, so
   the three active states read green (have it) / gold (prefer it) / red (never):
   verdict-tinted via the -rgb custom props like every other stateful chip. */
.genre-chip.boost { background: rgba(var(--accent-rgb), 0.14); border-color: var(--accent); color: var(--accent); }
.tag-pill.boost { background: rgba(var(--accent-rgb), 0.16); color: var(--accent); border-color: transparent; }

/* 📉 loosen/boost suggestion strip under the ⚙ match count (app.js
   renderSuggestRow) — chips share the .genre-chip look; dashed border says
   "proposal, not state" (same visual grammar as the AI cue chips). */
.suggest-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
.suggest-row .suggest-chip { border-style: dashed; }
.suggest-row .suggest-chip:hover { border-color: var(--accent); color: var(--accent); }
.suggest-row .suggest-chip.avoid:hover { border-color: var(--skip); color: var(--skip); }
