:root {
  /* Navy-blue theme: every neutral below is a tint of the same deep blue
     rather than a flat grey, so panels, borders and dimmed text all read
     as part of one cohesive palette instead of a grey UI dropped onto a
     blue background. Panels are translucent (paired with backdrop-filter
     on .card/.topbar/.modal-panel/etc.) so the glow blobs in body::before/
     ::after read through them — that's the frosted-glass "premium" layer. */
  /* Sampled directly from the mockup screenshot's actual pixels (page
     corner, card fill, card edge) rather than eyeballed. */
  --bg: #0f0c1d;
  --bg-2: #0f2245;
  --panel: #191531;
  --panel-2: #191531;
  --border: #322a54;
  --border-strong: rgba(130, 170, 255, 0.4);
  --text: #edf1ff;
  --text-dim: #a79fcf;
  --accent: #3b82f6;
  --accent-2: #22d3ee;
  --good: #2dd4bf;
  --bad: #fb7185;
  --warn: #fbbf24;
  --brand-gradient: linear-gradient(90deg, #2563eb 0%, #22d3ee 100%);

  --c-1: #35c9e1;
  --c-2: #f0c93d;
  --c-5: #ff8a5c;
  --c-10: #b06bff;
  --c-coin_flip: #4f8cff;
  --c-pachinko: #ff5cd6;
  --c-cash_hunt: #35d07f;
  --c-crazy_time: #ff4d5e;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

body {
  min-height: 100vh;
  overflow-x: hidden;
  position: relative;
}

/* Big, softly blurred circles of blue light anchored to opposite corners
   of the page — this is the "premium" ambient glow. Fixed + a negative
   z-index puts them above the solid --bg color but behind every normal
   in-flow element (topbar, cards, modals), and the heavy blur keeps them
   reading as light rather than as visible shapes. */
body::before,
body::after {
  content: '';
  position: fixed;
  border-radius: 50%;
  filter: blur(120px);
  z-index: -1;
  pointer-events: none;
}
/* Primary glow, top-left — this is the one that reads as the page's
   signature "hero" light, sitting fixed behind the topbar and stat tiles
   so it stays put while the page scrolls past it (the parallax feel). */
body::before {
  width: 780px;
  height: 780px;
  top: -320px;
  left: -260px;
  background: radial-gradient(circle at 40% 40%, rgba(56, 189, 248, 0.4), rgba(37, 99, 235, 0.16) 55%, transparent 72%);
}
/* Secondary, subtler glow in the opposite corner for balance. */
body::after {
  width: 680px;
  height: 680px;
  bottom: -320px;
  right: -220px;
  background: radial-gradient(circle at 60% 60%, rgba(59, 130, 246, 0.24), rgba(14, 116, 144, 0.1) 55%, transparent 72%);
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  /* Without an explicit position+z-index, the topbar only gets a stacking
     context implicitly (via backdrop-filter), and the native <select> in
     the .tabs bar right below it can end up compositing above the user
     menu's "Log Out" popup that hangs down out of the topbar — the popup
     shows through the select's own translucent/blurred pill instead of
     sitting cleanly on top of it. Giving the topbar itself an explicit,
     fairly high z-index makes its whole subtree (including that popup)
     unambiguously outrank the tabs bar and everything in the page below it. */
  position: relative;
  z-index: 30;
}

.topbar-title { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.topbar-title h1 { font-size: 1.15rem; margin: 0; }

/* Without this, topbar-user's own `display: flex` makes it a block-level
   box with no sibling row to share, so it drops to its own line below
   Alerts instead of sitting inline with it. */
.topbar-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.subtitle { color: var(--text-dim); font-size: 0.8rem; }

/* Gradient pill CTA — the site's one button style, used for every clickable
   button (Alerts, Save Alert Rule, alert-rule Remove, and the active tab).
   Horizontal gradient + a soft colored shadow underneath, plus a slight
   lift on hover — a punchier, more "premium landing page" CTA feel. */
.btn-primary {
  background: var(--brand-gradient);
  border: none;
  color: #fff;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 11px 22px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 10px 26px -8px rgba(37, 99, 235, 0.7);
  transition: filter 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.btn-primary:hover {
  filter: brightness(1.12);
  transform: translateY(-1px);
  box-shadow: 0 14px 30px -8px rgba(34, 211, 238, 0.6);
}
.btn-primary:active { transform: scale(0.98); }
.btn-primary.btn-full { display: block; width: 100%; padding: 12px 20px; font-size: 0.9rem; text-align: center; }

.btn-primary.btn-sm { padding: 6px 14px; font-size: 0.75rem; }

/* The table switcher now lives inline in the topbar (between the page
   title and the Alerts/user actions) rather than as its own bar above the
   video — see index.html's <header class="topbar">. Pill-shaped to match
   the timeframe filter's own pills instead of the old "attached to the
   panel below" tab look. */
.tabs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
.tab {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 9px 18px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  position: relative;
}
.tab:hover { border-color: var(--accent-2); }
/* Active tab reuses the same gradient as every other button on the site;
   inactive tabs stay outlined so the active/inactive distinction is still
   readable at a glance. */
.tab.active {
  color: #fff;
  background: var(--brand-gradient);
  border-color: transparent;
  box-shadow: 0 6px 18px -8px rgba(37, 99, 235, 0.7);
  z-index: 1;
}
/* Joined "segmented control" look — the buttons sit flush against each
   other rather than as separate pills, with only the group's outer corners
   rounded; the shared edge between them collapses to one 1px line instead
   of a doubled border. */
.tab:not(:first-child) { margin-left: -1px; }
.tab:first-child:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.tab:last-child:not(:first-child) {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.tabs[hidden] { display: none !important; }

main { padding: 0 20px 20px; }

.panel { display: none; }
.panel.active { display: block; }

/* Live Game hero: a user-configured third-party video embed.
   Background treatment only (per reference mockup — a modern purple-
   gradient SaaS panel): a violet radial+linear gradient mix plus a faint
   grid, scoped entirely to this one card via isolation/z-index so the
   rest of the page keeps its navy theme untouched. */
.hero-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    radial-gradient(circle at 18% -10%, rgba(216, 180, 254, 0.4), transparent 55%),
    radial-gradient(circle at 100% 110%, rgba(147, 51, 234, 0.35), transparent 60%),
    linear-gradient(155deg, #1b0e35 0%, #2c1257 45%, #6b5a8500 75%, #6d28d900 100%);
  border: 1px solid rgba(196, 152, 255, 0.28);
  border-radius: 16px;
  padding: 16px 18px;
  margin-top: 0;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 20px 48px -28px rgba(2, 6, 23, 0.75), 0 0 70px -30px rgba(168, 85, 247, 0.55);
}
/* Faint grid echoing the mockup's dashboard backdrop, faded out toward
   the card's edges so it reads as texture rather than a hard pattern. */
.hero-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
  background-size: 32px 32px;
  -webkit-mask-image: radial-gradient(circle at 50% 0%, black, transparent 75%);
  mask-image: radial-gradient(circle at 50% 0%, black, transparent 75%);
  pointer-events: none;
  z-index: 0;
}
.hero-card > * {
  position: relative;
  z-index: 1;
}
/* Live Game status bar — sits above the video frame (see #panel-template)
   with the table title/LIVE badge, a few real-time stats derived from data
   the app already fetches (current spin count, last result, an estimated
   next-spin countdown, and "updated Ns ago"), and a fullscreen toggle for
   the video. See renderHeroStatus()/tickHeroStatus() in app.js. */
.hero-status-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  background: rgba(10, 6, 24, 0.5);
  border: 1px solid rgba(196, 152, 255, 0.22);
  border-radius: 12px;
  padding: 14px 18px;
  margin-bottom: 16px;
}
.hero-status-seg {
  padding: 0 18px;
  border-left: 1px solid rgba(255, 255, 255, 0.12);
}
.hero-status-seg:first-child { border-left: none; padding-left: 0; }
.hero-status-title { flex: 1 1 180px; min-width: 160px; }
.hero-status-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.hero-status-title-row h2 { margin: 0; font-size: 1.05rem; }
.hero-status-sub { color: var(--text-dim); font-size: 0.8rem; margin-top: 3px; }
.hero-status-label {
  color: var(--text-dim);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 3px;
  white-space: nowrap;
}
.hero-status-value { font-weight: 700; font-size: 1rem; white-space: nowrap; }
.hero-status-value--sm { font-size: 0.8rem; font-weight: 600; color: var(--text-dim); }
.hero-status-timer { font-variant-numeric: tabular-nums; }
.hero-status-live { display: flex; align-items: center; gap: 8px; }
.hero-result-mult { color: var(--warn); font-weight: 800; margin-left: 4px; }
.hero-fullscreen-btn {
  margin-left: auto;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  color: var(--text);
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
  transition: border-color 0.15s ease;
}
.hero-fullscreen-btn:hover { border-color: var(--accent-2); }
.hero-fullscreen-btn svg { width: 16px; height: 16px; }

/* Small pulsing "on air" dot — used both inside the LIVE title badge and,
   at a different size/color via .hero-live-data-dot below, next to "Live
   Data". Purely decorative; not a real connection-health signal. */
.hero-live-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--bad);
  box-shadow: 0 0 6px var(--bad), 0 0 14px rgba(251, 113, 133, 0.7);
  flex: none;
  animation: hero-live-pulse 1.6s ease-in-out infinite;
}
.hero-live-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: linear-gradient(90deg, #f43f5e, #fb7185);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
}
.hero-live-badge .hero-live-dot {
  width: 6px;
  height: 6px;
  background: #fff;
  box-shadow: none;
}
.hero-live-data-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 6px var(--good);
  flex: none;
  animation: hero-live-pulse 1.6s ease-in-out infinite;
}
@keyframes hero-live-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.5; transform: scale(0.8); }
}
@media (max-width: 720px) {
  .hero-status-bar { flex-direction: column; align-items: stretch; gap: 10px; }
  /* .hero-status-title's desktop flex-basis (180px, meant as a target WIDTH
     for the row layout above) gets reinterpreted as a target HEIGHT once
     the bar switches to a column here, leaving a tall empty gap above the
     stats. Reset it so the title block just hugs its content on mobile. */
  .hero-status-title { flex: 1 1 auto; min-width: 0; }
  .hero-status-seg { border-left: none; padding: 0; }
}
/* Video + "Spins Since Last Bonus" panel side by side, 60/40 split. A grid
   (rather than flex) makes that ratio exact regardless of the gap between
   them. Stacks vertically on narrow viewports (see the max-width: 720px
   block below). */
.hero-main-row {
  display: grid;
  grid-template-columns: 3fr 2fr;
  align-items: stretch;
  gap: 16px;
  width: 100%;
}
.hero-video-frame {
  position: relative;
  min-width: 0;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 10px;
  overflow: hidden;
}
/* On the two-column desktop layout, aspect-ratio fights the grid's row
   stretch: with .hero-video-frame's height stretched to match the taller
   .bonus-panel next to it, the browser computed WIDTH from that stretched
   height via the 16:9 ratio (height * 16/9), which is wider than the
   video's own grid column — so the frame overflowed and covered part of
   the bonus panel. Dropping aspect-ratio here lets width and height both
   just fill the grid cell as normal; the video element inside already
   uses object-fit: cover, so it still fills the frame with no distortion.
   Below 720px .hero-main-row collapses to a single column with no sibling
   to stretch against, so aspect-ratio is left in place there to give the
   frame a sensible height from its own width. */
@media (min-width: 721px) {
  .hero-video-frame { aspect-ratio: unset; height: 100%; }
}

/* ---- Spins Since Last Bonus panel ---- */
.bonus-panel {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: rgba(10, 6, 24, 0.5);
  border: 1px solid rgba(196, 152, 255, 0.22);
  border-radius: 12px;
  padding: 16px;
}
.bonus-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.bonus-panel-icon { width: 18px; height: 18px; flex: none; }
.bonus-panel-head h3 {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
}
.bonus-panel-main {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.bonus-panel-value {
  font-size: 2.4rem;
  font-weight: 800;
  line-height: 1;
  color: #fb7185;
}
.bonus-panel-avg-box {
  text-align: right;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px 10px;
}
.bonus-panel-avg-label {
  color: var(--text-dim);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.bonus-panel-avg-value { font-size: 0.95rem; font-weight: 700; margin-top: 2px; }
.bonus-panel-last {
  color: var(--text-dim);
  font-size: 0.78rem;
}
.bonus-slider { display: flex; flex-direction: column; gap: 6px; }
.bonus-slider-track {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: linear-gradient(90deg, #fb7185, #a78bfa, #38bdf8);
}
.bonus-slider-pin {
  position: absolute;
  top: 50%;
  left: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid #1b0e35;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6), 0 2px 6px rgba(0, 0, 0, 0.5);
  transform: translate(-50%, -50%);
  transition: left 0.3s ease;
}
.bonus-slider-ticks {
  display: flex;
  justify-content: space-between;
  color: var(--text-dim);
  font-size: 0.62rem;
}
/* Stacked list rather than a 3-up grid of boxed tiles: each stat is its own
   full-width row (icon + label, value below), separated by a hairline —
   reads cleanly at the panel's narrower (40%-column) width. */
.bonus-panel-tiles {
  display: flex;
  flex-direction: column;
}
.bonus-panel-tiles .stat-tile {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding: 10px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.bonus-panel-tiles .stat-tile:first-child {
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.bonus-panel-tiles .stat-tile:last-child { padding-bottom: 2px; border-bottom: none; }
.bonus-panel-tiles .tile-label {
  display: flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bonus-panel-tiles .tile-value { font-size: 1.35rem; }
.tile-icon { width: 13px; height: 13px; flex: none; }
@media (max-width: 720px) {
  .hero-main-row { grid-template-columns: 1fr; }
}
.hero-video-iframe,
.hero-video-el {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: #000;
  object-fit: cover;
}
.hero-video-iframe[hidden],
.hero-video-el[hidden] { display: none; }
/* Cover placeholder shown when no video is configured/reachable: an
   abstract wheel motif (built from the same --c-* segment colors used
   everywhere else in the app, not a reproduction of any real game
   artwork) sits dimmed behind the message so the frame reads as a themed
   cover rather than a flat black box. */
.hero-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  color: var(--text-dim);
  font-size: 0.85rem;
  text-align: center;
  padding: 20px;
  background: radial-gradient(circle at 50% 45%, rgba(43, 22, 78, 0.55), rgba(0, 0, 0, 0.94) 72%);
  overflow: hidden;
}
.hero-placeholder[hidden] { display: none; }
.hero-placeholder::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 240px;
  height: 240px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: conic-gradient(
    var(--c-1) 0deg 45deg,
    var(--c-2) 45deg 90deg,
    var(--c-5) 90deg 135deg,
    var(--c-10) 135deg 180deg,
    var(--c-coin_flip) 180deg 225deg,
    var(--c-pachinko) 225deg 270deg,
    var(--c-cash_hunt) 270deg 315deg,
    var(--c-crazy_time) 315deg 360deg
  );
  box-shadow: 0 0 60px 10px rgba(0, 0, 0, 0.6);
  opacity: 0.32;
}
.hero-placeholder p {
  position: relative;
  z-index: 1;
  margin: 0;
  background: rgba(15, 12, 29, 0.6);
  padding: 8px 16px;
  border-radius: 8px;
}
.hero-disclaimer {
  color: var(--text-dim);
  font-size: 0.72rem;
  margin: 10px 0 0;
  text-align: center;
}

/* Time-frame filter: a segmented row of pill buttons (radio inputs, visually
   hidden) below the video instead of the old native <select> in the tabs
   bar — same brand-gradient "selected" treatment as .tab.active/.btn-primary
   elsewhere, so it reads as one design language. */
/* Sits directly under .hero-video-frame, matching its 960px max-width so
   the pill row reads as an attached "shelf" hanging off the video's own
   edges rather than a separate block floating in the card. The negative
   bottom margin tucks it up under the stat tiles that follow, closing the
   gap the way .hero-card's top margin does above the tabs bar. */
.timeframe-group {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  width: 100%;
  max-width: 960px;
  margin: 16px auto -16px;
  padding: 14px 56px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  border-left: 1px solid #604d7c;
  border-right: 1px solid #604d7c;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
  backdrop-filter: blur(10px);
}
/* Sits centered on the group's own top border, like a fieldset legend,
   labeling what the pill row is for without competing with the pills
   themselves for attention. */
.timeframe-group-label {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 2px 12px;
  border-radius: 999px;
  border: 1px solid rgba(196, 152, 255, 0.28);
  background: #1b0e35;
  color: var(--text-dim);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.timeframe-option { cursor: pointer; }
.timeframe-option input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.timeframe-option span {
  display: inline-block;
  padding: 4px 16px;
  border-radius: 999px;
  border: 1px solid rgba(196, 152, 255, 0.28);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
  font-size: 0.8rem;
  font-weight: 600;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.timeframe-option:hover span { border-color: var(--accent-2); }
.timeframe-option input:checked + span {
  background: var(--brand-gradient);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 6px 18px -8px rgba(37, 99, 235, 0.7);
}
.timeframe-option input:focus-visible + span {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

/* Live monitor: stat tiles + alerts feed + recent spins */
.stat-tile {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 16px 36px -26px rgba(2, 6, 23, 0.8);
}
.tile-label {
  color: var(--text-dim);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
}
.tile-value { font-size: 1.6rem; font-weight: 700; }

.live-monitor-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 14px;
  padding-top: 14px;
}
/* Spin Timeline + Recent Spins + Live Alerts as one row of three equal
   columns, all stretched (grid's default align-items) to the height of the
   tallest card so the row reads as one even band. */
.live-monitor-grid--triple {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.live-monitor-grid--triple > .card {
  display: flex;
  flex-direction: column;
}
.card-tall .alerts-list,
.card-tall .recent-spins-list { max-height: 420px; overflow-y: auto; }
/* Horizontal scroll instead of squeezing columns — the Segment Frequency
   table used to just let its columns shrink below their content's width on
   narrow phones, which made unbreakable tokens like "(-7.12pp)" spill
   visibly into the next column. A table that can scroll sideways instead
   never has that failure mode. */
.card-tall .recent-spins-list { overflow-x: auto; }

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.card-head h2 { margin: 0; }
.card-head-hint { color: var(--text-dim); font-size: 0.72rem; }

/* Live Alerts filter tabs (All / Bonus / Big Hit / Milestone) — a segmented
   pill row above the list, each with a live count badge; clicking one
   filters alerts-rows via renderAlerts()'s state.alertFilter. */
.alert-filter-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}
.alert-filter-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.alert-filter-tab:hover { border-color: var(--accent-2); }
.alert-filter-tab.active {
  background: var(--brand-gradient);
  border-color: transparent;
  color: #fff;
}
.alert-filter-tab-count {
  background: rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  padding: 1px 7px;
  font-size: 0.68rem;
  font-weight: 700;
}
.alert-filter-tab[data-filter="bonus"] .alert-filter-tab-count { background: rgba(52, 211, 153, 0.22); color: #34d399; }
.alert-filter-tab[data-filter="bighit"] .alert-filter-tab-count { background: rgba(248, 113, 113, 0.22); color: #f87171; }
.alert-filter-tab[data-filter="milestone"] .alert-filter-tab-count { background: rgba(96, 165, 250, 0.22); color: #60a5fa; }
.alert-filter-tab.active .alert-filter-tab-count { background: rgba(255, 255, 255, 0.25); color: #fff; }

/* Alert rows: a colored icon badge, a colored category pill next to the
   timestamp, then title/description — tinted per category to match the
   filter tabs above (bonus=green, bighit/massive=red, milestone=blue). */
.alert-card {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  border-radius: 12px;
  border-left: 4px solid var(--accent);
  padding: 12px 14px;
  margin-bottom: 8px;
  font-size: 0.85rem;
  background: rgba(255, 255, 255, 0.03);
}
.alert-card:last-child { margin-bottom: 0; }
.alert-card.bonus {
  border-left-color: #34d399;
  background: linear-gradient(90deg, rgba(52, 211, 153, 0.14), rgba(52, 211, 153, 0.02));
}
.alert-card.bighit,
.alert-card.massive {
  border-left-color: #f87171;
  background: linear-gradient(90deg, rgba(248, 113, 113, 0.16), rgba(248, 113, 113, 0.02));
}
.alert-card.milestone {
  border-left-color: #60a5fa;
  background: linear-gradient(90deg, rgba(96, 165, 250, 0.14), rgba(96, 165, 250, 0.02));
}
.alert-icon {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.alert-icon svg { width: 16px; height: 16px; }
.alert-card.bonus .alert-icon { background: #34d399; }
.alert-card.bighit .alert-icon,
.alert-card.massive .alert-icon { background: #f87171; }
.alert-card.milestone .alert-icon { background: #60a5fa; }
.alert-body { flex: 1; min-width: 0; }
.alert-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.alert-pill {
  display: inline-flex;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #08111f;
}
.alert-card.bonus .alert-pill { background: #34d399; }
.alert-card.bighit .alert-pill,
.alert-card.massive .alert-pill { background: #f87171; }
.alert-card.milestone .alert-pill { background: #60a5fa; }
.alert-time { color: var(--text-dim); font-size: 0.72rem; flex: none; }
.alert-title { font-weight: 700; margin-bottom: 2px; }
.alert-desc { color: var(--text-dim); font-size: 0.8rem; }

/* Recent Spins table: Finished / Slot Result / Spin Result / Multiplier —
   a flat table, no expandable "+" row. A real <table> rather than a CSS
   grid of divs, specifically so a narrow phone scrolls it sideways (via
   .card-tall .recent-spins-list's overflow-x above) instead of squeezing
   columns the way the old Segment Frequency grid did. */
.spins-table { width: 100%; min-width: 480px; border-collapse: collapse; font-size: 0.85rem; }
.spins-table thead th {
  text-align: left;
  color: var(--text-dim);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.spins-table tbody td {
  padding: 8px 10px;
  vertical-align: middle;
  white-space: nowrap;
}
/* Zebra striping instead of a border-bottom on every row: cleaner at a
   glance, and reads fine even when rows get dense (e.g. the 50-row Recent
   Spins list). .spins-row/.spins-row--alt are already applied per-row by
   renderRecentSpins' i % 2 check in app.js — this just gives each a real
   background instead of the near-invisible 3% tint it had before. */
.spins-row td { background: rgba(255, 255, 255, 0.02); }
.spins-row--alt td { background: rgba(255, 255, 255, 0.06); }
.spins-time { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.spins-mult { font-weight: 700; color: var(--warn); }
.spins-topslot-value { font-weight: 700; color: var(--accent-2); }
.spins-topslot-miss { color: var(--text-dim); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; }
.spins-empty-cell { color: var(--text-dim); }

.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  padding-top: 14px;
}
/* Statistics, RTP Analysis, and Sessions all open directly with a .grid as
   the page's first element — the hero banner above already supplies its
   own margin-bottom, so .grid's padding-top just doubled up into an
   oversized gap under the hero banner on those pages. Zero it out only
   when .grid is the very first thing in the page; a .grid further down
   the page (e.g. Sessions' later sections, or Alerts' card-then-grid
   layout) still gets its normal top spacing from a preceding element. */
.page > .grid:first-child {
  padding-top: 0;
}
/* .grid always sets display:grid itself, which as an author-origin rule
   beats the browser's built-in [hidden]{display:none} rule regardless of
   the hidden attribute being set — so a .grid element needs this explicit
   override to actually disappear when JS hides it (see #alerts-grid). */
.grid[hidden] { display: none; }
.card-wide { grid-column: 1 / -1; }
/* Segment Frequency / Bonus Rate / Fairness Grade as one row: 50/25/25. */
.grid--summary-3col { grid-template-columns: 2fr 1fr 1fr; }
/* Top Wins / Session Overview / Bonus Cycle History as one row: 50/25/25. */
.grid--overview-3col { grid-template-columns: 2fr 1fr 1fr; }

.card {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 16px 18px;
  min-width: 0;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 20px 48px -28px rgba(2, 6, 23, 0.75);
}
.card h2 { font-size: 0.95rem; margin: 0 0 6px; }
.card-hint { color: var(--text-dim); font-size: 0.78rem; margin: 0 0 12px; }

/* My Account content area (#account-content) renders its top-level sections
   (.card blocks, and occasionally a .grid of stat tiles like admin's All
   Users) as plain siblings, not inside a wrapper with its own gap — so
   without this they'd render flush against each other with no breathing
   room. `> * + *` (rather than `> .card + .card`) also covers a .grid
   followed by a .card, or vice versa, not just card-after-card. Scoped to
   #account-content specifically so it doesn't add unwanted spacing
   elsewhere (e.g. dashboard .grid layouts already handle their own gap). */
#account-content > * + * { margin-top: 16px; }

.stat-big { font-size: 2rem; font-weight: 700; }
.stat-sub { color: var(--text-dim); font-size: 0.8rem; margin-top: 2px; }
.stat-foot { color: var(--text-dim); font-size: 0.78rem; margin-top: 8px; }

.bonus-rate-big { color: var(--accent-2); }
.bonus-rate-bars { display: grid; gap: 10px; margin-top: 14px; }
.bonus-rate-bar-row {
  display: grid;
  grid-template-columns: 96px 1fr auto;
  align-items: center;
  gap: 10px;
}
.bonus-rate-bar-label { color: var(--text-dim); font-size: 0.78rem; }
.bonus-rate-bar-value { font-size: 0.82rem; font-weight: 700; text-align: right; }
.bonus-rate-bar-fill--recent { background: var(--good); }
.bonus-rate-bar-fill--alltime { background: linear-gradient(90deg, #7c3aed, #a78bfa); }

.stat-row { display: flex; gap: 24px; margin-bottom: 12px; flex-wrap: wrap; }
.stat-row .label { display: block; color: var(--text-dim); font-size: 0.75rem; }
.stat-row .value { display: block; font-size: 1.3rem; font-weight: 700; }

/* Segment frequency table */
.freq-row, .rtp-row, .mult-row {
  display: grid;
  grid-template-columns: 110px repeat(4, 1fr);
  gap: 10px;
  align-items: center;
  padding: 7px 0;
  font-size: 0.85rem;
}
/* Zebra striping instead of a border-bottom on every row (same treatment
   as .spins-table) — the header row shares this class too (see
   renderFrequency/renderRtp/renderMultipliers in app.js), so it's excluded
   here and styled separately by .freq-head below. */
.freq-row:not(.freq-head), .rtp-row:not(.freq-head), .mult-row:not(.freq-head) {
  background: rgba(255, 255, 255, 0.02);
}
.freq-row--alt, .rtp-row--alt, .mult-row--alt { background: rgba(255, 255, 255, 0.06); }
.freq-head { color: var(--text-dim); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; }

.seg-label { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.seg-chip { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.bar-track {
  height: 7px;
  background: #10142400;
  background: rgba(255,255,255,0.06);
  border-radius: 4px;
  position: relative;
  overflow: hidden;
}
.bar-fill { height: 100%; border-radius: 4px; }
.bar-expected-mark {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  background: var(--text);
  opacity: 0.6;
}

.delta-pos { color: var(--good); }
.delta-neg { color: var(--bad); }

.top-wins-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
  max-height: 340px;
  overflow-y: auto;
}
.top-wins-list li {
  display: grid;
  grid-template-columns: 28px 110px 1fr auto auto;
  gap: 10px;
  align-items: center;
  font-size: 0.85rem;
  padding: 6px 0;
  background: rgba(255, 255, 255, 0.02);
}
.top-wins-list li.win-row--alt { background: rgb(25 21 49); }
.rank { color: var(--text-dim); }
.mult-badge {
  font-weight: 700;
  color: var(--warn);
}

.history-time { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.synthetic-flag { color: var(--text-dim); font-size: 0.7rem; }

.grade-A { color: var(--good); }
.grade-B { color: #8fe2a8; }
.grade-C { color: var(--warn); }
.grade-D { color: #ff8a5c; }
.grade-F { color: var(--bad); }

/* Fairness Grade card: a colored ring around the letter grade, with a
   plain-language caption beside it (the actual n=/χ²/p-value figures stay
   available underneath the caption in .fairness-stats, just de-emphasized —
   see renderFairness() in app.js). */
.fairness-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding-top: 10px;
}
.fairness-ring {
  flex: none;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 4px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fairness-ring.grade-A { border-color: var(--good); }
.fairness-ring.grade-B { border-color: #8fe2a8; }
.fairness-ring.grade-C { border-color: var(--warn); }
.fairness-ring.grade-D { border-color: #ff8a5c; }
.fairness-ring.grade-F { border-color: var(--bad); }
.fairness-ring-letter { font-size: 1.4rem; font-weight: 800; }
.fairness-copy { min-width: 0; }
.fairness-desc { font-size: 0.85rem; margin: 0; }
.fairness-stats { color: var(--text-dim); font-size: 0.72rem; margin: 6px 0 0; }

/* Full-page loading screen: shown from first paint until app.js's boot()
   receives the first websocket snapshot, then faded out and removed (see
   hideLoadingScreen in app.js). The wheel spins on the same 1.1s linear
   keyframe animation the favicon spinner in app.js steps through by hand,
   so the tab icon and this overlay look like one continuous spin. */
.loading-screen {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: #191531;
  transition: opacity 0.25s ease-out;
}
.loading-screen.loading-screen--hidden {
  opacity: 0;
  pointer-events: none;
}
.loading-wheel {
  width: 64px;
  height: 64px;
  animation: wheel-spin 1.1s linear infinite;
}
.loading-text {
  margin: 0;
  color: var(--text-dim);
  font-size: 0.85rem;
  letter-spacing: 0.02em;
}
@keyframes wheel-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .loading-wheel { animation: none; }
}

.toast-stack {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: 10px;
  z-index: 1000;
  max-width: min(360px, calc(100vw - 32px));
}
.toast {
  position: relative;
  background: var(--panel-2);
  border: 1px solid var(--accent);
  border-left-width: 4px;
  border-radius: 12px;
  padding: 12px 30px 12px 14px;
  font-size: 0.85rem;
  box-shadow: 0 16px 40px -12px rgba(0, 0, 10, 0.55);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  animation: toast-in 0.2s ease-out;
}
.toast-close {
  position: absolute;
  top: 6px;
  right: 6px;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  padding: 2px 5px;
  border-radius: 6px;
}
.toast-close:hover { color: var(--text); background: rgba(255, 255, 255, 0.08); }
.toast.bonus { border-left-color: var(--warn); }
.toast.bigwin { border-left-color: var(--bad); }
.toast.milestone { border-left-color: var(--good); }
.toast.error { border-left-color: var(--bad); }
.toast small { display: block; color: var(--text-dim); margin-top: 3px; }

@keyframes toast-in {
  from { transform: translateY(-12px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

.footer {
  padding: 20px;
  color: var(--text-dim);
  font-size: 0.75rem;
  max-width: 900px;
}
.footer code { background: var(--panel-2); padding: 1px 5px; border-radius: 4px; }

/* Alerts modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: #311a55fa;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  z-index: 1200;
  overflow-y: auto;
}
.modal-overlay[hidden] { display: none; }

.modal-panel {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 22px 24px;
  width: 100%;
  max-width: 460px;
  box-shadow: 0 24px 70px rgba(201, 167, 255, 0.27);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
}

.modal-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
}
.modal-head h2 { margin: 0; font-size: 1.1rem; }
.modal-close {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.modal-close:hover { color: var(--text); }

.modal-hint { color: var(--text-dim); font-size: 0.78rem; line-height: 1.4; margin: 0 0 14px; }
.modal-hint code { background: var(--bg); padding: 1px 5px; border-radius: 4px; color: var(--text); }
.modal-table-hint { color: var(--text-dim); }
.modal-sms-hint { color: var(--warn); margin-top: -6px; }

.toggle-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; cursor: pointer; }
.toggle-switch { position: relative; display: inline-block; width: 40px; height: 22px; flex: none; }
.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.toggle-track {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 999px;
  transition: background 0.15s ease;
}
.toggle-track::before {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.15s ease;
}
.toggle-switch input:checked + .toggle-track { background: var(--brand-gradient); }
.toggle-switch input:checked + .toggle-track::before { transform: translateX(18px); }
.toggle-label { font-size: 0.85rem; font-weight: 600; }

.field { margin-bottom: 14px; }
.field label { display: block; color: var(--text-dim); font-size: 0.75rem; margin-bottom: 5px; }
.field-optional { color: var(--text-dim); font-weight: 400; }
.field input[type='text'],
.field input[type='url'],
.field input[type='tel'],
.field input[type='email'],
.field input[type='password'] {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
}
.field input::placeholder { color: #5f76a3; }
.field input:focus {
  outline: none;
  border-color: var(--accent-2);
  box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15);
}
/* Read-only fields (e.g. My Account > Profile's Email, which isn't
   editable here) — without this, a disabled input renders identically to
   an editable one and looks like a bug rather than an intentional lock. */
.field input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--panel);
}

.password-field { position: relative; }
.password-field input { padding-right: 38px; }
.password-toggle {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  background: none;
  border: none;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: 6px;
}
.password-toggle:hover { color: var(--text); }
.password-toggle svg { width: 18px; height: 18px; }
/* The two icons are switched by a class on the button rather than the
   `hidden` attribute/property: plain SVGElement doesn't implement the
   HTML `hidden` IDL accessor in every browser, so `icon.hidden = true`
   can silently set a inert JS property instead of the real attribute,
   leaving both icons visible (squeezed side by side) with nothing to
   toggle them apart. A CSS class toggle has no such gap. */
.password-toggle .icon-eye-off { display: none; }
.password-toggle.is-visible .icon-eye { display: none; }
.password-toggle.is-visible .icon-eye-off { display: block; }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.8rem;
  color: var(--text-dim);
  margin-bottom: 12px;
  cursor: pointer;
}

.table-select-field {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 0 0 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  align-items: center;
}
.table-select-field legend { color: var(--text-dim); font-size: 0.75rem; padding: 0 6px; }
.table-select-field[hidden] { display: none; }
.table-select-field.disabled { opacity: 0.55; }
.radio-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8rem;
  color: var(--text);
  cursor: pointer;
}
.radio-row input[type="radio"] { accent-color: var(--accent); cursor: pointer; }
.radio-row[hidden] { display: none; }
.table-select-field.disabled .radio-row,
.table-select-field.disabled .radio-row input { cursor: default; }

.thresholds-field {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin: 0 0 14px;
}
.thresholds-field legend { color: var(--text-dim); font-size: 0.75rem; padding: 0 6px; }
.threshold-row {
  display: grid;
  grid-template-columns: 90px 1fr;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.threshold-row:last-child { margin-bottom: 0; }
.threshold-row label { font-size: 0.8rem; color: var(--text); }
.threshold-row input {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 7px 10px;
  border-radius: 8px;
  font-size: 0.82rem;
  width: 100%;
}
.threshold-row input::placeholder { color: #5f76a3; }

.modal-error { color: var(--bad); font-size: 0.8rem; margin: -4px 0 12px; }

.form-actions { display: grid; gap: 8px; }
.link-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 0.78rem;
  text-decoration: underline;
  cursor: pointer;
  padding: 2px 0;
  justify-self: center;
}
.link-btn:hover { color: var(--text); }

/* Used to sit stacked below the rule form with a separating top border;
   now it's a standalone card next to the form (see .alerts-grid below), so
   it just needs the same look as any other .card. */
.alert-rules-section { max-height: 640px; overflow-y: auto; }
.alerts-grid { align-items: start; }
.alert-rules-section h3 { margin: 0 0 10px; font-size: 0.9rem; }
.alert-rule-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
}
.alert-rule-row:last-child { margin-bottom: 0; }
.alert-rule-row.editing { border-color: #2563eb; }
.alert-rule-main { min-width: 0; }
.alert-rule-table-badge {
  display: inline-block;
  color: var(--accent-2);
  background: rgba(34, 211, 238, 0.12);
  border: 1px solid rgba(34, 211, 238, 0.3);
  border-radius: 999px;
  padding: 1px 9px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-bottom: 5px;
}
.alert-rule-label { font-weight: 700; font-size: 0.85rem; }
.alert-rule-meta { color: var(--text-dim); font-size: 0.72rem; margin-top: 4px; }
.alert-rule-thresholds { margin-top: 4px; display: grid; gap: 2px; }
.alert-rule-threshold-line { color: var(--text-dim); font-size: 0.72rem; line-height: 1.4; }
.alert-rule-actions { display: flex; align-items: center; gap: 6px; flex: none; }
/* The per-rule enable/disable switch reuses .toggle-row/.toggle-switch
   (same look as the top-of-modal browser-notification toggle) but sits
   inline with the Edit/Remove buttons instead of stacked in its own row. */
.alert-rule-toggle { margin-bottom: 0; }

@media (max-width: 720px) {
  .grid { grid-template-columns: 1fr; }
  .live-monitor-grid { grid-template-columns: 1fr; }
  .modal-panel { padding: 18px; }
  .threshold-row { grid-template-columns: 1fr; align-items: stretch; }
  /* Below 720px, the 5-column desktop grid (segment + 4 data cells) no
     longer has room for headers like "OBSERVED"/"EXPECTED" or values like
     "(-11.62pp)" — they overlapped and became illegible on real phones.
     Instead: the segment name (always each row's first child, in
     .freq-row/.rtp-row/.mult-row alike) spans the full width on its own
     line, and the remaining 4 data cells wrap into a 2x2 grid below it. */
  .freq-row, .rtp-row, .mult-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px 12px;
    font-size: 0.78rem;
  }
  .freq-row > *:first-child, .rtp-row > *:first-child, .mult-row > *:first-child {
    grid-column: 1 / -1;
  }
  .top-wins-list li { grid-template-columns: 22px 90px 1fr auto; }
  .top-wins-list li .win-topslot { display: none; }
  /* The 56px side padding tuned for the 960px desktop video width leaves
     almost nothing for the pills on a phone-width screen. */
  .timeframe-group { padding: 12px 16px; }
}

/* ---------- Login / Sign up page ---------- */

.login-body {
  min-height: 100vh;
  display: flex;
}
.login-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
}
.login-card {
  width: 100%;
  max-width: 380px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 28px;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  box-shadow: 0 24px 70px -30px rgba(2, 6, 23, 0.85);
}
.login-head { text-align: center; margin-bottom: 20px; }
.login-head h1 { font-size: 1.2rem; margin: 0 0 6px; }
.login-head .subtitle { display: block; }

.login-tabs {
  display: flex;
  gap: 6px;
  background: #142645;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px;
  margin-bottom: 18px;
}
.login-tab {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text-dim);
  padding: 8px 12px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}
.login-tab.active {
  color: #fff;
  background: var(--brand-gradient);
}

.login-oauth { display: grid; gap: 10px; margin-bottom: 18px; }
/* !important needed: these buttons carry .btn-primary.btn-full too, whose
   two-class selector (0,2,0) outranks this one-class selector (0,1,0) and
   sets display:block, overriding the flex layout below regardless of
   source order. */
.oauth-btn { display: flex !important; align-items: center; justify-content: center; gap: 10px; text-decoration: none; }
.oauth-btn svg { width: 18px; height: 18px; flex: none; }
.oauth-btn[aria-disabled='true'] {
  opacity: 0.4;
  pointer-events: none;
  filter: grayscale(0.4);
}

.login-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-dim);
  font-size: 0.72rem;
  margin: 4px 0 16px;
}
.login-divider::before,
.login-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.login-notice {
  color: var(--text-dim);
  font-size: 0.78rem;
  background: #142645;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  margin: -4px 0 14px;
}
.login-notice--success {
  color: var(--good);
  background: rgba(45, 212, 191, 0.12);
  border-color: rgba(45, 212, 191, 0.4);
}

.login-switch {
  text-align: center;
  font-size: 0.8rem;
  color: var(--text-dim);
  margin: 14px 0 0;
}
.login-switch .link-btn { display: inline; font-weight: 700; }

/* "Forgot password?" — sits right under the password field, right-aligned
   like most login forms put it, pulled up close to the field above rather
   than reading as its own separate row. */
.auth-forgot-row {
  text-align: right;
  margin: -8px 0 4px;
}
.auth-forgot-row .link-btn { font-size: 0.78rem; }

/* ---------- Topbar auth state (index.html) ---------- */
/* .topbar-auth/.topbar-user both set display:flex at the same specificity
   as the browser's built-in `[hidden] { display: none }` rule — for equal
   specificity, an author stylesheet always wins over the UA default, so
   without the [hidden] overrides below, toggling the `hidden` attribute/
   property from app.js would have no visible effect and both would stay
   permanently on screen together regardless of login state. */

.topbar-auth {
  display: flex;
  align-items: center;
  gap: 8px;
}
.topbar-auth[hidden] { display: none; }
.topbar-auth a.btn-secondary,
.topbar-auth a.btn-primary {
  text-decoration: none;
  display: inline-block;
}

/* Plain-text "Alerts" link (icon + label, no button chrome) that sits
   inline with the user profile, immediately to its left. */
.alerts-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  color: var(--text);
  font-weight: 700;
  font-size: 0.85rem;
  padding: 6px 4px;
  cursor: pointer;
  transition: color 0.15s ease;
}
.alerts-link:hover { color: #FBBF24; }
.alerts-link[hidden] { display: none; }
.alerts-icon { width: 17px; height: 17px; fill: currentColor; flex-shrink: 0; }
/* Count of this account's configured Discord/SMS alert rules — see
   updateAlertsBadge() in app.js. Hidden entirely at zero rather than
   showing "0". */
.alerts-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #6c5ce7;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1;
}
.alerts-badge[hidden] { display: none; }

.topbar-user {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
}
.topbar-user[hidden] { display: none; }
/* Vertical divider between the Alerts link and the avatar + name — only
   ever visible together with topbar-user itself (both show/hide as a pair
   on login/logout), so no extra hidden-state handling is needed here. */
.topbar-user::before {
  content: '';
  width: 1px;
  height: 20px;
  background: var(--border);
}

/* The avatar + name together are one button that opens the small
   "Log Out" popup menu — see .user-menu below. */
.user-menu-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 4px 12px 4px 4px;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  transition: border-color 0.15s ease;
}
.user-menu-btn:hover,
.user-menu-btn[aria-expanded="true"] { border-color: var(--border); }
.topbar-user-name { color: var(--text); font-weight: 600; font-size: 0.82rem; }

.user-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--brand-gradient);
  color: #fff;
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  overflow: hidden;
  flex-shrink: 0;
}
.user-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.user-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 140px;
  background: var(--panel-2);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  padding: 6px;
  box-shadow: 0 16px 40px -12px rgba(0, 0, 10, 0.6);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  z-index: 60;
}
.user-menu[hidden] { display: none; }
.user-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--text);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 8px 10px;
  border-radius: 6px;
  cursor: pointer;
}
.user-menu-item:hover { background: rgba(255, 255, 255, 0.08); color: var(--bad); }
.btn-secondary {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  font-weight: 700;
  font-size: 0.85rem;
  padding: 10px 20px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease;
}
.btn-secondary:hover { border-color: var(--accent); }

/* ===================== Sidebar / multi-page shell ===================== */
/* Added for the sidebar-nav redesign: a real left-hand nav plus a
   hash-routed set of pages (#/dashboard, #/live-game, etc. — see app.js's
   renderRoute()). Every element carrying a `data-page` attribute is toggled
   via the `hidden` attribute from JS; the override below is required
   because several of those elements (.bonus-panel-tiles, .grid, .card-tall
   .recent-spins-list, etc.) already set their own `display` in this
   stylesheet, and an author rule of equal-or-lower specificity than
   `[hidden]` still beats the browser's UA default for that attribute — see
   the older, identical note above for .topbar-auth[hidden]. */
[data-page][hidden] { display: none !important; }
.page-global[hidden] { display: none !important; }
/* Same reasoning as above: .tabs and .topbar-timeframe each set their own
   `display` (flex), which otherwise outranks the browser's default
   `[hidden]{display:none}` rule when the router hides them for a
   table-independent page (Alerts/Settings) — see renderRoute() in app.js. */
.topbar-timeframe[hidden] { display: none !important; }

.app-shell {
  display: flex;
  align-items: stretch;
  min-height: 100vh;
}

.sidebar {
  width: 220px;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 18px 14px;
  background: var(--panel);
  border-right: 1px solid var(--border);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  z-index: 40;
}
.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 18px;
  color: inherit;
  text-decoration: none;
}
.sidebar-brand-icon { width: 30px; height: 30px; flex: none; }
.sidebar-brand-text { font-weight: 700; font-size: 0.95rem; line-height: 1.15; }
/* Only shown on the mobile off-canvas sidebar (see the max-width: 900px
   block below) — desktop's sidebar is always visible so it has nothing to
   close. .sidebar is already position: sticky/fixed at every width, so it
   is this button's containing block without any extra positioning here. */
.sidebar-close {
  display: none;
  position: absolute;
  top: 14px;
  right: 14px;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
}
.sidebar-close:hover { color: var(--text); background: rgba(255, 255, 255, 0.08); }
.sidebar-close svg { width: 18px; height: 18px; }
.sidebar-nav { display: flex; flex-direction: column; gap: 3px; flex: 1; }
.nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 10px;
  color: var(--text-dim);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.nav-item:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); }
.nav-item.active {
  background: var(--brand-gradient);
  color: #fff;
  box-shadow: 0 10px 24px -12px rgba(37, 99, 235, 0.7);
}
/* Fixed-width icon slot so every label lines up regardless of glyph shape;
   opacity dims unselected items the same way the old .nav-dot did, and
   .active brings each icon up to full strength (see the Sidebar redesign
   mockup this replaces the plain-dot markers with). */
.nav-icon {
  width: 18px;
  height: 18px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.75;
}
.nav-icon svg { width: 18px; height: 18px; fill: currentColor; }
.nav-item.active .nav-icon { opacity: 1; }
/* Unread/configured-rule count next to "Alerts" — mirrors the topbar's
   .alerts-badge (see updateAlertsBadge() in app.js, which now keeps both
   in sync), just pinned to the right edge of the nav row instead. */
.nav-badge {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #6c5ce7;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  flex: none;
}
.nav-badge[hidden] { display: none; }
/* Promo card below the nav list pointing new visitors at the Alerts page.
   The CTA reuses .btn-primary (the site's one button style) rather than
   inventing a second button look — see the note above that class. */
.sidebar-promo {
  margin: 10px 2px 4px;
  padding: 14px;
  border-radius: 14px;
  background: linear-gradient(160deg, rgba(124, 58, 237, 0.22), rgba(37, 99, 235, 0.08));
  border: 1px solid rgba(167, 139, 250, 0.28);
}
.sidebar-promo-row { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 12px; }
.sidebar-promo-icon {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #a8cfff, #6c5ce7);
  color: #fff;
}
.sidebar-promo-icon svg { width: 19px; height: 19px; }
.sidebar-promo-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sidebar-promo-title { font-size: 0.82rem; font-weight: 700; color: var(--text); }
.sidebar-promo-sub { font-size: 0.72rem; color: var(--text-dim); }
/* Doubled up with .btn-primary here (matching .btn-primary.btn-full's own
   two-class specificity) so these actually win the cascade instead of
   silently losing to .btn-primary.btn-full's font-size/padding — a real
   dead-rule bug caught live in DevTools. font-weight is knocked down from
   .btn-primary's bold 700 to a lighter 500 for this smaller CTA. */
.sidebar-promo-btn.btn-primary { font-size: 0.8rem; padding: 4px 14px; font-weight: 500; text-decoration: none; }
.sidebar-foot {
  color: var(--text-dim);
  font-size: 0.7rem;
  padding: 10px 8px 2px;
  margin: 0;
}

.main-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* This used to be a 40px padding-bottom here (moved from body — see git
     history) to give .app-shell's height (the sidebar's sticky containing
     block) some real space past the last card, since body's own padding
     wasn't counted and the sticky sidebar detached 40px early otherwise.
     Now that .site-footer is a real, always-present last child of this
     column, IT anchors .app-shell's bottom edge instead — so the manual
     padding became redundant and was just adding 40px of empty space below
     the footer on every page. Removed; if a future page ever renders with
     no footer, this may need revisiting. */
}

.sidebar-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  width: 34px;
  height: 34px;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
}
.sidebar-toggle svg { width: 18px; height: 18px; }

#page-title { font-size: 1.15rem; margin: 0; }

/* ---- Hero banner (promo + Show Past Results filter + decorative art) ----
   Replaces the old plain topbar row with a proper hero card: a dark
   gradient panel with a couple of large, softly blurred color blobs behind
   everything (.hero-banner-glow) for the "premium neon" look, three
   flex children laid out left-to-right (promo text+CTA / the existing
   timeframe filter, now restyled to sit inline instead of centered on its
   own / a decorative wheel-and-coins graphic), and overflow:hidden so the
   glow blobs and the art never spill past the card's own rounded corners. */
.hero-banner {
  position: relative;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
  margin: 12px 20px 20px;
  padding: 20px 20px;
  border: 1px solid var(--border);
  border-radius: 18px;
  background: linear-gradient(115deg, #100a26 0%, #1a1140 45%, #442c65 100%);
  overflow: hidden;
}
/* Two blurred, tinted circles rather than one — a single blob reads as a
   flat spotlight, two overlapping ones (pink + blue, offset) is what
   actually gives the "colorful stage lighting" impression the reference
   design has, without needing an image for it. Pure decoration, so it's a
   sibling <div> marked aria-hidden rather than a background image — an
   image here would have to be re-exported any time the theme colors
   change; this stays in sync with --accent-2/--c-crazy_time automatically. */
.hero-banner-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 14% 20%, rgba(255, 77, 94, 0.35), transparent 40%),
    radial-gradient(circle at 55% -10%, rgba(176, 107, 255, 0.3), transparent 45%),
    radial-gradient(circle at 100% 60%, rgb(68 30 123), transparent 45%);
  filter: blur(30px);
}
.hero-banner-promo,
.hero-banner-filter {
  position: relative; /* sit above .hero-banner-glow */
  z-index: 1;
}
.hero-banner-promo {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  flex: 1 1 320px;
  min-width: 260px;
}
.hero-banner-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(251, 113, 133, 0.16);
  color: var(--bad);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.hero-banner-badge-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--bad);
  box-shadow: 0 0 6px 1px var(--bad);
}
.topbar-promo-title {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 800;
  line-height: 1.2;
  color: var(--text);
}
.modal-head, .hero-banner-accent { color: #fbbf24; }
.topbar-promo-desc {
  margin: 0;
  max-width: 46ch;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-dim);
}
.hero-banner-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  padding: 10px 18px;
  border: none;
  border-radius: 10px;
  background: var(--brand-gradient);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  transition: filter 0.15s ease;
}
.hero-banner-btn:hover { filter: brightness(1.08); }
.hero-banner-btn-icon,
.hero-banner-btn-chevron { width: 14px; height: 14px; flex: none; }

/* The existing Show Past Results filter, now an inline peer of the promo
   text instead of its own centered block — see .timeframe-group--topbar
   for the base pill-row styling this still shares with every other use of
   .timeframe-group. */
.timeframe-group--topbar {
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  position: static;
  flex: 0 0 auto;
  width: 230px;
  max-width: 230px;
  margin: 0;
  padding: 14px 18px;
  border: 1px solid rgb(70 58 121);
  border-radius: 14px;
  background: rgba(15, 12, 29, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.timeframe-group-label--inline {
  position: static;
  transform: none;
  border: none;
  background: none;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.7rem;
}
.hero-banner-filter-icon { width: 13px; height: 13px; flex: none; }
/* Fixed 2-column grid rather than flex-wrap: with only one .timeframe-group
   in the app (this one, inside the fixed-width hero-banner filter panel),
   a wrapping flex row left uneven rows (2 pills, then 3, then 2) depending
   on each label's text width. A grid gives a predictable layout instead:
   "All Time" spans both columns on its own row, then the six duration
   pills pair up two-per-row in the order they already appear in the
   markup (1h/6h, 12h/24h, 48h/72h). */
.timeframe-options {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  width: 100%;
}
.timeframe-option {
  display: block;
}
.timeframe-option:first-child {
  grid-column: 1 / -1;
}
.timeframe-option span {
  width: 100%;
  text-align: center;
}

@media (max-width: 720px) {
  .hero-banner { flex-direction: column; align-items: stretch; text-align: center; }
  /* .hero-banner-promo's desktop flex-basis (320px, meant as a target WIDTH
     for the row layout above) gets reinterpreted as a target HEIGHT once
     the banner switches to a column here, leaving a tall empty gap below
     the button. Reset it so the promo block just hugs its content on
     mobile (same fix as .hero-status-title above). */
  .hero-banner-promo { flex: 0 0 auto; align-items: center; }
  .timeframe-group--topbar { align-items: center; width: 100%; max-width: 100%; }
  .timeframe-group-label--inline { justify-content: center; width: 100%; }
}

/* ---- "Where to Play" casino-referral modal (#play-crazy-time-modal) ----
   Two labeled groups of outbound affiliate links (Sweepstakes / No KYC
   Crypto — see app.js's CASINO_LINKS/renderCasinoLinks), each rendered as a
   grid of logo+name cards rather than plain inline links, so this reads as
   a deliberate, organized directory rather than a wall of text links. Wider
   than the base .modal-panel (see .modal-panel--wide) to fit a 3-column
   card grid instead of the narrow single-column layout every other modal
   (alerts, delete-account, stream embed) uses. */
.modal-panel--wide { max-width: 760px; }
.casino-group + .casino-group { margin-top: 18px; }
.casino-group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
}
.casino-group-head--sweepstakes { --casino-group-ring: #e879f9; --casino-group-tint: rgba(232, 121, 249, 0.12); }
.casino-group-head--crypto { --casino-group-ring: #6c5ce7; --casino-group-tint: #6c5ce71a; }
.casino-group-icon {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid var(--casino-group-ring);
  color: var(--casino-group-ring);
  background: var(--casino-group-tint);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  font-weight: 700;
}
.casino-group-title {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text);
  white-space: nowrap;
}
.casino-group-line {
  flex: 1;
  height: 2px;
  min-width: 24px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--casino-group-ring), transparent 85%);
  position: relative;
}
.casino-group-line::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  transform: translateY(-50%);
  background: var(--casino-group-ring);
  box-shadow: 0 0 8px var(--casino-group-ring);
}
.casino-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
@media (max-width: 640px) {
  .casino-list { grid-template-columns: 1fr; }
}
@media (min-width: 641px) and (max-width: 900px) {
  .casino-list { grid-template-columns: repeat(2, 1fr); }
}
.casino-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
  min-width: 0;
}
.casino-link:hover { border-color: var(--accent-2); background: var(--panel-2); transform: translateY(-1px); }
.casino-link-logo {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.casino-link-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.casino-link-name {
  flex: 1;
  min-width: 0;
  font-size: 0.88rem;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.casino-link-external {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
}
.casino-link:hover .casino-link-external { color: var(--accent-2); border-color: var(--accent-2); }
/* Deliberately smaller/quieter than .modal-hint — this is fine-print
   (age/responsible-gambling notice), not primary modal copy. */
.casino-disclaimer {
  margin: 18px 0 0;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--panel);
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.casino-disclaimer-badge {
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 2px solid #322a54;
  color: #a79fcf;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 800;
}
.casino-disclaimer-text {
  margin: 0;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--text-dim);
}
.casino-disclaimer-text a { color: #a79fcf; }

/* Per-table routed pages (Dashboard/Statistics/RTP Analysis/etc. — see
   #panel-template in index.html and applyTablePageVisibility() in app.js).
   The hero card + stat tiles above already carry their own spacing; this
   just keeps whatever comes next (a .grid, .card, or .live-monitor-grid)
   from butting up against them with no gap when a page has no card of its
   own before them. */
.panel > .page { padding-top: 0px; }

.page-head { padding: 16px 0 4px; }
.page-head h2 { margin: 0 0 6px; font-size: 1.1rem; }
.page-global { padding: 0 20px 20px; display: grid; gap: 14px; }

/* ---- Spin Timeline strip ---- */
.spin-timeline-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 2px 0 6px;
}
.timeline-chip {
  flex: none;
  min-width: 30px;
  height: 30px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6rem;
  font-weight: 800;
  color: #08111f;
}
/* Color key below the chip grid: same seg-chip swatch + label pairing used
   in the Segment Frequency table, in a 2-column layout — see
   renderSpinTimeline() in app.js. */
.timeline-legend {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 16px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.timeline-legend-item { font-size: 0.8rem; font-weight: 500; }

/* ---- Segment Frequency list (Dashboard) ---- */
.freq-simple-list { display: flex; flex-direction: column; }
.freq-simple-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.freq-simple-row:first-child { padding-top: 4px; }
.freq-simple-row:last-child { padding-bottom: 0; border-bottom: none; }
.freq-simple-label {
  flex: none;
  width: 84px;
  font-size: 0.83rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.freq-simple-observed { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.freq-simple-observed-pct { font-size: 0.85rem; font-weight: 700; }
/* No visible unfilled track: the colored .bar-fill is the only thing drawn,
   so its length reads directly as the observed percentage. */
.freq-simple-track { background: transparent; }
.freq-simple-expected { flex: none; width: 56px; text-align: right; font-size: 0.83rem; font-weight: 600; }

.mini-tile { padding: 12px 14px; }

/* ---- Session Overview / Bonus Cycle History: icon list rows ---- */
.overview-list { display: flex; flex-direction: column; }
.overview-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.overview-row:first-child { padding-top: 4px; }
.overview-row:last-child { padding-bottom: 0; border-bottom: none; }
.overview-icon {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.06);
  border: 0;
}
.overview-icon svg { width: 24px; height: 24px; }
.overview-icon--chip { border: none; }
.overview-label { flex: 1; min-width: 0; color: var(--text-dim); font-size: 0.92rem; }
.overview-value { font-weight: 700; font-size: 1.05rem; white-space: nowrap; }
.bonus-cycle-history { display: grid; gap: 8px; margin-top: 14px; }
.cycle-row {
  display: grid;
  grid-template-columns: 130px 1fr 40px 110px;
  align-items: center;
  gap: 10px;
  font-size: 0.8rem;
}
.cycle-seg { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.cycle-bar-track {
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
}
.cycle-bar-fill { height: 100%; border-radius: 4px; }
.cycle-gap { text-align: right; font-weight: 700; }
.cycle-time { color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* ---- Recent Sessions (per-day rollup) ---- */
.sessions-table { display: grid; gap: 0; font-size: 0.85rem; }
.day-row {
  display: grid;
  grid-template-columns: 110px repeat(5, 1fr);
  gap: 10px;
  align-items: center;
  padding: 8px 0;
}
.day-row--head {
  color: var(--text-dim);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.day-row:not(.day-row--head) { background: rgba(255, 255, 255, 0.02); }
.day-row--alt { background: rgba(255, 255, 255, 0.06) !important; }
.day-label { font-weight: 600; }

/* ---- Settings page ---- */
.settings-hero-list { display: grid; gap: 10px; }
.settings-hero-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
}
.settings-hero-row-label { font-weight: 600; font-size: 0.85rem; }
.settings-hero-row-url {
  color: var(--text-dim);
  font-size: 0.72rem;
  display: block;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-account-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}

@media (max-width: 900px) {
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
    /* Extra clearance at the true bottom of the off-canvas panel so the
       promo card + footer (pushed down by .sidebar-nav's flex: 1) sit with
       a visible gap above the edge instead of flush against it, where a
       phone's own browser chrome or the viewport's rounding can clip it. */
    padding-bottom: 28px;
  }
  .app-shell.sidebar-open .sidebar { transform: translateX(0); }
  .sidebar-toggle { display: inline-flex; }
  .sidebar-close { display: inline-flex; }
  /* The table switcher and Login/Signup only have room to wrap onto their
     own row below ~900px (see .topbar's own flex-wrap) — center each
     group on that row instead of leaving it flush left. */
  .tabs, .topbar-actions { width: 100%; justify-content: center; }
  .cycle-row { grid-template-columns: 1fr; gap: 4px; }
  .cycle-time { text-align: left; }
  .day-row { grid-template-columns: repeat(2, 1fr); }
  .day-row--head { display: none; }
  .day-label { grid-column: 1 / -1; font-size: 0.9rem; }
}

/* ===================== My Account / admin dashboard ===================== */
/* New components for the My Account page-global section (Profile, Invite
   Friends, Activity Logs, Delete Account, and — for admins — Maintenance
   Mode, All Users, Brands, Pages, Menus, Footer, Emails, All Activity
   Logs), plus the site-wide footer and the dynamic Privacy Policy / Terms
   and Conditions pages it links to. Built entirely from existing tokens
   (--bg/--panel/--border/--text/--accent/etc.) and existing component
   patterns (.card, .field, .toggle-row, .checkbox-row, .form-actions,
   .btn-primary/.btn-secondary) rather than a new visual language. */

.field textarea,
.field select {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
  font-family: inherit;
}
.field textarea { resize: vertical; min-height: 80px; }
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent-2);
  box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15);
}

/* My Account page: a left sidebar (#account-tabs, grouped into My Account
   / Site Administration / Logout — see renderAccountPage) beside the
   section's own content (#account-content), mirroring the app's own
   Dashboard/Live Game/... sidebar layout instead of a horizontal pill row. */
.account-layout { display: flex; align-items: stretch; gap: 24px; }
/* Same author-origin-beats-[hidden] issue as .grid/.tabs/.page-global above
   — #account-body carries both class="account-layout" and the hidden
   attribute, and without this the flex display wins, so logging out (which
   sets $body.hidden = true in renderAccountPage) left the sidebar/content
   visibly on screen under the "You're not logged in" gate. */
.account-layout[hidden] { display: none; }
.account-sidebar {
  flex: 0 0 240px;
  min-width: 0;
  border-right: 1px solid #322a54;
  padding-right: 24px;
}
.account-main { flex: 1; min-width: 0; }
@media (max-width: 860px) {
  .account-layout { flex-direction: column; }
  .account-sidebar { flex-basis: auto; width: 100%; border-right: none; padding-right: 0; border-bottom: 1px solid #322a54; padding-bottom: 20px; }
}

/* Each group is its own .account-tabs list; the label sits directly above
   it. Inside the sidebar these stack as full-width rows (like .nav-item in
   the main sidebar) rather than the pill row Pages/SEO's own in-content
   sub-nav still uses (that markup isn't inside .account-sidebar, so it
   keeps the plain .account-tabs pill styling below untouched). */
.account-tab-group { margin-bottom: 22px; }
.account-tab-group:last-child { margin-bottom: 0; }
.account-tab-group-label {
  color: var(--text-dim);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 0 8px;
}

/* Sub-nav pill row inside My Account (switches account-content without
   leaving the page) — same pill shape as .tab/.timeframe-option. */
.account-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.account-tab {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
}
.account-tab:hover { border-color: var(--accent); color: var(--text); }
.account-tab.active { background: var(--brand-gradient); border-color: transparent; color: #fff; }
.account-tab .nav-icon { opacity: 0.75; }
.account-tab.active .nav-icon { opacity: 1; }

/* Sidebar context: full-width flat rows with a left-aligned icon, same
   shape as the main app sidebar's .nav-item, instead of the pill above. */
.account-sidebar .account-tabs { flex-direction: column; flex-wrap: nowrap; gap: 3px; margin: 0; }
.account-sidebar .account-tab {
  width: 100%;
  justify-content: flex-start;
  background: transparent;
  border: none;
  border-radius: 10px;
  padding: 10px 12px;
}
.account-sidebar .account-tab:hover { background: rgba(255, 255, 255, 0.05); border-color: transparent; }
/* .account-sidebar .account-tab above (same 2-class specificity as
   .account-tab.active, but declared later) was winning the cascade and
   wiping the active tab's gradient background back to transparent — this
   3-class selector outranks both, so the active sidebar row actually gets
   the brand-gradient fill, not just the box-shadow. */
.account-sidebar .account-tab.active {
  background: var(--brand-gradient);
  color: #fff;
  box-shadow: 0 10px 24px -12px rgba(37, 99, 235, 0.7);
}
/* Logout is appended to the account tab list (see renderAccountPage) but
   isn't a section like the others — a subtle red tint sets it apart from
   the neutral profile/admin tabs beside it. */
.account-tab.account-tab-danger { color: var(--bad); border-color: rgba(251, 113, 133, 0.35); }
.account-tab.account-tab-danger:hover { border-color: var(--bad); color: var(--bad); }

/* Danger button — Delete Account, Ban — mirrors .btn-secondary's outline
   shape but in the site's existing --bad red instead of the neutral border. */
.btn-danger {
  background: transparent;
  border: 1px solid rgba(251, 113, 133, 0.5);
  color: var(--bad);
  font-weight: 700;
  font-size: 0.85rem;
  padding: 10px 20px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease;
}
.btn-danger:hover { background: rgba(251, 113, 133, 0.14); }
.btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-danger.btn-full { display: block; width: 100%; padding: 12px 20px; font-size: 0.9rem; text-align: center; }
.btn-secondary.btn-full { display: block; width: 100%; padding: 12px 20px; font-size: 0.9rem; text-align: center; }
/* .form-actions is a CSS grid, which blockifies+stretches any child
   regardless of its own display value — except an inline <a>'s intrinsic
   width still wins unless told otherwise, so anchor buttons inside it get
   an explicit nudge to actually look like the <button> siblings beside them. */
.form-actions a.btn-secondary,
.form-actions a.btn-primary,
.form-actions a.btn-danger {
  display: block;
  text-align: center;
  text-decoration: none;
}

/* Generic small status pill — All Users' role/ban status, admin markers. */
.badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 0.72rem; font-weight: 700; }
.badge-active { background: rgba(45, 212, 191, 0.14); color: var(--good); }
.badge-banned { background: rgba(251, 113, 133, 0.14); color: var(--bad); }
.badge-admin { background: rgba(251, 191, 36, 0.14); color: var(--warn); }
.badge-muted { background: rgba(148, 163, 184, 0.14); color: var(--text-dim); }

/* Generic data table for admin lists (All Users, invited players,
   activity logs) — same look as .spins-table (Recent Spins) but without
   forcing a min-width, since these tables aren't meant to horizontal-
   scroll on desktop the way the dense spins list is. */
.data-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.data-table thead th {
  text-align: left;
  color: var(--text-dim);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.data-table tbody td { padding: 8px 10px; vertical-align: middle; }
.data-table tbody tr:nth-child(odd) td { background: rgba(255, 255, 255, 0.02); }
.data-table tbody tr:nth-child(even) td { background: rgba(255, 255, 255, 0.05); }

/* Files admin tab — small square preview of each uploaded image. */
.upload-thumb {
  width: 36px;
  height: 36px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  display: block;
}

/* Invite Friends' referral-link row. */
.copy-row { display: flex; gap: 10px; margin-bottom: 4px; }
.copy-row input {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
}

/* Menus admin — list of label/href pairs an admin can add to the footer. */
.menu-list { list-style: none; margin: 0 0 14px; padding: 0; }
.menu-list-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.menu-list-row input {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 0.82rem;
}
.menu-link-label { flex: 0 0 30%; }
.menu-link-href { flex: 1; }
.menu-subheading { font-size: 0.82rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 8px; }
.menu-list-static { margin-bottom: 18px; }
.menu-list-row-static {
  justify-content: space-between;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
}

/* Simple contenteditable rich-text editor (Pages admin) — swaps out a raw
   HTML textarea for something a non-developer admin can use directly,
   using the browser's own document.execCommand formatting (no editor
   library/bundler in this project). */
.rte-toolbar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.rte-toolbar button {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 11px;
  border-radius: 6px;
  font-size: 0.8rem;
  cursor: pointer;
}
.rte-toolbar button:hover { border-color: var(--accent); color: var(--accent); }
.rte-editor {
  min-height: 260px;
  max-height: 480px;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  color: var(--text);
  font-size: 0.9rem;
  line-height: 1.55;
  margin-bottom: 10px;
}
.rte-editor:focus { outline: none; border-color: var(--accent); }
.rte-editor h2 { font-size: 1.1rem; margin: 14px 0 6px; }
.rte-editor h2:first-child { margin-top: 0; }
.rte-editor p { margin: 0 0 10px; }
.rte-editor ul, .rte-editor ol { margin: 0 0 10px 20px; }
.rte-editor a { color: var(--accent); }

/* Brand admin: text field + "Browse..." file-picker button side by side. */
.file-field-row { display: flex; gap: 10px; }
.file-field-row input[type="url"] { flex: 1; }
.icon-btn {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-dim);
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
}
.icon-btn:hover { color: var(--bad); border-color: var(--bad); }

/* Modals admin (Where to Play casino links): reuses .menu-list-row's flex
   layout and .icon-btn's square buttons, but each row also needs a name +
   URL pair, an "open in new tab" checkbox, and a drag handle for reorder
   alongside the remove button Menus' extraLinks rows already have. */
.casino-admin-name { flex: 0 0 22%; }
.casino-admin-url { flex: 1; }
/* Square logo thumbnail/upload button — click opens the shared hidden file
   input (see renderAdminModals), uploading through the same /api/admin/upload
   endpoint Brand/SEO images use. Shows the "missing image" placeholder glyph
   until a logo URL is set. */
.casino-admin-logo-btn {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  cursor: pointer;
  padding: 0;
}
.casino-admin-logo-btn img { width: 100%; height: 100%; object-fit: cover; display: block; }
.casino-admin-logo-btn:hover { border-color: var(--accent-2); color: var(--accent-2); }
.casino-admin-newtab {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
  font-size: 0.75rem;
  color: var(--text-dim);
  white-space: nowrap;
  cursor: pointer;
}
/* .menu-list-row input's generic styling (background/border/padding) is
   meant for the text/url fields above, not a checkbox — undo it here so
   the checkbox renders like a normal checkbox instead of a padded box. */
.casino-admin-newtab input[type="checkbox"] {
  width: auto;
  padding: 0;
  background: none;
  border: none;
}
/* Grab cursor on the handle itself (not the whole row, since only the
   handle's mousedown arms row.draggable — see renderAdminModals). */
.casino-admin-drag { cursor: grab; }
.casino-admin-drag:active { cursor: grabbing; }
.casino-admin-drag:hover { color: var(--accent); border-color: var(--accent); }
/* The row currently being dragged — a faded placeholder look rather than
   hiding it outright, so its original position in the list stays visible
   as a drop-target reference while it's mid-drag. */
.casino-admin-row-dragging { opacity: 0.4; }

/* Emails admin — one block per template. */
.email-divider { border: none; border-top: 1px solid var(--border); margin: 18px 0; }

/* Footer admin preview strip (logo + name, read-only). */
.site-footer-preview-row { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; color: var(--text-dim); font-size: 0.85rem; }
.site-footer-preview-logo { height: 22px; width: auto; }

/* Inline status banners — maintenance mode, delete-account warning. */
.notice { padding: 10px 14px; border-radius: 10px; font-size: 0.85rem; margin-bottom: 16px; border: 1px solid transparent; }
.notice-warn { background: rgba(251, 191, 36, 0.1); color: var(--warn); border-color: rgba(251, 191, 36, 0.3); }
.notice-danger { background: rgba(251, 113, 133, 0.1); color: var(--bad); border-color: rgba(251, 113, 133, 0.3); }
.notice-good { background: rgba(45, 212, 191, 0.1); color: var(--good); border-color: rgba(45, 212, 191, 0.3); }

/* Success-styled toast (profile saved, etc.) — same left-border-accent
   pattern as .toast.bonus/.bigwin/.milestone/.error above. */
.toast.good { border-left-color: var(--good); }

/* ---- dynamic Privacy Policy / Terms and Conditions pages ---- */
.legal-content h2 { font-size: 1rem; margin: 20px 0 6px; color: var(--text); }
.legal-content h2:first-child { margin-top: 0; }
.legal-content p { color: var(--text-dim); font-size: 0.88rem; line-height: 1.65; margin: 0 0 10px; }
.legal-content strong { color: var(--text); }

/* ---- site-wide footer (rendered on every page, see refreshSiteFooter) ---- */
.site-footer {
  margin-top: auto;
  border-top: 1px solid var(--border);
  padding: 20px 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  color: var(--text-dim);
  font-size: 0.78rem;
}
.site-footer-brand { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--text); font-weight: 700; font-size: 0.88rem; }
.site-footer-brand img { height: 20px; width: 20px; }
.site-footer-links { display: flex; gap: 18px; flex-wrap: wrap; }
.site-footer-links a { color: var(--text-dim); text-decoration: none; }
.site-footer-links a:hover { color: var(--text); }
.site-footer-copy { color: var(--text-faint, var(--text-dim)); }

@media (max-width: 720px) {
  .site-footer { flex-direction: column; align-items: flex-start; }
  .menu-link-label { flex-basis: 40%; }
}
