@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');

/* One dark, monochrome design system across the whole app - not just the landing
   page - so the app doesn't feel like a different, more generic product once you
   sign in. Primary actions are a plain white-on-dark inversion (bg: var(--accent),
   text: var(--bg)) rather than a gradient pill; color is reserved for genuinely
   semantic things (success/danger) and each sport's own series color. */
:root {
  --bg: #000;
  --bg-gradient: radial-gradient(ellipse 70% 40% at 50% 0%, rgba(255, 255, 255, 0.09), transparent 70%);
  --card-bg: #141416;
  --card-bg-soft: #1b1b1e;
  --border: rgba(255, 255, 255, 0.11);
  --text: #f2f2f3;
  --muted: #8b8b92;
  --accent: #f2f2f3;
  --accent-soft: rgba(255, 255, 255, 0.08);
  --danger: #ff6b6b;
  --success: #35d399;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 10px 28px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.55);
  --radius: 12px;
  --radius-sm: 8px;
  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: 'Inter', -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg-gradient), var(--bg);
  background-attachment: fixed;
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.2s ease, color 0.2s ease;
  overflow-x: hidden;
  position: relative;
}

/* Same fine-grain noise the landing page originated (see .landing-noise below),
   now the whole product's atmosphere - a fixed pseudo-layer rather than a real
   element so every template gets it for free, no markup change needed anywhere.
   Kept off the .landing page itself, which already has its own (identical) layer
   via a real element - GSAP's ScrollTrigger there measures real page height, and a
   second fixed full-viewport layer under it is one less thing to account for. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
body:has(.landing)::after { display: none; }

a { color: var(--accent); }

/* Reusable small-caps pill label - the landing hero's "eyebrow" tag, now also used
   to kick off every app page's <h1> so the whole product reads as one confident
   typographic system rather than landing being its own separate thing. */
.eyebrow {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.18);
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
}

.page-header { margin-bottom: 1.75rem; }
.page-header .eyebrow { margin-bottom: 0.85rem; }
.page-header h1 { margin: 0; }

/* ---------- nav ---------- */

.topnav {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.85rem 1.25rem;
  background: color-mix(in srgb, var(--card-bg) 85%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: env(safe-area-inset-top, 0px);
  z-index: 50;
}

.brand-icon { width: 19px; height: 19px; flex-shrink: 0; }
.brand {
  font-weight: 800;
  font-size: 1.05rem;
  text-decoration: none;
  color: var(--text);
  letter-spacing: -0.01em;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
/* A small entrance of its own on load, distinct from (and on top of) .topnav's
   own fade below - the first thing on the page naming whose Cue this is gets a
   beat of its own rather than just being swept along with the whole bar. */
@keyframes brand-reveal { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: translateX(0); } }
.brand-reveal { animation: brand-reveal 0.5s 0.05s ease both; }
@media (prefers-reduced-motion: reduce) {
  .brand-reveal { animation: none; }
}

.nav-links { display: flex; align-items: flex-start; gap: 1.3rem; flex-wrap: wrap; row-gap: 0.5rem; }
/* Icon stacked above the label on every breakpoint - phones just shrink the
   label down to a caption instead of switching to a different layout, so the
   row stays compact without a text reflow at the mobile breakpoint. */
.nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-decoration: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.78rem;
  padding: 0.3rem 0.15rem;
  position: relative;
  transition: color 0.15s ease;
  white-space: nowrap;
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
}
.nav-icon { width: 20px; height: 20px; }
.nav-icon svg { width: 100%; height: 100%; display: block; }
.nav-label { line-height: 1; }

@media (max-width: 520px) {
  .topnav { padding: 0.75rem 1rem; }
  /* The brand line wraps onto its own row above this one at this width (see
     .topnav's flex-wrap) - giving nav-links the full row width and centering
     its items within that is what actually centers the menu block, since a
     lone wrapped flex item otherwise just sizes to its content and sits at
     the row's start. */
  .nav-links { gap: 0.9rem; width: 100%; justify-content: center; }
  .nav-icon { width: 22px; height: 22px; }
  .nav-label { font-size: 0.58rem; letter-spacing: 0.01em; }
}
.nav-item::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -4px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transition: transform 0.18s ease;
}
.nav-item:hover, .nav-item.active, .nav-item[aria-expanded="true"] { color: var(--text); }
.nav-item:hover::after, .nav-item.active::after, .nav-item[aria-expanded="true"]::after { transform: scaleX(1); }

/* ---------- feedback widget (nav) ---------- */

.feedback-widget { position: relative; display: flex; }
.feedback-popover {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 60;
  width: min(400px, calc(100vw - 32px));
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  transform-origin: top right;
  animation: pop-in 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}
/* display:flex above otherwise beats the browser's own [hidden]{display:none} UA
   rule (an author style setting `display` always wins over that default,
   regardless of specificity) - without this override the popover showed by
   default on every page load instead of only after clicking Feedback. */
.feedback-popover[hidden] { display: none; }
.feedback-popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  margin-bottom: 0.1rem;
}
.feedback-popover textarea {
  width: 100%;
  min-height: 8rem;
  resize: vertical;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  line-height: 1.5;
}
.feedback-popover textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.feedback-popover .definable-add { align-self: flex-end; }
.feedback-sent-msg { margin: 0.5rem 0; font-size: 0.88rem; color: var(--success); text-align: center; }

/* ---------- feedback reply banner (calendar page) ---------- */

.feedback-reply-banner {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  padding: 1rem 1.1rem;
  margin-bottom: 1.5rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  animation: pop-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.feedback-reply-icon { flex-shrink: 0; width: 28px; height: 28px; }
.feedback-reply-icon svg { width: 100%; height: 100%; }
.feedback-reply-body { min-width: 0; flex: 1; }
.feedback-reply-label { display: block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 0.3rem; }
.feedback-reply-text { margin: 0; font-size: 0.9rem; line-height: 1.55; white-space: pre-wrap; }

.page { max-width: 1520px; margin: 0 auto; padding: 1.75rem; padding-inline: max(1.25rem, env(safe-area-inset-left)) max(1.25rem, env(safe-area-inset-right)); }

/* The app shell's version of the landing page's scroll-triggered reveal (see the
   IntersectionObserver in base.html) - fade + rise as a section reaches the
   viewport, not just once on load. An element already on screen when the
   observer attaches gets .is-visible added almost immediately, which is what
   makes this also read as a normal page-load transition. */
.topnav, .card, .filters, #calendar-scroll {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.topnav.is-visible, .card.is-visible, .filters.is-visible, #calendar-scroll.is-visible {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .topnav, .card, .filters, #calendar-scroll { opacity: 1; transform: none; transition: none; }
}

/* ---------- landing page ----------
   The landing page's dark, monochrome, oversized-type language (black ground,
   tight-tracking type, minimal chrome, no gradient blobs or pastel cards) is now
   the whole product's design system, not landing's alone - see --bg/body::after/
   .eyebrow/.page-header above. This block is what's left that's still genuinely
   landing-specific: the full-bleed hero layout and its own scroll-reveal timing,
   which the rest of the app has no equivalent of (dense utility pages, not a
   one-time pitch). Never switches with prefers-color-scheme - always dark. */

.landing {
  max-width: none;
  padding: 0;
  overflow-x: hidden;
  background: var(--bg);
  color: #f2f2f2;
  min-height: 100vh;
}

.landing-noise {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: #000;
}
.landing-noise::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 60% 45% at 50% 0%, rgba(255, 255, 255, 0.09), transparent 70%);
}
.landing-noise::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

.hero {
  min-height: min(82vh, 720px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem clamp(1.25rem, 4vw, 3rem) 4rem;
  /* No .landing-nav above this anymore to clear a phone's notch/status bar - the
     hero is the top of the page now, so it carries that safe-area padding itself. */
  padding-top: calc(2rem + env(safe-area-inset-top, 0px));
  text-align: center;
}
.hero-inner { max-width: 620px; display: flex; flex-direction: column; align-items: center; gap: 1.4rem; }
.hero h1 {
  font-size: clamp(2.4rem, 7vw, 4.4rem);
  /* 600 rather than the old 700 - it's the strong (800, below) that needs to read
     as the bold one, and 700-vs-800 at this size was too close to actually tell
     apart. Widening the gap this way instead of pushing the accent past 800 (no
     900 weight is loaded) is what makes "Cue" the one that visibly pops. */
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1.03;
  margin: 0;
  color: rgba(255, 255, 255, 0.68);
}
.hero h1 strong { font-weight: 800; color: #fff; }
.hero-sub { color: rgba(255, 255, 255, 0.6); font-size: 1.05rem; line-height: 1.6; max-width: 480px; }
.hero-note { font-size: 0.78rem; color: rgba(255, 255, 255, 0.4); }

.landing .google-btn {
  background: #fff;
  color: #000;
  border-radius: 999px;
  box-shadow: none;
  font-weight: 600;
}
.landing .google-btn:hover { transform: translateY(-1px); box-shadow: 0 8px 24px rgba(255, 255, 255, 0.15); filter: none; background: #f0f0f0; }

/* Real, already-stored fixtures shown right under the hero - the pitch ("real
   sources, not guesses") made visually true immediately, rather than three more
   scrolls of prose before any proof shows up. See .fixture-card above. */
.landing-fixtures { padding: 0 clamp(1.25rem, 4vw, 3rem) 1rem; }
.landing-fixtures-inner { max-width: 1100px; margin: 0 auto; }
.landing-fixtures .fixture-strip { margin-top: 1rem; margin-bottom: 0; }

.features { padding: 3rem clamp(1.25rem, 4vw, 3rem) 5rem; max-width: 780px; margin: 0 auto; }
.feature-list { display: flex; flex-direction: column; }
.feature-row {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: 1.25rem;
  padding: 1.75rem 0;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.feature-row:last-child { border-bottom: 1px solid rgba(255, 255, 255, 0.12); }
.feature-index { font-size: 0.8rem; color: rgba(255, 255, 255, 0.35); font-weight: 600; padding-top: 0.15rem; }
.feature-row h3 { margin: 0 0 0.5rem; font-size: 1.05rem; letter-spacing: -0.01em; color: #fff; font-weight: 600; }
.feature-row p { margin: 0; color: rgba(255, 255, 255, 0.55); font-size: 0.92rem; line-height: 1.6; max-width: 46ch; }

.landing-footer { text-align: center; padding: 2rem 1.5rem 3rem; color: rgba(255, 255, 255, 0.35); font-size: 0.78rem; display: flex; flex-direction: column; gap: 0.5rem; }
.landing-footer-links a { color: rgba(255, 255, 255, 0.45); text-decoration: none; }
.landing-footer-links a:hover { color: rgba(255, 255, 255, 0.75); text-decoration: underline; }

.button, .google-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 1.25rem;
  padding: 0.7rem 1.5rem;
  background: var(--accent);
  color: var(--bg);
  border-radius: 999px;
  text-decoration: none;
  border: none;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.button:hover, .google-btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); filter: brightness(1.05); }
.button:active, .google-btn:active { transform: translateY(0); }

.error {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  padding: 0.6rem 0.9rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}

/* ---------- fixture strip (shared: calendar's "Up next", landing's real-fixture
   strip) - a small horizontal row of real, already-stored events, styled like a
   landing feature row rather than a generic card, since both are meant to read as
   proof/payoff rather than another data table. */

.fixture-strip { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1.25rem; margin-bottom: 2rem; }
/* Each day is its own group (label stacked above that day's card(s), built
   client-side in calendar.js, grouped by local calendar day) - the groups are
   what sit inline in the strip and wrap, not the label and cards separately, so
   the label always reads as "over these events" rather than just another item
   in the row before them. */
.fixture-day-group { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
.fixture-day-header {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.fixture-day-cards { display: flex; flex-wrap: wrap; gap: 0.85rem; }
.fixture-card {
  --ev-color: #9a9aa2;
  flex: 1 1 220px;
  max-width: 340px;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1.1rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--ev-color);
  border-radius: var(--radius);
  text-align: left;
  cursor: default;
  font: inherit;
  color: inherit;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
button.fixture-card { appearance: none; cursor: pointer; }
.fixture-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
/* Same treatment as .series-panel-list li.panel-past - an event that's already
   started stays visible instead of vanishing, just dimmed. */
.fixture-card.is-past { opacity: 0.45; }
.fixture-logo { width: 36px; height: 36px; object-fit: contain; border-radius: 6px; flex-shrink: 0; background: #fff; padding: 3px; box-sizing: border-box; }
.fixture-icon { font-size: 1.4rem; flex-shrink: 0; width: 36px; text-align: center; }
.fixture-body { min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.fixture-series { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.fixture-title { font-size: 0.92rem; font-weight: 600; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fixture-time { font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Landing's version sits on plain black with no card chrome (nothing else on that
   page has any either) - a bordered card there would look like a stray app
   fragment glued onto the pitch instead of part of it. */
.landing .fixture-card { background: transparent; border-color: rgba(255, 255, 255, 0.14); }
.landing .fixture-card:hover { border-color: var(--ev-color); }
.landing .fixture-series { color: rgba(255, 255, 255, 0.45); }
.landing .fixture-time { color: rgba(255, 255, 255, 0.45); }

/* ---------- calendar layout ---------- */

.calendar-layout {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 1.5rem;
  align-items: start;
}

@media (max-width: 760px) {
  .calendar-layout { grid-template-columns: 1fr; }
  /* position:sticky on a grid sibling can visually overlap the item below it once
     the layout collapses to one column - just let it flow normally on mobile. */
  .filters { position: static; }
  /* With 15+ followed series the filter list is now genuinely long - on a phone,
     showing it before the calendar buries the thing you actually opened the page
     for below a full screen of checkboxes. Grid `order` puts the calendar first
     without needing to reorder the HTML (filters stay first in the DOM/tab order
     for keyboard/screen-reader users, only the visual position changes). */
  #calendar-scroll { order: 1; }
  .filters { order: 2; }
}

.filters-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 0.75rem;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.filters-toggle-icon { color: var(--muted); width: 16px; height: 16px; transition: transform 0.15s ease; flex-shrink: 0; }
.filters.collapsed .filters-toggle-icon { transform: rotate(-90deg); }
.filters.collapsed .filters-body { display: none; }
/* .filters-toggle's own margin-bottom exists to space it from .filters-body -
   with the body hidden there's nothing below it to space away from, so that
   margin just became extra padding under the label/chevron alone, pushing them
   above center inside the now-taller-than-it-needs-to-be collapsed box (equal
   padding above and below the toggle otherwise, from .filters itself). Zeroing
   it here is what actually centers the row and trims the box down to just the
   toggle's own height plus that shared padding. */
.filters.collapsed .filters-toggle { margin-bottom: 0; }
@media (min-width: 761px) {
  /* The toggle only does something on mobile (see below) - on desktop the
     Following list is always expanded, so the chevron/click affordance would
     just be noise. */
  .filters-toggle { cursor: default; }
  .filters-toggle-icon { display: none; }
  .filters.collapsed .filters-body { display: block; }
}

/* Same collapse pattern as .filters-toggle above, generalized for any card
   section (currently the Add page's Sports/TV Shows) - expanded by default at
   any width, unlike Following, since browsing is the point of landing here.
   Both this and .icon-btn below are prefixed with .card - a plain .card button
   rule further down (class+element, higher specificity than a bare single
   class) styles every unclassed <button> inside a card as an accent pill, and
   without the prefix it silently wins over these regardless of source order. */
.card .section-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 0.75rem;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.section-toggle h2 { margin: 0; }
.section-toggle-icon { color: var(--muted); transition: transform 0.15s ease; flex-shrink: 0; }
.card.is-collapsed .section-toggle-icon { transform: rotate(-90deg); }
.card.is-collapsed .section-body { display: none; }

/* A small circular icon-only button - dismissing a panel, closing something -
   distinct from .panel-close (which is sized/positioned for the slide-in panel
   specifically) so it can drop into an ordinary header's flex row anywhere
   (a .card section header, or the card-less feedback reply banner). Declared
   twice on purpose: the bare rule covers non-card contexts, the .card-prefixed
   one wins the specificity fight against the plain ".card button" catch-all
   below when it happens to be nested inside one. */
.icon-btn, .card .icon-btn {
  flex-shrink: 0;
  width: 26px; height: 26px;
  margin-top: 0;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.icon-btn:hover, .card .icon-btn:hover { background: var(--accent-soft); color: var(--text); transform: none; box-shadow: none; filter: none; }
.section-header-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.75rem; }
.section-header-row h2 { margin: 0; }

.filters {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem;
  box-shadow: var(--shadow-sm);
  position: sticky;
  top: 5.5rem;
  /* A CSS Grid item's automatic minimum width defaults to its content's
     min-content size, not the grid track it was given - .series-name's
     white-space:nowrap makes that min-content the full unwrapped series name,
     which on mobile (a single 1fr column) blew this whole panel out past the
     viewport instead of respecting its column and letting the name ellipsize. */
  min-width: 0;
}
.filters h3 { margin: 0 0 0.75rem; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
/* Same selector specificity as .filters h3 above (one class + one element each),
   so source order alone decided the tie - this coming second is what actually
   zeroes the toggle button's own heading margin instead of silently losing to
   the rule above. Without it, the bottom margin the rule above sets stayed part
   of the h3's flex-alignment box, which pushed the visible "Following" text
   above center relative to the chevron next to it (which has no margin) even
   though .filters-toggle has align-items:center. */
.filters-toggle h3 { margin: 0; }

.series-filter { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.series-filter li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem;
  border-radius: var(--radius-sm);
  transition: background 0.15s ease;
}
/* Same colored-left-border "bracket" the calendar's own event chips use (.ev),
   so a series' color reads consistently whether you're looking at it on the
   calendar or in the Following list - logo-only rows had no color cue at all
   before this, only the ones falling back to the plain .dot did. */
.series-filter li[data-series-id] { border-left: 3px solid var(--row-color, transparent); }
.series-filter li:hover { background: var(--accent-soft); }
.series-filter li:hover .unfollow-btn { opacity: 1; }
.series-group-header { padding: 0.6rem 0.5rem 0.25rem; }
.series-group-header:first-child { padding-top: 0; }
.series-name { line-height: 1.3; min-width: 0; flex: 1; font-size: 0.87rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tag-pill {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.7);
  background: transparent;
  border-radius: 0;
  border-bottom: 2px solid var(--border);
  padding: 0.3rem 0.1rem 0.55rem;
}
.unfollow-btn {
  opacity: 0;
  flex-shrink: 0;
  width: 22px; height: 22px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.unfollow-btn:hover { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
@media (hover: none) {
  /* 22px is well under the ~40-44px touch-target guideline for a destructive
     action - grow the hit area on touch devices without changing how it looks on
     a mouse-driven desktop, where hover already reveals it at the smaller size. */
  .unfollow-btn { opacity: 1; width: 40px; height: 40px; font-size: 1.2rem; }
}
.dot {
  width: 40px; height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  flex-shrink: 0;
}
/* Logos are always the source's own real badge/crest image, but plenty of them
   (e.g. MotoGP's wordmark) are plain dark artwork on a transparent background -
   fine on the source's own white page, unreadable on this app's dark theme
   without a real backing of their own. A small white card behind every logo
   fixes that uniformly, and barely shows behind crests that already carry their
   own solid color. */
.filter-logo { width: 40px; height: 40px; object-fit: contain; border-radius: 4px; flex-shrink: 0; background: #fff; padding: 1px; box-sizing: border-box; }
.add-link {
  display: block;
  margin-top: 0.9rem;
  text-align: center;
  font-size: 0.82rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--accent);
  padding: 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px dashed var(--border);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.add-link:hover { background: var(--accent-soft); border-color: var(--accent); }

/* Add page's browsable "already defined" lists - same row shape as the Following
   sidebar's .series-filter, but a plain row (no checkbox) plus an explicit Add
   button instead of a label wrapping everything. */
.definable-list li { padding: 0.15rem 0; }
.definable-row { display: flex; align-items: center; gap: 0.55rem; flex: 1; min-width: 0; padding: 0.35rem 0.5rem; }
@media (max-width: 480px) {
  /* A league/TV row's name competes with a two-button "League"/"Teams" pair for
     space - on a phone that pair alone is wider than what's left after the logo,
     squeezing the name down to a handful of characters. Let the controls drop to
     their own line instead of fighting the name for the same row. */
  .definable-list li { flex-wrap: wrap; row-gap: 0.35rem; }
  .definable-list li .add-control { width: 100%; justify-content: flex-end; }
}
.definable-add {
  flex-shrink: 0;
  padding: 0.35rem 0.85rem;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  font-size: 0.82rem;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.definable-add:hover { transform: translateY(-1px); filter: brightness(1.05); }
.definable-add:disabled { opacity: 0.6; cursor: default; transform: none; }
.definable-add.secondary { background: var(--card-bg-soft); color: var(--text); border: 1px solid var(--border); }
.add-control { display: flex; gap: 0.4rem; flex-shrink: 0; }

/* Inline "follow specific teams instead of the whole league" picker, opened by the
   league row's "Pick teams" button - replaces that row's add-control in place. */
.team-picker { display: flex; flex-direction: column; gap: 0.35rem; min-width: 220px; }
.team-picker-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
/* .team-picker-header .team-picker-close (2 classes), not just .team-picker-close
   (1 class): when this picker opens from an AI search result, it's nested inside
   .candidate-list, whose ".candidate-list button" rule (class+element) outranks a
   single class regardless of source order - without the bump it silently won and
   rendered this as an oversized solid pill instead of a small transparent x, only
   when opened from that one context (fine from the Sports/TV panes, which have no
   equivalent generic button rule) - exactly the "inconsistent depending on where
   you opened it" bug this looked like. */
.team-picker-header .team-picker-close {
  flex-shrink: 0;
  width: 22px; height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.team-picker-header .team-picker-close:hover {
  background: color-mix(in srgb, var(--danger) 15%, transparent);
  color: var(--danger);
  transform: none;
}
.team-picker-input {
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  color: var(--text);
  font-size: 0.82rem;
}
.team-picker-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.team-picker-results { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.team-picker-results li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  font-size: 0.82rem;
}
.team-picker-results li.muted { background: none; justify-content: flex-start; }
.team-picker-results button {
  flex-shrink: 0;
  padding: 0.2rem 0.65rem;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  font-size: 0.76rem;
  cursor: pointer;
}

.team-checklist {
  list-style: none;
  margin: 0;
  padding: 0.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
}
.team-checklist li { border-radius: 6px; }
.team-checklist li:hover { background: var(--accent-soft); }
.team-checklist label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.82rem;
  cursor: pointer;
}
.team-checklist input[type="checkbox"] { accent-color: var(--accent); width: 15px; height: 15px; flex-shrink: 0; }
.team-checklist label.already-following { opacity: 0.55; cursor: default; }
.team-checklist label.already-following .muted { margin-left: auto; font-size: 0.72rem; }
.team-apply-btn { align-self: flex-start; }

/* FullCalendar puts its own "fc" classes directly onto the element it's given as a
   render target, rather than inserting a separate child wrapper - so #calendar
   itself becomes the ".fc" root. That element can't simultaneously be both "the
   thing that needs a 560px minimum width" and "the box that clips/scrolls
   overflow" (a box can't scroll its own min-width away). #calendar-scroll is the
   actual card/scroll container; #calendar is the FullCalendar root inside it. */
#calendar-scroll {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
  position: relative;
}
#calendar { min-width: 560px; transition: opacity 0.16s ease; }
/* Toggled briefly in calendar.js around a genuine view switch (month/week/list) -
   crossfades the swap instead of the grid popping straight from one layout to
   another. Cleared again once eventsSet fires for the new view's data. */
#calendar.view-transitioning { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  #calendar { transition: none; }
}
@media (max-width: 640px) {
  /* listWeek/listMonth (the mobile default view) reads fine at any width - only the
     day-grid views need the horizontal-scroll safety net above. */
  #calendar:has(.fc-list) { min-width: 0; }
  #calendar-scroll { padding: 0.85rem; }
  .fc .fc-toolbar { flex-wrap: wrap; row-gap: 0.6rem; }
  .fc .fc-toolbar-title { font-size: 1rem; }
  .fc .fc-button { padding: 0.35rem 0.6rem; font-size: 0.82rem; }
  /* Grid views (not list) overflow the viewport with no native scrollbar visible
     enough to suggest that - a persistent edge fade hints there's more to the
     right without needing JS to track scroll position. */
  #calendar-scroll:has(#calendar:not(:has(.fc-list)))::after {
    content: "";
    position: absolute;
    top: 0.85rem; bottom: 0.85rem; right: 0;
    width: 28px;
    background: linear-gradient(to right, transparent, var(--card-bg) 88%);
    pointer-events: none;
  }
}

/* FullCalendar overrides for a softer, more modern look */
.fc {
  --fc-border-color: var(--border);
  --fc-page-bg-color: transparent;
  --fc-neutral-bg-color: var(--card-bg-soft);
  /* List view's row hover defaults to a light gray built for a light theme - reads
     as a jarring white flash against this dark one, on both mouse hover and the
     "stuck" hover state touch devices leave behind after a tap. */
  --fc-list-event-hover-bg-color: var(--accent-soft);
}
.fc .fc-toolbar-title { font-size: 1.2rem; font-weight: 800; letter-spacing: -0.01em; }
.fc .fc-button {
  background: var(--card-bg-soft);
  border: 1px solid var(--border);
  color: var(--text);
  box-shadow: none;
  text-transform: capitalize;
  font-weight: 600;
  border-radius: 999px !important;
  transition: background 0.15s ease, transform 0.1s ease;
}
.fc .fc-button:hover { background: var(--accent-soft); transform: translateY(-1px); }
/* FullCalendar's default .fc-button-group styling overlaps adjacent buttons
   (negative margin) and squares off the touching edges so a group reads as one
   connected segmented control - looks right for rectangular buttons, but pill-
   shaped ones need real daylight between them instead to read as separate round
   buttons, like the individual pill buttons on x.ai. */
.fc .fc-button-group { gap: 0.4rem; }
.fc .fc-button-group > .fc-button { margin-left: 0 !important; }
.fc .fc-button-primary:not(:disabled).fc-button-active,
.fc .fc-button-primary:not(:disabled):active {
  background: var(--accent);
  border-color: transparent;
  color: var(--bg);
}
.fc .fc-day-today { background: var(--accent-soft) !important; }
.fc-theme-standard td, .fc-theme-standard th { border-color: var(--border); }
.fc .fc-daygrid-day-number, .fc .fc-col-header-cell-cushion { color: var(--text); font-weight: 600; text-decoration: none; }
/* Today's date gets a solid filled badge, not just a faint tinted cell background -
   the same treatment Google/Apple Calendar use, since it reads instantly at a glance
   rather than requiring you to notice a subtle shade difference. */
.fc .fc-day-today .fc-daygrid-day-number {
  background: var(--accent);
  color: var(--bg);
  border-radius: 50%;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  margin: 4px;
  padding: 0;
}
.fc .fc-list-day.fc-day-today .fc-list-day-cushion { background: var(--accent-soft); }
.fc .fc-list-day.fc-day-today .fc-list-day-text,
.fc .fc-list-day.fc-day-today .fc-list-day-side-text { color: var(--accent); font-weight: 800; }

/* FullCalendar's built-in "+N more" popover is bypassed in favor of the day panel
   (see moreLinkClick in calendar.js), but style it defensively anyway in case it
   ever appears - its default light-theme styling was unreadable against this dark
   design (looked like a broken transparent overlay). */
.fc .fc-popover {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  color: var(--text);
}
.fc .fc-popover-header { background: var(--card-bg-soft); color: var(--text); }
.fc .fc-popover-close { color: var(--muted); }

.fc-event { border: none !important; background: transparent !important; cursor: pointer; }
.ev {
  --ev-color: var(--accent);
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 2px 6px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--ev-color) 16%, var(--card-bg));
  border-left: 3px solid var(--ev-color);
  overflow: hidden;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.ev:hover { transform: translateY(-1px) scale(1.01); box-shadow: var(--shadow-sm); }
/* Freshly-rendered events rise/fade in with a small per-event stagger (--i set in
   calendar.js's eventDidMount) instead of popping in all at once - the animation
   is cleared (animationend -> animationName:none, same pattern base.html already
   uses for .card/.filters) once it finishes, so it stops holding the `transform`
   property and .ev:hover's own transition above can take over again; otherwise a
   finished-but-still-"filling" CSS animation on the same property permanently
   outranks a transition on it, and hover would silently stop moving the chip. */
@keyframes ev-rise { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
.fc-event:not(.fc-placeholder-row) .ev { animation: ev-rise 0.26s ease both; animation-delay: calc(var(--i, 0) * 22ms); }
@media (prefers-reduced-motion: reduce) {
  .fc-event:not(.fc-placeholder-row) .ev { animation: none; }
}
.ev-logo { width: 14px; height: 14px; object-fit: contain; border-radius: 3px; flex-shrink: 0; background: #fff; padding: 1px; box-sizing: border-box; }
.ev-icon { font-size: 0.75rem; flex-shrink: 0; }
.ev-time { font-weight: 700; font-size: 0.72rem; color: var(--muted); flex-shrink: 0; }
.ev-title { font-size: 0.76rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ev-title strong { font-weight: inherit; }
/* Synthetic placeholder row (see withEmptyDayPlaceholders/eventDidMount in
   calendar.js) so list view still shows every day, not just ones with events -
   looks and behaves like nothing is there, since nothing is. */
.fc-placeholder-row { pointer-events: none; }
.ev-empty-day { color: var(--muted); font-style: italic; padding: 2px 6px; border-left: 3px solid transparent; }
/* Day-grid cells have an explicit width from the grid column, so nowrap+ellipsis
   correctly clips there. FullCalendar's list view has no such constraint on its
   title cell - nowrap text just grew the table (and the whole page) wider than the
   viewport instead of truncating. Let it wrap normally there instead. */
.fc-list-event-title .ev-title { white-space: normal; overflow: visible; text-overflow: clip; }
.fc-list-event-title .ev { overflow: visible; }
/* FullCalendar's own list-view dot (colored via the event's border/background
   color) duplicates the same color the custom .ev chip already shows as a left
   border - redundant, so hide FullCalendar's copy and let the chip's own border
   be the only color cue. */
.fc-list-event-dot { display: none !important; }

/* Hovering a Following row spotlights that series' events (see calendar.js) -
   brighten + glow using the event's own color rather than one fixed highlight
   color, so it still reads correctly against any series' color. */
.fc-event.ev-spotlight { position: relative; z-index: 4; }
.fc-event.ev-spotlight .ev {
  background: color-mix(in srgb, var(--ev-color) 32%, var(--card-bg));
  box-shadow: 0 0 0 2px var(--ev-color);
}
/* A day past dayMaxEvents folds its extra events behind "+N more" - FullCalendar
   never renders a folded event as an element at all, so a spotlighted series with
   something hidden there has nothing to highlight directly. Highlighting the
   "+more" link itself is the next best indicator that it's under there. */
.fc-daygrid-more-link.ev-spotlight-more {
  background: var(--accent) !important;
  color: var(--bg) !important;
  border-radius: 4px;
  box-shadow: 0 0 0 2px var(--accent);
}

/* ---------- event popover ---------- */

.event-popover {
  position: absolute;
  z-index: 200;
  width: min(320px, calc(100vw - 24px));
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 1rem;
  font-size: 0.85rem;
  /* transform-origin is set per-open in positionPopover() to the hovered event's
     own position, so this grows from what you actually hovered instead of a flat
     top-left corner - default here only as a fallback before JS sets a real one. */
  transform-origin: top center;
  animation: pop-in 0.16s cubic-bezier(0.16, 1, 0.3, 1);
  /* In a stacked list (a busy day cell, a list view, the "Up next" strip), this
     panel opens right over whatever sits just below the hovered event - without
     this, its own (otherwise inert) body would win every hit-test there, so a
     mouse moving down toward the next event never reaches it and gets stuck on
     the first popover forever. pointer-events:none here lets that hover pass
     straight through to the real event underneath; only the actually-interactive
     bits opt back in below. */
  pointer-events: none;
}
@keyframes pop-in { from { opacity: 0; transform: scale(0.94) translateY(2px); } to { opacity: 1; transform: scale(1) translateY(0); } }
.pop-header { display: flex; align-items: flex-start; gap: 0.6rem; margin-bottom: 0.6rem; }
.pop-logo { width: 32px; height: 32px; object-fit: contain; border-radius: 6px; background: #fff; padding: 3px; box-sizing: border-box; }
.pop-icon { font-size: 1.4rem; }
.pop-series { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 700; }
.pop-title { font-weight: 700; font-size: 0.95rem; line-height: 1.25; }
.pop-row { display: flex; align-items: center; gap: 0.45rem; margin-bottom: 0.4rem; color: var(--text); }
.pop-channel-logo { width: 16px; height: 16px; object-fit: contain; border-radius: 3px; flex-shrink: 0; }
.pop-desc { color: var(--muted); font-size: 0.82rem; line-height: 1.4; margin: 0.5rem 0; max-height: 6em; overflow: auto; pointer-events: auto; }
.pop-link { display: inline-block; margin-top: 0.3rem; font-weight: 600; font-size: 0.82rem; text-decoration: none; pointer-events: auto; }
.pop-link:hover { text-decoration: underline; }
.pop-actions { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; margin-top: 0.4rem; }
.pop-open-list {
  flex-shrink: 0;
  margin-left: auto;
  padding: 0.3rem 0.7rem;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  font-size: 0.78rem;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
  pointer-events: auto;
}
.pop-open-list:hover { transform: translateY(-1px); filter: brightness(1.05); }

/* ---------- series list panel (opened by clicking a calendar event) ---------- */

.panel-backdrop {
  position: fixed; inset: 0;
  background: rgba(10, 10, 20, 0.35);
  backdrop-filter: blur(2px);
  z-index: 300;
  animation: fade-in 0.15s ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.series-panel {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(420px, 100vw);
  z-index: 301;
  background: var(--card-bg);
  box-shadow: var(--shadow-lg);
  animation: slide-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  display: flex;
  flex-direction: column;
}
/* [hidden] and .series-panel{display:flex} tie on specificity; without this, the
   later rule in the cascade (ours) wins and the panel stays visible even when the
   `hidden` attribute is set. This was a real bug: the panel sat fixed over the page
   permanently, covering content/nav, regardless of open/closed state. */
.series-panel[hidden] { display: none; }
@keyframes slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }

.series-panel-inner {
  padding: 1.5rem;
  padding-top: calc(1.5rem + env(safe-area-inset-top, 0px));
  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.panel-close {
  position: absolute;
  top: 1rem; right: 1rem;
  /* Above .series-panel-header's sticky z-index:1 - without this the header
     (opaque background, same stacking context) painted over the button and
     ate its clicks, since a position:absolute element with the default
     z-index:auto loses to any sibling with an explicit z-index regardless of
     DOM order. */
  z-index: 2;
  width: 32px; height: 32px;
  border: none;
  border-radius: 50%;
  background: var(--card-bg-soft);
  color: var(--text);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease;
}
.panel-close:hover { background: var(--accent-soft); }

/* Sticky, not static: renderPanelList() auto-scrolls to the first upcoming event
   whenever the list is long enough to overflow (skipped only for the hidden-events
   panel), which otherwise pushes this header - the only thing saying which day or
   series you're looking at - out of view above the fold. Pinning it keeps that
   context visible no matter how far the list scrolls. */
.series-panel-header {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 1.25rem;
  padding-right: 2rem;
  position: sticky;
  top: 0;
  background: var(--card-bg);
  z-index: 1;
}

.series-panel-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; scroll-margin-top: 0.4rem; }
.series-panel-list li {
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--row-color, var(--border));
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  transition: box-shadow 0.15s ease, transform 0.15s ease;
  scroll-margin-top: 0.75rem;
}
.series-panel-list li:hover { box-shadow: var(--shadow-sm); transform: translateX(2px); }
.series-panel-list li.panel-past { opacity: 0.45; }
/* Rows rise/fade in with a per-row stagger (--i set in renderPanelList) instead of
   the whole list appearing at once - same animationend->animationName:none
   clearing pattern as .ev's entrance above, for the same reason: a finished
   `animation` on `transform` otherwise permanently blocks the :hover translateX
   `transition` on that same property. */
@keyframes panel-row-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.series-panel-list li:not(.skeleton-row) { animation: panel-row-rise 0.32s ease both; animation-delay: calc(var(--i, 0) * 30ms); }
.series-panel-list li.skeleton-row {
  height: 3.6rem;
  border-color: transparent;
  background: linear-gradient(90deg, var(--card-bg-soft) 25%, var(--border) 50%, var(--card-bg-soft) 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.3s ease infinite;
}
@keyframes skeleton-pulse { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
  .series-panel-list li:not(.skeleton-row) { animation: none; }
  .series-panel-list li.skeleton-row { animation: none; opacity: 0.6; }
}
.panel-row { display: flex; align-items: center; gap: 0.75rem; }
.panel-time-col {
  flex-shrink: 0;
  width: 3.6rem;
  text-align: center;
  padding-right: 0.65rem;
  border-right: 1px solid var(--border);
}
.panel-time-weekday { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
.panel-time-main { font-size: 0.95rem; font-weight: 800; color: var(--text); letter-spacing: -0.01em; }
.panel-row-main { min-width: 0; flex: 1; }
.panel-series { display: flex; align-items: center; gap: 0.4rem; font-size: 0.72rem; color: var(--muted); margin-bottom: 0.25rem; font-weight: 600; }
.panel-logo { width: 15px; height: 15px; object-fit: contain; border-radius: 3px; background: #fff; padding: 1px; box-sizing: border-box; }
.panel-row-icon { font-size: 0.8rem; }
.panel-match { font-size: 0.92rem; font-weight: 700; margin: 0 0 0.3rem; letter-spacing: -0.005em; }
.panel-meta { display: flex; align-items: center; gap: 0.6rem; font-size: 0.78rem; color: var(--muted); }
.panel-channel { display: flex; align-items: center; gap: 0.3rem; }
.panel-channel-logo { width: 14px; height: 14px; object-fit: contain; border-radius: 3px; }
.panel-meta a { color: var(--accent); text-decoration: none; }
.panel-action-btn {
  flex-shrink: 0;
  width: 26px; height: 26px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--card-bg);
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.panel-action-dismiss:hover { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); border-color: var(--danger); }
.panel-action-restore { color: var(--success); border-color: var(--success); }
.panel-action-restore:hover { background: color-mix(in srgb, var(--success) 15%, transparent); }

@media (max-width: 480px) {
  .series-panel { width: 100vw; }
}

/* ---------- cards / forms ---------- */

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem 1.75rem;
  margin-bottom: 1.5rem;
  box-shadow: var(--shadow-sm);
}
.card h2 { margin-top: 0; font-size: 1.05rem; letter-spacing: -0.01em; }

/* ---------- legal pages (privacy / terms) ---------- */

.legal { max-width: 46rem; }
.legal h2 { margin-top: 2rem; }
.legal h2:first-child { margin-top: 0; }
.legal p, .legal ul { color: var(--muted); font-size: 0.92rem; line-height: 1.65; }
.legal ul { padding-left: 1.2rem; margin: 0; display: flex; flex-direction: column; gap: 0.7rem; }
.legal li strong { color: var(--text); }
.legal code { background: var(--card-bg-soft); border: 1px solid var(--border); border-radius: 4px; padding: 0.1rem 0.35rem; font-size: 0.85em; }

/* ---------- quick-start guide ---------- */

/* .page is wide (max-width: 1520px) so admin/settings-style pages have room for
   tables and forms - the guide's own content is a much narrower single column
   (like the landing page's hero), so left alone it just sits pinned to the left
   edge of all that extra width instead of sitting in the middle of the screen.
   Centers the column itself (mx: auto below) and, for the header, the text within
   it too - same treatment as .hero's eyebrow/h1/sub. The individual step rows stay
   left-aligned internally (icon beside text reads badly centered), only the
   column as a whole moves to the middle. */
.guide-page .page-header { display: flex; flex-direction: column; align-items: center; text-align: center; }
.guide-page .guide-steps,
.guide-page .guide-tip {
  margin-left: auto;
  margin-right: auto;
}

.guide-steps { display: flex; flex-direction: column; gap: 1rem; max-width: 46rem; margin-bottom: 1.5rem; }
.guide-step {
  display: flex;
  align-items: flex-start;
  gap: 1.1rem;
  padding: 1.25rem 1.5rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
/* Several of these reuse the exact SVGs already wired into the real nav
   (Calendar/Add/Feedback) - the guide should look like it's pointing at the
   actual app, not a separately-illustrated stand-in for it. */
.guide-step-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  background: var(--card-bg-soft);
  color: var(--text);
}
.guide-step-icon svg { width: 22px; height: 22px; }
.guide-step-body { min-width: 0; }
.guide-step-num { display: block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 0.3rem; }
.guide-step-body h2 { margin: 0 0 0.4rem; font-size: 1.02rem; letter-spacing: -0.01em; }
.guide-step-body p { margin: 0; color: var(--muted); font-size: 0.9rem; line-height: 1.6; }

.guide-tip { display: flex; align-items: flex-start; gap: 0.85rem; max-width: 46rem; color: var(--text); }
.guide-tip svg { flex-shrink: 0; margin-top: 0.1rem; color: var(--text); }
.guide-tip p { margin: 0; color: var(--muted); font-size: 0.9rem; line-height: 1.6; }
.guide-tip strong { color: var(--text); }
/* The landing hero's oversized, tight-tracking type, scaled down to fit an inline
   utility-page header rather than a one-time full-bleed pitch. */
h1 { font-size: clamp(1.7rem, 3.4vw, 2.35rem); font-weight: 700; letter-spacing: -0.03em; line-height: 1.08; margin: 0; }

.card form label { display: block; margin-bottom: 0.9rem; font-size: 0.85rem; font-weight: 600; color: var(--muted); }
.card input[type="text"], .card select {
  width: 100%;
  padding: 0.55rem 0.7rem;
  margin-top: 0.3rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.card input[type="text"]:focus, .card select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.card button {
  margin-top: 0.4rem;
  padding: 0.55rem 1.1rem;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
}
.card button:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); filter: brightness(1.05); }

.mapping-table { width: 100%; border-collapse: collapse; font-size: 0.83rem; }
.mapping-table th {
  text-align: left;
  padding: 0.5rem 0.5rem;
  color: var(--muted);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--border);
}
.mapping-table td { border-bottom: 1px solid var(--border); padding: 0.5rem; vertical-align: middle; }
.mapping-table tr:hover td { background: var(--accent-soft); }
.mapping-table input[type="text"] { width: 100%; padding: 0.35rem 0.5rem; border: 1px solid var(--border); border-radius: 6px; background: var(--card-bg-soft); color: var(--text); text-overflow: ellipsis; }
.mapping-table button { padding: 0.35rem 0.8rem; font-size: 0.8rem; }

.calendar-feed-row { display: flex; gap: 0.6rem; margin: 0.9rem 0; }
.calendar-feed-row input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  color: var(--muted);
  font-family: inherit;
  font-size: 0.85rem;
}
#calendar-token-regenerate-form { display: inline-block; }

/* ---------- admin ---------- */

.admin-subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1.75rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--border);
}
.admin-subnav a {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.88rem;
  padding: 0.45rem 0.9rem;
  border-radius: 999px;
  transition: background 0.15s ease, color 0.15s ease;
}
.admin-subnav a:hover { color: var(--text); background: var(--accent-soft); }
.admin-subnav a.active { color: var(--bg); background: var(--accent); }
.admin-subnav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--card-bg-soft);
  color: var(--text);
  font-size: 0.7rem;
  font-weight: 700;
}
.admin-subnav a.active .admin-subnav-badge { background: color-mix(in srgb, var(--bg) 15%, transparent); color: var(--bg); }
.admin-subnav-badge-error { background: color-mix(in srgb, var(--danger) 20%, transparent); color: var(--danger); }
.admin-subnav a.active .admin-subnav-badge-error { background: color-mix(in srgb, var(--danger) 25%, var(--bg)); color: var(--danger); }

.admin-log-row { display: block; }
.admin-log-logger { font-family: ui-monospace, monospace; font-size: 0.72rem; margin-left: 0.5rem; }
.admin-log-time { float: right; font-size: 0.72rem; }
.admin-log-message { margin-top: 0.4rem; font-size: 0.85rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; clear: both; }
.admin-badge-warn { background: color-mix(in srgb, #e6b422 18%, transparent); color: #e6b422; }

.feedback-row { display: block; }
.feedback-text { margin: 0.5rem 0; font-size: 0.92rem; line-height: 1.55; white-space: pre-wrap; }
.feedback-reply-form { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }
.feedback-reply-form textarea {
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  color: var(--text);
  font: inherit;
  font-size: 0.86rem;
  line-height: 1.5;
}
.feedback-reply-form textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.feedback-reply-actions { display: flex; gap: 0.5rem; }
.feedback-reply-sent { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--border); }
.feedback-reply-sent p { margin: 0.4rem 0 0; font-size: 0.88rem; line-height: 1.5; color: var(--muted); white-space: pre-wrap; }

.icon-picker-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; }
.icon-option {
  display: flex;
  gap: 1rem;
  padding: 1.1rem;
  background: var(--card-bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.15s ease;
}
.icon-option.is-active { border-color: var(--accent); }
.icon-option-preview { flex-shrink: 0; width: 56px; height: 56px; }
.icon-option-preview svg { width: 56px; height: 56px; display: block; }
.icon-option-body { min-width: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.icon-option-name { margin: 0; font-weight: 700; font-size: 0.95rem; letter-spacing: -0.01em; display: flex; align-items: center; gap: 0.5rem; }
.icon-option-desc { margin: 0; font-size: 0.82rem; line-height: 1.55; color: var(--muted); }
.icon-option form { margin-top: 0.15rem; }

.admin-stats { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.admin-stat { display: flex; flex-direction: column; gap: 0.15rem; }
.admin-stat strong { font-size: 1.6rem; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.admin-stat span { font-size: 0.78rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
.admin-following { max-width: 32rem; font-size: 0.82rem; color: var(--muted); }
.admin-badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
}
.admin-badge-ok { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.admin-badge-error { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); cursor: help; }

.requests-bulk-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0.9rem 0;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.requests-bulk-bar label { display: flex; align-items: center; gap: 0.4rem; color: var(--muted); cursor: pointer; }
.requests-bulk-bar select { width: auto; margin-top: 0; }
#requests-list .req-check { width: 16px; height: 16px; margin-right: 0.7rem; flex-shrink: 0; accent-color: var(--text); cursor: pointer; }
.request-row-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.request-row-actions select { width: auto; margin-top: 0; }
.request-detail-text { max-width: 40rem; }
button.danger { background: transparent; border: 1px solid var(--border); color: var(--danger); }
button.danger:hover:not(:disabled) { background: color-mix(in srgb, var(--danger) 12%, transparent); border-color: var(--danger); }
button:disabled { opacity: 0.45; cursor: not-allowed; }
.admin-sort-header { cursor: pointer; user-select: none; }
.admin-sort-header:hover { color: var(--text); }
.admin-sort-header[data-sort-dir="asc"]::after { content: " \25B2"; font-size: 0.65em; }
.admin-sort-header[data-sort-dir="desc"]::after { content: " \25BC"; font-size: 0.65em; }
.admin-error-text { color: var(--danger); font-size: 0.8rem; margin-top: 0.3rem; max-width: 40rem; word-break: break-word; }
.candidate-list .rescan-btn { background: var(--card-bg-soft) !important; color: var(--text) !important; }

/* An 8-column table has no honest way to fit a phone screen - rather than force
   horizontal scrolling on the whole settings page, turn each row into a stacked
   card below this breakpoint, with each cell labelled by its column (data-label,
   set in settings.html) instead of relying on a header row. */
@media (max-width: 640px) {
  .mapping-table thead { display: none; }
  .mapping-table, .mapping-table tbody, .mapping-table tr, .mapping-table td { display: block; width: 100%; }
  .mapping-table tr {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.75rem;
    margin-bottom: 0.75rem;
    background: var(--card-bg-soft);
  }
  .mapping-table td {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    border-bottom: none;
    padding: 0.35rem 0;
  }
  .mapping-table td[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    flex: 0 0 5.5rem;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
  }
  .mapping-table td:first-child { font-weight: 700; padding-bottom: 0.5rem; margin-bottom: 0.25rem; border-bottom: 1px solid var(--border); }
  .mapping-table td:first-child::before { content: none; }
  .mapping-table td:last-child { padding-top: 0.5rem; }
  .mapping-table button { width: 100%; }
}

.muted { color: var(--muted); font-size: 0.88rem; }

.autocomplete-wrap { position: relative; }
.autocomplete-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 20;
  list-style: none;
  margin: 0;
  padding: 0.4rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  max-height: 280px;
  overflow-y: auto;
}
.autocomplete-list li {
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  font-size: 0.88rem;
  cursor: pointer;
  transition: background 0.12s ease;
}
.autocomplete-list li:hover { background: var(--accent-soft); }

.chip-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
.chip {
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card-bg-soft);
  color: var(--text);
  font-size: 0.82rem;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.12s ease, border-color 0.15s ease;
}
.chip:hover { background: var(--accent-soft); border-color: var(--accent); transform: translateY(-1px); }

.ai-add { display: flex; gap: 0.6rem; }
.ai-input-clear-wrap { position: relative; flex: 1; display: flex; }
.ai-add input {
  width: 100%;
  padding: 0.65rem 2.2rem 0.65rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card-bg-soft);
  color: var(--text);
  font-size: 0.92rem;
}
.ai-add input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* #ai-clear-btn (id, not just .ai-clear-btn): ".ai-add button" below is an
   element+class selector, which outranks a single class regardless of source
   order - without the id bump, that generic rule silently won and rendered this
   as an oversized solid pill (Search-button styling) instead of a small
   transparent x, badly misaligned inside the input. */
#ai-clear-btn.ai-clear-btn {
  position: absolute;
  right: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  width: 22px; height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 1.1rem;
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
#ai-clear-btn.ai-clear-btn:hover { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); transform: translateY(-50%); }
.ai-add button {
  padding: 0.65rem 1.3rem;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.ai-add button:hover { transform: translateY(-1px); filter: brightness(1.05); }

.candidate-list { list-style: none; padding: 0; margin: 1rem 0 0; display: flex; flex-direction: column; gap: 0.5rem; }
.candidate-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-soft);
  transition: box-shadow 0.15s ease, transform 0.12s ease;
}
.candidate-list li:hover { box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.candidate-list button {
  padding: 0.45rem 0.9rem;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.candidate-list button:hover { transform: translateY(-1px); filter: brightness(1.05); }
.candidate-list .actions { display: flex; gap: 0.5rem; }
@media (max-width: 480px) {
  /* Long request text/status controls (e.g. admin's per-row status dropdown +
     Delete button) don't fit next to unwrapped content on a phone - the row has no
     scroll container of its own, so the overflow was reachable only by scrolling
     the whole page sideways, with no hint that was possible. Let the row wrap. */
  .candidate-list li { flex-wrap: wrap; row-gap: 0.5rem; }
  .candidate-list li > div:first-of-type { min-width: 0; flex: 1 1 auto; }
}

.status-badge {
  display: inline-block;
  flex-shrink: 0;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
}
.status-badge.status-sent { background: var(--card-bg); color: var(--muted); border: 1px solid var(--border); }
.status-badge.status-in_progress { background: var(--accent-soft); color: var(--text); }
.status-badge.status-done { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.status-badge.status-denied { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }

.candidate-list li.request-form-row { display: block; }
.request-form p { margin: 0 0 0.6rem; font-size: 0.82rem; line-height: 1.5; }
.request-form textarea.request-details {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 0.6rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  color: var(--text);
  font: inherit;
  resize: vertical;
}
.dismiss-suggestion { background: var(--muted) !important; }
