/* =========================================================================
   QUANTUM EDGE AI — design system
   Palette named around a single idea: markets exist in superposition
   until you act. Cyan = measured/settled, violet = still in flux.
   ========================================================================= */

:root {
  /* Color */
  --void: #0a0a0c;              /* page background — neutral near-black */
  --deep-field: #111114;        /* section background */
  --panel: #16161a;             /* card / panel background */
  --panel-raised: #1e1e24;      /* hovered / elevated panel */
  --qubit-cyan: #3fe0c5;        /* primary accent — "measured" state */
  --qubit-violet: #3b82f6;      /* secondary accent — "superposition" state (blue; overridden to brown in .admin-shell) */
  --qubit-violet-rgb: 59, 130, 246;
  --signal-white: #f2f2f5;      /* primary text */
  --muted-drift: #9aa0ac;       /* secondary text */
  --collapse-coral: #b91c1c;    /* negative / alert (text/border/badge weight) */
  --collapse-coral-rgb: 185, 28, 28;
  --alert-coral: #ff6b81;       /* lighter red for translucent alert/error backgrounds & glows — collapse-coral reads too dark at low opacity on this background */
  --alert-coral-rgb: 255, 107, 129;
  --qubit-green: #16a34a;       /* action/confirm accent */
  --qubit-green-rgb: 22, 163, 74;
  --link-accent: #d4af37;       /* plain/neutral hyperlink color — true metallic gold, not amber/yellow; deliberately fixed (not shell-varying like qubit-violet), see .footer-nudge a and .intel-link */
  --border-soft: rgba(var(--qubit-violet-rgb), 0.22);
  --border-faint: rgba(255, 255, 255, 0.08);

  /* Type */
  --font-display: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, 'Cascadia Code', Consolas, 'SFMono-Regular', monospace;

  /* Shape / motion */
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 22px;
  --shadow-glow: 0 0 60px rgba(var(--qubit-violet-rgb), 0.18);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ---- Reset --------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: radial-gradient(ellipse 120% 80% at 50% -10%, #17171c 0%, var(--void) 60%);
  color: var(--signal-white);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* Safety net: no single element should be able to widen the whole
     page. `clip`, not `hidden` -- `hidden` forces the browser to treat
     body as its own scroll container (per the CSS overflow-x/overflow-y
     interdependency rule: setting one axis non-visible computes the
     other to `auto`), which breaks position:sticky for any descendant
     that's meant to stick to the real page viewport, not body's own
     scrollport -- confirmed live on .app-sidebar (2026-08-23). `clip`
     achieves the same width guard without establishing a scroll
     container at all. */
  overflow-x: clip;
}

img, canvas { display: block; max-width: 100%; }

a { color: inherit; text-decoration: none; }

button { font-family: inherit; cursor: pointer; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.01em;
  margin: 0 0 0.5em;
}

p { margin: 0 0 1em; color: var(--muted-drift); }

::selection { background: var(--qubit-violet); color: var(--void); }

:focus-visible {
  outline: 2px solid var(--qubit-cyan);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---- Utility --------------------------------------------------------- */

.container {
  width: 100%;
  max-width: 1180px;
  margin-inline: auto;
  padding-inline: 24px;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--qubit-green);
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.eyebrow::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--qubit-cyan);
  box-shadow: 0 0 8px var(--qubit-cyan);
}

/* Scoped to the "Technical signals" eyebrow specifically (ticker cards
   + chart pages) — this one relates to live-updating data, unlike most
   .eyebrow labels (plain static section headers), so it gets the same
   green pulsing-dot treatment as the live-price/live-badge dots
   instead of the base cyan static one. */
.eyebrow-live::before {
  background: var(--qubit-green);
  box-shadow: 0 0 8px var(--qubit-green);
  animation: live-pulse 2s ease-in-out infinite;
}

.mono { font-family: var(--font-mono); }

.text-positive { color: var(--qubit-green); }
.text-negative { color: var(--collapse-coral); }
.text-muted { color: var(--muted-drift); }

/* ---- Info tooltip -------------------------------------------------------
   Small info-circle SVG icon that reveals a short explanation on hover/
   keyboard focus — pure CSS, no JS beyond building the markup. Use:
   <span class="info-tip" tabindex="0"><svg>...</svg>
   <span class="info-tip-text">Explanation here.</span></span> */

.info-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  color: var(--muted-drift);
  cursor: help;
  user-select: none;
  transition: color 0.12s ease;
}

.info-tip svg {
  width: 100%;
  height: 100%;
  display: block;
}

.info-tip:hover,
.info-tip:focus,
.info-tip:focus-visible {
  color: var(--qubit-cyan);
}

.info-tip-text {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 260px;
  padding: 8px 10px;
  border-radius: 8px;
  background: #2a2a33;
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: var(--signal-white);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 0, 0, 0.35);
  z-index: 20;
}

.info-tip:hover .info-tip-text,
.info-tip:focus .info-tip-text,
.info-tip:focus-visible .info-tip-text {
  opacity: 1;
  visibility: visible;
}

/* ---- Buttons ----------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 26px;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 14.5px;
  border: 1px solid transparent;
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease), border-color 0.15s var(--ease);
  white-space: nowrap;
}

.btn:active { transform: translateY(1px); }

.btn-primary {
  background: rgba(var(--qubit-green-rgb), 0.12);
  color: var(--signal-white);
  border-color: rgba(154, 160, 172, 0.35);
}

.btn-primary:hover {
  box-shadow: 0 6px 24px rgba(var(--qubit-green-rgb), 0.35);
  background: rgba(var(--qubit-green-rgb), 0.2);
  border-color: var(--muted-drift);
}

.btn-ghost {
  background: transparent;
  border-color: var(--border-soft);
  color: var(--signal-white);
}

.btn-google {
  background: #fff;
  border-color: #fff;
  color: #3c4043;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.btn-google:hover {
  background: #f8f9fa;
  border-color: #f8f9fa;
}

.btn-google svg { width: 18px; height: 18px; flex-shrink: 0; }

.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0;
  color: var(--muted-drift);
  font-size: 12.5px;
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border-faint);
}

.btn-ghost:hover { border-color: var(--qubit-cyan); }

.btn-block { width: 100%; }

.btn-sm { padding: 10px 18px; font-size: 13.5px; }

/* ---- Forms ------------------------------------------------------------- */

.field { margin-bottom: 20px; }

.field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted-drift);
  margin-bottom: 8px;
  letter-spacing: 0.01em;
}

/* Text-like inputs, sitewide — same reasoning as the global `select`
   rule below: scoping this to `.field input` only left any input NOT
   wrapped in a `.field` div (e.g. the "Add funds" amount box) with
   plain browser-default styling instead of this app's dark theme.
   The exclusion list is deliberately negative (everything except the
   input types with their own native widget/behavior) rather than a
   positive list of text/email/tel/etc., so a `type="number"` or
   `type="search"` field — or a `type`-less input, which defaults to
   text — is covered automatically without needing its own line here. */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"]):not([type="hidden"]):not([type="range"]):not([type="color"]):not([type="image"]),
textarea {
  background: var(--void);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  padding: 13px 14px;
  color: var(--signal-white);
  font-family: var(--font-body);
  font-size: 16px; /* 16px stops iOS Safari from auto-zooming the page on focus */
  transition: border-color 0.15s var(--ease);
}

/* Width stays scoped to .field — an unscoped width:100% would stretch
   inputs sized/positioned by their own inline styles elsewhere on the
   page (the same risk already avoided for the global `select` rule). */
/* Never a checkbox/radio: at 100% wide one fills its own line and sits
   centred above its label (register.php's terms box, 2026-10-05). */
.field input:not([type="checkbox"]):not([type="radio"]), .field textarea { width: 100%; }

textarea { resize: vertical; }
.field textarea { min-height: 90px; }

input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"]):not([type="hidden"]):not([type="range"]):not([type="color"]):not([type="image"]):focus,
textarea:focus {
  border-color: var(--muted-drift);
}

/* outline suppressed for pointer focus only — :focus-visible (keyboard
   nav) must fall through to the sitewide ring at the top of this file,
   or keyboard users lose all focus indication on every text field. */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"]):not([type="hidden"]):not([type="range"]):not([type="color"]):not([type="image"]):focus:not(:focus-visible),
textarea:focus:not(:focus-visible) {
  outline: none;
}

input.has-error { border-color: var(--collapse-coral); }

/* Native date/datetime-local calendar icon renders near-black by
   default — invisible against this app's dark background.
   color-scheme: dark alone is what fixes this correctly across every
   modern browser (Chrome/Edge/Safari AND Firefox) — do NOT also add
   filter: invert(1) on the -webkit- pseudo-element below; that was
   inverting an icon color-scheme had already lightened, flipping it
   back to a barely-visible dark icon (confirmed live in Chrome). */
input[type="datetime-local"],
input[type="date"] {
  color-scheme: dark;
}

input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
}

/* Native radio/checkbox controls otherwise render in the browser's
   default blue with no styling hook here — accent-color is the
   standard way to recolor them without rebuilding custom widgets. */
input[type="radio"],
input[type="checkbox"] {
  accent-color: var(--qubit-green);
}

/* Sitewide dropdown styling — native <select> otherwise renders as a
   plain browser-default box (often auto-inverted to grey by the OS/
   browser's own dark-mode handling, not this app's design system).
   Scoped to the bare element (not `.field select`) so it covers every
   dropdown regardless of what wraps it — pages that already set their
   own inline style on a <select> keep that look unchanged, since an
   inline style always wins over this rule. */
select {
  background-color: var(--void);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%239aa0ac' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  padding: 13px 38px 13px 14px;
  color: var(--signal-white);
  font-family: var(--font-body);
  font-size: 16px; /* 16px stops iOS Safari from auto-zooming the page on focus */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  cursor: pointer;
  transition: border-color 0.15s var(--ease);
}

select:focus {
  border-color: var(--muted-drift);
}

select:focus:not(:focus-visible) {
  outline: none;
}

select.has-error { border-color: var(--collapse-coral); }

select option {
  background: var(--void);
  color: var(--signal-white);
}

.field-error {
  color: var(--collapse-coral);
  font-size: 13px;
  margin-top: 6px;
}

.field-checkbox {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 13.5px;
  color: var(--muted-drift);
}

/* Inside a .field, `.field label` (display:block, bold caption type) is
   more specific than `.field-checkbox` and turned the row back into a
   block — register.php's terms box, 2026-10-05. Restate the row here. */
.field label.field-checkbox {
  display: flex;
  font-size: 13.5px;
  font-weight: 400;
  letter-spacing: normal;
  margin-bottom: 0;
}

.field-checkbox input { margin-top: 3px; flex-shrink: 0; }

.field-checkbox a { color: var(--qubit-green); }

/* ---- Ticker jump form (Charts hub "Jump to any ticker") ---------------- */

.ticker-jump-form {
  display: flex;
  gap: 12px;
  max-width: 380px;
}

.ticker-jump-input {
  flex: 1;
  min-width: 0; /* lets the input actually shrink instead of pushing the button out of the row */
  text-transform: uppercase;
  background: var(--void);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  padding: 13px 14px;
  color: var(--signal-white);
  font-family: var(--font-body);
  font-size: 16px;
}

.ticker-jump-input:focus {
  border-color: var(--qubit-cyan);
}

.ticker-jump-input:focus:not(:focus-visible) {
  outline: none;
}

.ticker-jump-form .btn { flex-shrink: 0; }

@media (max-width: 420px) {
  .ticker-jump-form { flex-direction: column; max-width: none; }
  .ticker-jump-form .btn { width: 100%; }
}

.alert {
  border-radius: var(--radius-sm);
  padding: 13px 16px;
  font-size: 14px;
  margin-bottom: 20px;
  border: 1px solid;
}

.alert-error {
  background: rgba(var(--alert-coral-rgb), 0.08);
  border-color: rgba(var(--alert-coral-rgb), 0.35);
  color: #ffb3c0;
}

.alert-success {
  background: rgba(63, 224, 197, 0.08);
  border-color: rgba(63, 224, 197, 0.35);
  color: var(--qubit-green);
}

/* True green (vs. the site's teal "success" accent elsewhere) — used
   for the admin email test result banner specifically. */
.alert-success-green {
  background: rgba(127, 184, 154, 0.08);
  border-color: rgba(127, 184, 154, 0.35);
  color: #7fb89a;
}

/* ---- Cards / panels ------------------------------------------------- */

.panel {
  background: var(--panel);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-lg);
}

/* =========================================================================
   Marketing nav + footer (public pages)
   ========================================================================= */

.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 22px;
  position: relative;
}

.site-nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  color: var(--signal-white);
}

.site-nav-toggle svg { width: 20px; height: 20px; }
.site-nav-toggle:hover { border-color: var(--qubit-cyan); }

.site-nav-actions-mobile { display: none; }

/* Logo mark redesign (2026-08): a live-pulse line in a rounded square,
   replacing the old animated orbit-ring "Q" substitute. This mark is a
   standalone icon, not a letterform stand-in, so the wordmark now
   renders in full ("Quantum Edge AI", markup no longer drops the
   leading Q) with a normal positive gap between icon and text. */
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-display);
  font-size: 36px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* Pulse-line mark — a rounded square containing a ticker/ECG-style live
   line, with a bright dot continuously traveling the line's path
   (SMIL animateMotion + mpath — same animation technique the mark this
   replaced already used for its orbiting electron, so it's proven safe
   as an SVG data URI background-image). Baked as a data URI (not inline
   markup) so every `<span class="brand-mark">` across ~20 pages/
   partials stays untouched; swapping the mark everywhere is a CSS-only
   change. Two color variants: green everywhere member/marketing-facing
   (matches the site's primary accent), brown in admin — kept
   deliberately distinct so the mark itself still signals "you're in
   admin", same intent as the mark it replaced. */
.brand-mark {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  border-radius: 14px;
  background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2040%2040%22%3E%3Cpath%20id%3D%22pl%22%20d%3D%22M5%2C27%20L16%2C27%20L21%2C11%20L27%2C33%20L35%2C19%22%20fill%3D%22none%22%20stroke%3D%22%23e9fbf1%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20opacity%3D%220.75%22%2F%3E%3Ccircle%20r%3D%223.2%22%20fill%3D%22%2315803d%22%3E%3CanimateMotion%20dur%3D%222.4s%22%20repeatCount%3D%22indefinite%22%3E%3Cmpath%20href%3D%22%23pl%22%2F%3E%3C%2FanimateMotion%3E%3C%2Fcircle%3E%3C%2Fsvg%3E") center / contain no-repeat;
}

/* Admin variant — brown line/background (admin's own palette). Scoped to
   .admin-shell/.admin-login-screen (not the narrower .admin-sidebar/
   .admin-login-card) so it also catches the mobile topbar's brand-mark
   in admin/partials/sidebar.php. */
.admin-shell .brand-mark,
.admin-login-screen .brand-mark {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2040%2040%22%3E%3Cpath%20id%3D%22pl%22%20d%3D%22M5%2C27%20L16%2C27%20L21%2C11%20L27%2C33%20L35%2C19%22%20fill%3D%22none%22%20stroke%3D%22%23e9fbf1%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20opacity%3D%220.75%22%2F%3E%3Ccircle%20r%3D%223.2%22%20fill%3D%22%23b8763f%22%3E%3CanimateMotion%20dur%3D%222.4s%22%20repeatCount%3D%22indefinite%22%3E%3Cmpath%20href%3D%22%23pl%22%2F%3E%3C%2FanimateMotion%3E%3C%2Fcircle%3E%3C%2Fsvg%3E");
}

/* Sidebar contexts stay small, deliberately — the dashboard/admin
   sidebar is a fixed 240px column (~200px interior), and the full
   "Quantum Edge AI" wordmark alone (now rendered in full — see the
   logo mark redesign note above) already uses most of that at any real
   size. Font-size trimmed slightly from the 18px this used pre-redesign
   to claw back room for a real (non-negative) icon gap. Scoped to
   .app-shell/.admin-shell (the sidebar layouts), NOT .admin-login-screen
   (that's a centered, unconstrained login card — it gets the same 2x
   size as every other marketing/login page via the base .brand rule). */
.app-shell .brand,
.admin-shell .brand {
  font-size: 16px;
  gap: 8px;
}

.app-shell .brand-mark,
.admin-shell .brand-mark {
  width: 28px;
  height: 28px;
  border-radius: 8px;
}

.site-nav-links {
  display: flex;
  align-items: center;
  gap: 28px;
  font-size: 14.5px;
  color: var(--muted-drift);
}

.site-nav-actions { display: flex; align-items: center; gap: 14px; }

.site-footer {
  border-top: 1px solid var(--border-faint);
  padding-block: 28px 32px;
  margin-top: 80px;
  color: var(--muted-drift);
  font-size: 13.5px;
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 22px;
  padding-bottom: 20px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--border-faint);
}

.site-footer-links .label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-drift);
  margin-right: 2px;
}

.site-footer-links a { color: var(--muted-drift); transition: color 0.15s var(--ease); }
.site-footer-links a:hover { color: var(--qubit-green); }

.site-footer-bottom {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}

/* ---- Hero -------------------------------------------------------------- */

.hero-actions { display: flex; gap: 14px; align-items: center; }

.hero-photo {
  position: relative;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  align-items: center;
  gap: 40px;
  padding-block: 100px 80px;
  min-height: 560px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: radial-gradient(ellipse 120% 100% at 30% 15%, #17171c 0%, var(--void) 70%);
  border: 1px solid var(--border-faint);
}

/* Full-bleed variant — index.php's hero and any features-*.php detail
   page hero that has a real background photo (.hero-photo alone, no
   photo yet, stays boxed inside .container). */
.hero.hero-photo {
  display: block;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-inline: 24px;
  border-radius: 0;
  border-inline: none;
}

/* features.php's top header — full-bleed background photo, but the
   content here is simple centered text (not the 2-column hero-copy /
   atom-wrap split), so this doesn't reuse .hero-inner's grid layout. */
.features-header-photo {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-inline: 24px;
  border-block: 1px solid var(--border-faint);
  background:
    radial-gradient(ellipse 60% 85% at 50% 50%, rgba(8, 8, 10, 0.88) 0%, rgba(8, 8, 10, 0.55) 45%, rgba(8, 8, 10, 0.1) 75%, rgba(8, 8, 10, 0) 100%),
    url('/assets/img/cards/features-header.jpg') center / cover no-repeat;
}

/* A tight, concentrated vignette right behind the text block (not a
   uniform full-box gradient like the first attempt) — strong contrast
   exactly where the words sit, fading out fast so the flanking chart
   panels stay vibrant. Text-shadow layered on top as a second line of
   defense for the edges of that fade. */
.features-header-photo .eyebrow,
.features-header-photo h1,
.features-header-photo .lead,
.features-header-photo .stats-bar {
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.85), 0 1px 4px rgba(0, 0, 0, 0.9);
}

/* .lead normally uses the muted secondary-text gray (--muted-drift),
   which has less inherent contrast than the white heading — brightened
   specifically here since a photo background needs more contrast than
   the plain dark backgrounds .lead is normally used against. */
.features-header-photo .lead {
  color: #d5d7dd;
}

/* Same problem as .lead, worse: .stats-bar's pill background is only
   55% opaque and its text is 12.5px/11.5px muted-gray — barely holds up
   against a busy photo even with the vignette. Darker pill, bigger
   text, brighter gray specifically here. */
.features-header-photo .stats-bar {
  background: rgba(10, 10, 12, 0.85);
  border-color: rgba(255, 255, 255, 0.14);
  padding: 18px 32px;
}

.features-header-photo .stats-bar .stat {
  font-size: 15px;
  color: #d5d7dd;
}

.features-header-photo .live-badge {
  font-size: 14px;
  color: #d5d7dd;
}

.hero-inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  align-items: center;
  gap: 40px;
  max-width: 1180px;
  margin-inline: auto;
}

.quantum-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.quantum-glow-a,
.quantum-glow-b {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  animation: quantum-glow-pulse 9s ease-in-out infinite;
}

.quantum-glow-a {
  width: 380px;
  height: 380px;
  top: -80px;
  left: 8%;
  background: radial-gradient(circle, var(--qubit-cyan), transparent 70%);
}

.quantum-glow-b {
  width: 320px;
  height: 320px;
  bottom: -100px;
  right: 12%;
  background: radial-gradient(circle, var(--qubit-violet), transparent 70%);
  animation-delay: 3s;
}

@keyframes quantum-glow-pulse {
  0%, 100% { opacity: 0.16; transform: scale(1); }
  50% { opacity: 0.3; transform: scale(1.15); }
}

.hero-copy, .hero-photo .atom-wrap {
  position: relative;
  z-index: 1;
}

.hero-copy {
  padding: 10px;
}

.hero-photo h1 {
  font-size: clamp(32px, 4.4vw, 54px);
  line-height: 1.08;
  margin-block: 18px 20px;
  text-shadow: 0 0 30px rgba(0, 0, 0, 0.6);
}

.hero-photo p.lead {
  font-size: 17px;
  max-width: 46ch;
  margin-bottom: 32px;
  color: #d6d6db;
}

.atom-wrap {
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.atom-wrap svg { width: 100%; height: 100%; max-width: 400px; }

.atom-wrap .nucleus-core { animation: nucleus-pulse 2.4s ease-in-out infinite; }

@keyframes nucleus-pulse {
  0%, 100% { r: 10; opacity: 1; }
  50% { r: 14; opacity: 0.75; }
}

/* ---- Section headers shared by benefits + features --------------------- */

.section-label { text-align: center; margin-bottom: 12px; }

.section-title {
  text-align: center;
  font-size: 30px;
  max-width: 780px;
  margin-inline: auto;
  margin-bottom: 50px;
}

/* ---- Benefits ------------------------------------------------------------ */

.benefits { padding-block: 80px; }

.benefits-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}

.benefit-card { padding: 0 0 26px; position: relative; overflow: hidden; text-align: center; }

.benefit-card > *:not(.card-image) { position: relative; z-index: 1; }

.benefit-card .num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 30px;
  margin: 18px auto 14px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--qubit-green);
  border: 1px solid rgba(63, 224, 197, 0.35);
  background: rgba(63, 224, 197, 0.08);
  border-radius: 8px;
}

.benefit-card h3 { font-size: 17px; margin: 0 22px 8px; }
.benefit-card p { font-size: 14px; margin: 0 22px; }

.benefit-card {
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}

.benefit-card:hover {
  transform: translateY(-3px);
  border-color: var(--qubit-cyan);
}

/* ---- Feature grid -------------------------------------------------- */

.feature-grid { padding-block: 20px 90px; }

.feature-grid-inner {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
}

.feature-card {
  padding: 0 0 26px;
  position: relative;
  overflow: hidden;
}

.feature-card > *:not(.card-image) { position: relative; z-index: 1; }

.feature-card h3 { font-size: 16px; margin: 18px 22px 8px; }
.feature-card p { font-size: 14px; margin: 0 22px; }

/* ---- Card header image (benefit-card / feature-card) --------------------
   Bleeds edge-to-edge inside the card (both now use padding:0 on top so no
   negative-margin trick is needed) and clips to the panel's own top radius. */
.card-image {
  aspect-ratio: 5 / 3;
  overflow: hidden;
  background: var(--deep-field);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  margin-bottom: 18px;
}

.card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.feature-card {
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}

.feature-card:hover {
  transform: translateY(-3px);
  border-color: var(--qubit-cyan);
}

/* =========================================================================
   /features.php — category "vertical" banners + stats bar
   ========================================================================= */

.stats-bar {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
  padding: 16px 28px;
  margin: 32px auto 0;
  max-width: fit-content;
  border: 1px solid var(--border-faint);
  border-radius: 999px;
  background: rgba(22, 22, 26, 0.55);
}

.stats-bar .stat {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--muted-drift);
  white-space: nowrap;
}

.stats-bar .stat strong {
  color: var(--signal-white);
  font-weight: 600;
}

.feature-vertical {
  position: relative;
  display: grid;
  grid-template-columns: 1.3fr 0.7fr;
  align-items: center;
  gap: 32px;
  padding: 56px 48px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: radial-gradient(ellipse 120% 100% at 20% 20%, #17171c 0%, var(--void) 70%);
  border: 1px solid var(--border-faint);
  margin-bottom: 28px;
}

.feature-vertical-copy { position: relative; z-index: 1; }
.feature-vertical-copy .eyebrow { margin-bottom: 12px; }
.feature-vertical-copy h2 { font-size: clamp(24px, 3vw, 34px); margin-bottom: 12px; }
.feature-vertical-copy p { max-width: 56ch; margin-bottom: 20px; }

.feature-vertical-copy .explore-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--qubit-green);
  font-family: var(--font-mono);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: gap 0.15s var(--ease);
}

.feature-vertical-copy .explore-link:hover { gap: 12px; }

.feature-vertical-icon {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.feature-vertical-icon svg {
  width: 100%;
  max-width: 170px;
  height: auto;
  opacity: 0.9;
  filter: drop-shadow(0 0 34px rgba(63, 224, 197, 0.25));
}

/* Per-category recoloring — reuses .quantum-field's existing glow-orb
   structure (see .hero-photo above), just different colors, so no new
   animation/JS is needed per category. */
.feature-vertical-signals .quantum-glow-a { background: radial-gradient(circle, var(--qubit-violet), transparent 70%); }
.feature-vertical-signals .quantum-glow-b { background: radial-gradient(circle, var(--collapse-coral), transparent 70%); }
.feature-vertical-signals .feature-vertical-icon svg { filter: drop-shadow(0 0 34px rgba(var(--qubit-violet-rgb), 0.3)); }

.feature-vertical-ai .quantum-glow-a { background: radial-gradient(circle, var(--qubit-cyan), transparent 70%); }
.feature-vertical-ai .quantum-glow-b { background: radial-gradient(circle, var(--qubit-violet), transparent 70%); width: 380px; height: 380px; }

.feature-vertical-news .quantum-glow-a { background: radial-gradient(circle, var(--collapse-coral), transparent 70%); }
.feature-vertical-news .quantum-glow-b { background: radial-gradient(circle, var(--qubit-cyan), transparent 70%); }

.feature-vertical-trading .quantum-glow-a { background: radial-gradient(circle, var(--qubit-green), transparent 70%); }
.feature-vertical-trading .quantum-glow-b { background: radial-gradient(circle, var(--qubit-cyan), transparent 70%); }
.feature-vertical-trading .feature-vertical-icon svg { filter: drop-shadow(0 0 34px rgba(var(--qubit-green-rgb), 0.3)); }

.feature-vertical-charts .quantum-glow-a { background: radial-gradient(circle, var(--qubit-violet), transparent 70%); }
.feature-vertical-charts .quantum-glow-b { background: radial-gradient(circle, var(--qubit-cyan), transparent 70%); width: 380px; height: 380px; }
.feature-vertical-news .feature-vertical-icon svg { filter: drop-shadow(0 0 34px rgba(var(--alert-coral-rgb), 0.25)); }

.feature-vertical-qsuite .quantum-glow-a { background: radial-gradient(circle, var(--qubit-violet), transparent 70%); }
.feature-vertical-qsuite .quantum-glow-b { background: radial-gradient(circle, var(--qubit-cyan), transparent 70%); }
.feature-vertical-qsuite .feature-vertical-icon svg { filter: drop-shadow(0 0 34px rgba(var(--qubit-violet-rgb), 0.3)); }

/* Only 3 accent colors exist in this palette (cyan/violet/coral) and
   every pairing is already in use elsewhere above — Predictions
   deliberately reuses Signals' exact violet/coral pairing rather than
   inventing a new color, since it's the closest existing category
   thematically (structural analysis, real data, nothing fabricated). */
.feature-vertical-predictions .quantum-glow-a { background: radial-gradient(circle, var(--qubit-violet), transparent 70%); }
.feature-vertical-predictions .quantum-glow-b { background: radial-gradient(circle, var(--collapse-coral), transparent 70%); }
.feature-vertical-predictions .feature-vertical-icon svg { filter: drop-shadow(0 0 34px rgba(var(--qubit-violet-rgb), 0.3)); }

/* Hero-only background photo — scoped to .hero-photo specifically so it
   doesn't also apply to the smaller features.php summary banner, which
   shares .feature-vertical-predictions but not .hero-photo. Was a broad
   flat gradient (120%/100% ellipse at strong 0.78-0.92 opacity) that
   made the photo look badly washed out — replaced with the same tight,
   left-positioned vignette + text-shadow approach used for the other 5
   detail-page heroes. */
.feature-vertical-predictions.hero-photo {
  background:
    radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%),
    url('/assets/img/cards/predictions-hero.jpg') center / cover no-repeat;
}

.feature-vertical-predictions.hero-photo .eyebrow,
.feature-vertical-predictions.hero-photo h1,
.feature-vertical-predictions.hero-photo .lead {
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.85), 0 1px 4px rgba(0, 0, 0, 0.9);
}

.feature-vertical-predictions.hero-photo .lead {
  color: #d5d7dd;
}

/* Hero background photos for the other 5 detail pages, added later —
   using the refined vignette approach worked out on features.php's
   header instead of the flatter gradient above (which happened to work
   fine for predictions-hero.jpg's composition, but isn't guaranteed to
   for whatever these 5 photos turn out to look like). Vignette is
   positioned left-of-center (32% not 50%) since these pages keep the
   2-column hero-copy/empty-right-side layout, not centered text —
   right side stays vibrant since nothing sits on top of it anymore
   (the atom graphic that used to be there is gone). Same text-shadow
   safety net as the header for the fade zone at the vignette's edge. */
.feature-vertical-portfolio.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/portfolio-hero.jpg') center / cover no-repeat; }
.feature-vertical-signals.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/signals-hero.jpg') center / cover no-repeat; }
.feature-vertical-ai.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/ai-hero.jpg') center / cover no-repeat; }
.feature-vertical-news.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/news-hero.jpg') center / cover no-repeat; }
.feature-vertical-qsuite.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/qsuite-hero.jpg') center / cover no-repeat; }
/* Added 2026-08-24 with the Trading Simulator and Charts verticals. Same
   vignette + cover-image pattern as the five above. */
.feature-vertical-trading.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/practice-hero.jpg') center / cover no-repeat; }
.feature-vertical-charts.hero-photo { background: radial-gradient(ellipse 70% 90% at 32% 50%, rgba(8, 8, 10, 0.82) 0%, rgba(8, 8, 10, 0.5) 45%, rgba(8, 8, 10, 0.12) 75%, rgba(8, 8, 10, 0) 100%), url('/assets/img/cards/charts-hero.jpg') center / cover no-repeat; }

.feature-vertical-portfolio.hero-photo .eyebrow,
.feature-vertical-portfolio.hero-photo h1,
.feature-vertical-portfolio.hero-photo .lead,
.feature-vertical-signals.hero-photo .eyebrow,
.feature-vertical-signals.hero-photo h1,
.feature-vertical-signals.hero-photo .lead,
.feature-vertical-ai.hero-photo .eyebrow,
.feature-vertical-ai.hero-photo h1,
.feature-vertical-ai.hero-photo .lead,
.feature-vertical-news.hero-photo .eyebrow,
.feature-vertical-news.hero-photo h1,
.feature-vertical-news.hero-photo .lead,
.feature-vertical-qsuite.hero-photo .eyebrow,
.feature-vertical-qsuite.hero-photo h1,
.feature-vertical-qsuite.hero-photo .lead,
.feature-vertical-trading.hero-photo .eyebrow,
.feature-vertical-trading.hero-photo h1,
.feature-vertical-trading.hero-photo .lead,
.feature-vertical-charts.hero-photo .eyebrow,
.feature-vertical-charts.hero-photo h1,
.feature-vertical-charts.hero-photo .lead {
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.85), 0 1px 4px rgba(0, 0, 0, 0.9);
}

.feature-vertical-portfolio.hero-photo .lead,
.feature-vertical-signals.hero-photo .lead,
.feature-vertical-ai.hero-photo .lead,
.feature-vertical-news.hero-photo .lead,
.feature-vertical-qsuite.hero-photo .lead {
  color: #d5d7dd;
}

.feature-vertical-group { margin-bottom: 60px; }
.feature-vertical-group .feature-grid-inner { margin-top: 0; }

@media (max-width: 900px) {
  .feature-vertical { grid-template-columns: 1fr; padding: 40px 28px; text-align: center; }
  .feature-vertical-copy p { margin-inline: auto; }
  .feature-vertical-icon svg { max-width: 130px; }
  .stats-bar { gap: 18px 22px; padding: 14px 20px; }
}

/* ---- Real-data channel grid (Signal Intelligence — 34 wired channels) -- */

.channel-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 14px;
}

.channel-card {
  padding: 16px 16px;
  min-height: 104px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  cursor: default;
  transition: transform 0.18s var(--ease), border-color 0.18s var(--ease),
              background 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

.channel-card:hover {
  transform: translateY(-5px) scale(1.03);
  border-color: var(--qubit-cyan);
  background: var(--panel-raised);
  box-shadow: 0 14px 34px rgba(63, 224, 197, 0.22);
  z-index: 1;
}

.channel-card h4 {
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 600;
  margin: 0 0 6px;
  color: var(--signal-white);
  transition: color 0.18s var(--ease);
}

.channel-card:hover h4 { color: var(--qubit-green); }

.channel-card p {
  font-size: 11.5px;
  line-height: 1.4;
  margin: 0;
  color: var(--muted-drift);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (max-width: 900px) {
  .channel-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 560px) {
  .channel-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ---- News feed (news.php) ------------------------------------------------
   Cards compose .card-image (header art) with .channel-card's hover
   treatment and .badge/.signal-badge for category/sentiment — no new
   visual language invented, just assembled from existing pieces. */

.news-filter-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.news-card {
  display: block;
  color: inherit;
  transition: transform 0.18s var(--ease), border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

.news-card:hover {
  transform: translateY(-4px);
  border-color: var(--qubit-cyan);
  box-shadow: 0 14px 34px rgba(63, 224, 197, 0.16);
}

.news-card-image-fallback {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, rgba(63, 224, 197, 0.12), rgba(var(--qubit-violet-rgb), 0.12));
  color: var(--muted-drift);
}

.news-card-image-fallback svg { width: 30%; height: 30%; }

.news-card-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.news-card-play svg {
  width: 46px;
  height: 46px;
  color: #fff;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.5));
}

.news-card .card-image { position: relative; }

.news-card-body { padding: 0 22px 22px; }

.news-card-badges { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }

.news-card-body h3 { font-size: 16px; margin-bottom: 6px; }

.news-card-body p {
  font-size: 13.5px;
  color: var(--muted-drift);
  margin-bottom: 10px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.news-card-meta {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--muted-drift);
  display: flex;
  gap: 4px;
}

.news-featured-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

.news-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-top: 24px;
}

@media (max-width: 900px) {
  .news-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
  .news-featured-grid { grid-template-columns: 1fr; }
  .news-grid { grid-template-columns: 1fr; }
}

/* ---- "Your tickers" digest — compact card variant ------------------------ */

.ticker-digest-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
}

.ticker-digest-card {
  padding: 14px 16px;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-md);
  background: var(--panel-raised);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ticker-digest-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 2px;
}

.ticker-digest-link {
  font-size: 12.5px;
  color: var(--signal-white);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ticker-digest-link:hover { color: var(--qubit-green); }

/* ---- Video click-to-load modal -------------------------------------------- */

.news-video-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(5, 6, 10, 0.85);
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.news-video-modal.is-open { display: flex; }

.news-video-modal-inner {
  position: relative;
  width: 100%;
  max-width: 900px;
}

.news-video-modal-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #000;
}

.news-video-modal-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.news-video-modal-close {
  position: absolute;
  top: -40px;
  right: 0;
  background: transparent;
  border: none;
  color: var(--signal-white);
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
}

/* ---- Portfolio "Details" ticker modal (2026-08-23) -----------------------
   Same fixed/backdrop-blur/.app-shell-scoped overlay mechanics as
   .confirm-dialog-overlay (see confirm-dialog.js's own comment for why
   .app-shell, not <body>), sized like .news-video-modal-inner but larger
   -- this holds real tabbed content, not a video. z-index one above the
   confirm dialog so a "Remove this position?" confirm can never be
   trapped underneath an already-open Details modal. */

.ticker-detail-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 7, 11, 0.78);
  backdrop-filter: blur(6px) saturate(1.1);
  -webkit-backdrop-filter: blur(6px) saturate(1.1);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 2001;
  padding: 20px;
}

.ticker-detail-modal-overlay.is-open { display: flex; }

.ticker-detail-modal {
  position: relative;
  background: var(--panel-raised);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 28px;
  max-width: 1100px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
}

.ticker-detail-modal-close {
  position: absolute;
  top: 16px;
  right: 16px;
  background: transparent;
  border: none;
  color: var(--muted-drift);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}

.ticker-detail-modal-close:hover { color: var(--signal-white); }

.ticker-detail-modal-head {
  font-size: 20px;
  font-weight: 700;
  color: var(--signal-white);
  margin: 0 24px 16px 0;
}

.ticker-detail-tabs { margin-bottom: 20px; }

.ticker-detail-modal-body { min-height: 120px; }

/* ---- Footer CTA ---------------------------------------------------------- */

.footer-cta {
  text-align: center;
  padding-block: 60px 90px;
}

.footer-cta h2 { font-size: 28px; margin-bottom: 14px; }
.footer-cta p { margin-bottom: 28px; }

@media (max-width: 900px) {
  .hero-photo { grid-template-columns: 1fr; padding-block: 60px; min-height: auto; }
  .hero-inner { grid-template-columns: 1fr; }
  .atom-wrap { max-width: 240px; margin-inline: auto; }
  .benefits-grid { grid-template-columns: repeat(2, 1fr); }
  .feature-grid-inner { grid-template-columns: repeat(2, 1fr); }
  .contact-grid { grid-template-columns: 1fr; gap: 32px; }
}

@media (max-width: 560px) {
  .benefits-grid { grid-template-columns: 1fr; }
  .feature-grid-inner { grid-template-columns: 1fr; }
}

/* =========================================================================
   Auth screens (split layout)
   ========================================================================= */

.auth-layout {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
}

.auth-visual {
  position: relative;
  background: var(--deep-field);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 48px;
  overflow: hidden;
}

.auth-visual canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.auth-visual-copy {
  position: relative;
  z-index: 1;
}

.auth-visual-copy .eyebrow { margin-bottom: 14px; }

.auth-visual-copy h2 {
  font-size: clamp(24px, 2.6vw, 32px);
  max-width: 18ch;
}

.auth-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px;
}

.auth-card { width: 100%; max-width: 400px; }

.auth-card .brand { margin-bottom: 36px; }

.auth-card h1 { font-size: 24px; margin-bottom: 6px; }

.auth-card .subhead {
  color: var(--muted-drift);
  font-size: 14.5px;
  margin-bottom: 30px;
}

.auth-switch {
  text-align: center;
  margin-top: 24px;
  font-size: 14px;
  color: var(--muted-drift);
}

.auth-switch a { color: var(--qubit-green); font-weight: 600; }

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

/* =========================================================================
   Dashboard shell
   ========================================================================= */

.app-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
}

.app-sidebar {
  border-right: 1px solid var(--border-faint);
  padding: 28px 20px;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(var(--qubit-violet-rgb), 0.07), transparent 45%), var(--deep-field);
  /* height:100vh + overflow-y:auto is required, not optional -- without
     it, a sticky element taller than the viewport just gets clipped by
     the screen's bottom edge once stuck (sticky alone creates no
     internal scroll mechanism), making anything below the fold
     UNREACHABLE until you scroll almost to the very bottom of the
     whole page. This does mean the sidebar's own scroll position is
     separate from the page's, and resets to the top on every fresh
     page load (e.g. after clicking a nav link scrolled into view) --
     see sidebar.js for the fix that persists it across navigations. */
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  scrollbar-width: none;      /* Firefox */
  -ms-overflow-style: none;   /* legacy Edge/IE */
}

.app-sidebar::-webkit-scrollbar {
  display: none;               /* Chrome/Safari/Edge Chromium */
}

.app-sidebar::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(180deg, transparent, rgba(var(--qubit-violet-rgb), 0.5), transparent);
}

.app-sidebar .brand { margin-bottom: 0; }

.nav-group { display: flex; flex-direction: column; gap: 0; margin-bottom: 28px; }

.nav-group-label {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(242, 242, 245, 0.55);
  padding: 0 12px;
  margin-bottom: 7px;
}

/* Eastern-time clock, shown at the top of every logged-in page (both
   the desktop sidebar and the mobile topbar render one — see
   partials/sidebar.php) — always America/New_York regardless of the
   viewer's own timezone, since this is a trading app where market
   hours are the reference that matters, not local time. Digital-blue
   digits set in Share Tech Mono (Google Font, loaded in
   partials/sidebar.php) — a technical-display face with real
   letterforms; DSEG7's true seven-segment glyphs were tried first and
   dropped for readability. */
.app-clock {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  font-family: var(--font-mono);
}

.app-clock-day {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted-drift);
}

.app-clock-day .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--qubit-green);
  animation: live-pulse 2s ease-in-out infinite;
}

.app-clock-time {
  font-family: 'Share Tech Mono', var(--font-mono);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--signal-white);
}

.app-sidebar .app-clock { width: 100%; margin: 22.5px 0; padding: 12px 0; }
.app-sidebar .js-eastern-clock-day {
  display: inline-block;
  max-width: 220px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

.app-mobile-topbar .app-clock { margin-left: auto; margin-top: 5px; padding: 6px 14px; }
.app-mobile-topbar .app-clock-day { font-size: 12px; }
.app-mobile-topbar .app-clock-time { font-size: 10px; }

/* The date string's length varies with the weekday/month name (e.g.
   "Wednesday August 05, 2026" is 2 chars longer than "Wednesday July
   31, 2026") — narrow enough combinations wrapped onto two lines in
   this tight mobile pill. Clamp to one line with an ellipsis instead,
   so this never recurs as the date keeps changing throughout the year. */
.app-mobile-topbar .js-eastern-clock-day {
  display: inline-block;
  max-width: 150px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9.5px 12px;
  border-radius: var(--radius-sm);
  font-size: 14.5px;
  color: var(--muted-drift);
  border-left: 2px solid transparent;
  transition: color 0.15s var(--ease), background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.nav-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: inline-flex;
  opacity: 0.8;
}

.nav-icon svg { width: 100%; height: 100%; }

.nav-link:hover { color: var(--signal-white); background: var(--panel); }
.nav-link:hover .nav-icon { opacity: 1; }

.nav-link.active {
  color: var(--signal-white);
  background: var(--panel);
  border-left-color: var(--qubit-green);
  box-shadow: inset 0 0 16px rgba(22, 163, 74, 0.1);
}

.nav-link.active .nav-icon { opacity: 1; color: var(--qubit-green); }

.app-sidebar-footer { margin-top: auto; }

.user-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-faint);
  font-size: 13.5px;
}

.user-chip .avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--qubit-cyan), var(--qubit-violet));
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--void);
  font-weight: 700;
}

.app-main { padding: 32px 40px 60px; min-width: 0; }

.app-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 32px;
}

.app-topbar h1 { font-size: 22px; margin: 0; }

.dash-section-heading {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-drift);
  margin: 28px 0 12px;
}

.dash-section-heading:first-of-type { margin-top: 4px; }

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  margin-bottom: 20px;
}

/* Caps a .card-grid at 4 columns instead of auto-fit's unbounded
   growth, AND caps each column's own width at that same even 4-way
   split (accounting for the 3 gaps) instead of letting it grow to 1fr
   -- min and max are identical, so a row with fewer than 4 cards
   never stretches them to fill the leftover space (a lone card no
   longer spans the full page width). Falls back to a 240px floor on
   narrow viewports, same floor .card-grid itself uses, so it still
   shrinks to fewer, wider columns there rather than overflowing.
   Originally added 2026-08-14 for crypto-paper-trading.php's
   open-position grid only (adding more coins was squeezing every
   card's 4-segment seg-bar until "Set target/stop" truncated); widened
   2026-08-24 to the max-width behavior above and applied uniformly
   across every stock/crypto ticker-card grid site-wide (portfolio.php
   previously had its own cap5 variant -- folded into this one for
   consistency, per Wayne's "all stocks and crypto pages" ask). */
.card-grid-cap4 {
  grid-template-columns: repeat(auto-fit, minmax(max(240px, (100% - 3 * 18px) / 4), max(240px, (100% - 3 * 18px) / 4)));
}

/* On a phone-width viewport there's only ever room for one real column
   anyway, so the "don't stretch a lone card to fill leftover space"
   reasoning above doesn't apply — but the fixed min==max width above
   still does, which is exactly what broke this: a container narrower
   than 240px + the fixed track's own margins can't stretch its one
   column to fill the screen, leaving a half-width card with empty
   space beside it (reported live on Android 2026-08-24 across every
   stock/crypto ticker-card grid site-wide — portfolio.php,
   paper-trading.php, watchlist.php, crypto-*.php, etc., all share this
   one class). Forced to a single explicit column rather than left to
   auto-fit's math — Wayne's explicit ask is exactly one card per row
   on mobile, not "however many 240px columns fit" (a wide phone/
   phablet in portrait could still fit 2 up to ~500px). */
@media (max-width: 560px) {
  .card-grid-cap4 {
    grid-template-columns: 1fr;
  }
}

.stat-card { padding: 22px; }

.stat-card .label {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-drift);
  margin-bottom: 10px;
}

.stat-card .value {
  font-family: var(--font-mono);
  font-size: 28px;
  font-weight: 600;
}

.empty-state {
  padding: 48px 32px;
  text-align: center;
  border: 1px dashed var(--border-soft);
  border-radius: var(--radius-lg);
}

.empty-state .glyph {
  width: 44px;
  height: 44px;
  margin-inline: auto;
  border-radius: 12px;
  background: linear-gradient(135deg, rgba(63, 224, 197, 0.16), rgba(var(--qubit-violet-rgb), 0.16));
  border: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 18px;
}

.empty-state h3 { font-size: 17px; margin-bottom: 8px; }
.empty-state p { max-width: 40ch; margin-inline: auto; margin-bottom: 22px; font-size: 14px; }

/* ---- Data table ------------------------------------------------------- */

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14.5px;
}

.data-table th {
  text-align: left;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-drift);
  font-weight: 500;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-faint);
}

.data-table td {
  padding: 14px 16px;
  border-bottom: 1px solid var(--border-faint);
  vertical-align: middle;
}

.data-table tbody tr:hover { background: rgba(255, 255, 255, 0.02); }

.data-table .ticker-cell {
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.data-table .num { font-family: var(--font-mono); text-align: right; }

/* ---- Position card (.mp-card) — portfolio.php, and the same pattern
   reused for paper-trading.php/crypto-portfolio.php/crypto-paper-trading.php
   — one .panel.ticker-card.mp-card per position, inside a .card-grid,
   same layout on mobile and desktop (no separate breakpoint-specific
   markup — .card-grid's own auto-fit/minmax reflows the column count). */
.mp-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.mp-ticker-wrap { min-width: 0; }
.mp-ticker { font-weight: 700; font-size: 17px; color: var(--signal-white); text-decoration: none; }

/* Real per-coin brand icon (crypto_icon_html(), functions.php) sized to
   match .mp-ticker's own 17px font-size — sits directly to the left of
   the symbol, not larger than the text next to it. display:inline-block
   overrides this file's own global "img, canvas { display: block; }"
   reset (top of file) -- left at the block default, the icon dropped
   onto its own line above the ticker text instead of sitting beside it
   in any non-flex call site (confirmed live 2026-08-14). margin-right is
   its own spacing (not relying on a flex-row gap, since not every call
   site wraps the icon+ticker in one) — on crypto-portfolio.php's flex
   row this stacks with that row's own gap, a couple px more space than
   strictly needed but not visually significant. */
.crypto-icon { display: inline-block; width: 17px; height: 17px; margin-right: 5px; vertical-align: -3px; flex-shrink: 0; }
.mp-company {
  font-size: 11.5px;
  color: var(--muted-drift);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mp-price { font-size: 19px; font-weight: 600; color: var(--qubit-violet); flex-shrink: 0; white-space: nowrap; }
.text-live { color: var(--qubit-violet); }

/* Anchors the "Live · streaming" badge to a fixed spot in the card's
   top-right corner (.ticker-card is already position: relative),
   independent of the flex flow entirely — confirmed live 2026-08-10
   that leaving it as a normal flex sibling of a now-taller
   .mp-ticker-wrap (ticker + price stacked) let .mp-head's flex-wrap
   push it down onto its own line once the wrap's total width got
   too tight, instead of staying pinned in the corner. .mp-head's
   inline padding-right reserves room so ticker/price text never runs
   under it. */
.mp-live-corner { position: absolute; top: 20px; right: 20px; }

.mp-subline {
  font-size: 12.5px;
  color: var(--muted-drift);
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.mp-stats { display: flex; flex-direction: column; gap: 7px; margin-bottom: 14px; }
.mp-stat-row { display: flex; align-items: baseline; justify-content: space-between; font-size: 13.5px; gap: 12px; }
.mp-stat-label { color: var(--muted-drift); }
.mp-stat-value { font-weight: 500; text-align: right; }

/* Gain/loss-style labels (Gain/loss, Open P&L, Today's P&L) carry
   .text-positive/.text-negative in markup so the same class list also
   colors the adjacent VALUE span, but the label text itself should stay
   neutral white — only the number should say "up" or "down", not the
   label naming it. Higher specificity than .mp-stat-label alone so this
   wins over its muted-grey default regardless of source order. */
.mp-stat-label.text-positive, .mp-stat-label.text-negative { color: var(--signal-white); }

.mp-actions { padding-top: 12px; border-top: 1px solid var(--border-faint); }

/* .mp-actions's padding-top/border-top above was the divider for the
   OLD plain-text action-link row. Every .mp-actions element now also
   has .seg-bar (added 2026-08-14), which draws its own full border
   around the row — .mp-actions's leftover padding-top became a dead
   12px gap trapped INSIDE .seg-bar's border with no matching
   padding-bottom, since .seg-bar itself sets no container-level
   padding (only its child buttons are padded). Confirmed live: pages
   whose bar never had .mp-actions (watchlist.php) never showed this
   gap. Zeroed here rather than editing .mp-actions itself, since nothing
   in this app uses .mp-actions without .seg-bar. */
.mp-actions.seg-bar { padding-top: 0; border-top: none; }

/* Set target/stop panel floats above the card (.ticker-card is already
   position: relative) instead of expanding inline -- an inline expand
   grew the card taller, which in a CSS grid pushes every row below it
   down and can stretch sibling cards in the same row too. Positioned
   out of normal flow so opening it never reflows the rest of the page. */
/* Site-styled confirm()/prompt()/alert() replacement — see
   assets/js/confirm-dialog.js. position:fixed + appended inside
   .app-shell (not a card), so this can't fall into the same "positioned
   ancestor with z-index:auto" stacking trap .mp-target-panel below hit
   — no ancestor here competes with it for paint order. z-index is set
   well above this file's other known high values (999-1200, mobile
   nav/sticky elements) since a confirmation must always win.

   Styled to match the app-shell's "Observatory" card language
   (dashboard-skin.css's .app-shell .panel/.app-sidebar — glass panel,
   cyan gradient sheen, glowing cyan edge) rather than plain flat colors
   — requested 2026-08-14 ("make it look flashy, match the cards"). Uses
   the SAME --panel-raised/--border-soft/--signal-white/--radius-lg
   tokens the real cards use (not hand-picked colors that could drift
   out of sync with them) — this only resolves to the right cyan-branded
   values because confirm-dialog.js appends the dialog inside .app-shell
   specifically so it's actually inside that token scope, not a sibling
   of it. */
.confirm-dialog-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 7, 11, 0.78);
  backdrop-filter: blur(6px) saturate(1.1);
  -webkit-backdrop-filter: blur(6px) saturate(1.1);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  padding: 20px;
}

.confirm-dialog-overlay.is-open { display: flex; }

.confirm-dialog {
  position: relative;
  background: linear-gradient(160deg, rgba(63, 224, 197, 0.08), transparent 50%), var(--panel-raised);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border: 1px solid rgba(63, 224, 197, 0.35);
  border-radius: var(--radius-lg);
  padding: 28px 28px 26px;
  max-width: 400px;
  width: 100%;
  box-shadow:
    0 24px 70px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(63, 224, 197, 0.08),
    0 0 70px rgba(63, 224, 197, 0.16);
  animation: confirm-dialog-in 0.25s var(--ease);
  overflow: hidden;
}

/* Same fading edge-light motif as .app-sidebar::after, borrowed for a
   top "instrument" accent instead of a side one. */
.confirm-dialog::before {
  content: '';
  position: absolute;
  top: 0;
  left: 10%;
  right: 10%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--qubit-cyan), transparent);
}

@keyframes confirm-dialog-in {
  from { opacity: 0; transform: scale(0.94) translateY(6px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .confirm-dialog { animation: none; }
}

.confirm-dialog-message {
  color: var(--signal-white);
  font-size: 14.5px;
  line-height: 1.5;
  margin: 0 0 20px;
}

/* Base text-input styling (background/border/padding/color) already
   comes from the sitewide `input:not(...)` rule above — this only adds
   the width/spacing that rule leaves to `.field` elsewhere, since this
   input isn't inside one. Shown only for window.appPrompt() calls; the
   declarative data-confirm flow never displays it. */
.confirm-dialog-input {
  display: block;
  width: 100%;
  margin: 0 0 20px;
}

.confirm-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.mp-target-panel {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: 8px;
  z-index: 30;
  /* Deliberately NOT var(--panel-raised) here — dashboard-skin.css
     overrides that token to a translucent rgba(20,26,38,0.66) for
     .app-shell's glass look, which is fine for normal in-flow .panel
     cards sitting on the page's own background, but this panel is
     absolutely positioned and floats outside its card's bounds, often
     over unrelated page content below it (e.g. the footer, when opened
     on the last card in a grid) — a translucent background there reads
     as a broken/see-through dropdown. This is the opaque version of
     that SAME app-shell navy (rgb(20,26,38), alpha stripped) — this
     class only ever renders inside .app-shell (paper-trading.php,
     crypto-paper-trading.php), never on a marketing page, so matching
     THIS context's tone is correct; style.css's own non-app-shell
     --panel-raised (#1e1e24) is a different, warmer grey from a
     different palette and visibly clashed here — confirmed live
     2026-08-14. */
  background: rgb(20, 26, 38);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 16px 20px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}

/* Page-level market-session badge (portfolio.php, watchlist.php, and
   chart.php's ticker header) — one per page, not per-row.
   Shows which of five sessions is active right now and recolors/
   relabels itself automatically as the day crosses each boundary — see
   /assets/js/market-session.js, the shared client-side session clock
   this reads from. */
.session-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  border: 1px solid;
}
.session-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 6px currentColor; }

/* Moon+stars icon, same as chart.php's overnight badge — present in
   every .session-badge's markup but hidden by default; only the
   overnight state shows it (and hides the plain dot in its place). No
   price/change here, unlike chart.php's badge — this one is
   page-level (portfolio.php/watchlist.php), not tied to one ticker. */
.session-badge .icon { width: 13px; height: 13px; flex-shrink: 0; display: none; }
.session-badge-overnight .icon { display: inline-flex; }
.session-badge-overnight .dot { display: none; }

.session-badge-regular { color: var(--qubit-green); border-color: rgba(22, 163, 74, 0.35); background: rgba(22, 163, 74, 0.08); }
.session-badge-regular .dot { animation: live-pulse 2s ease-in-out infinite; }

.session-badge-pre_market,
.session-badge-after_hours { color: #fb923c; border-color: rgba(251, 146, 60, 0.35); background: rgba(251, 146, 60, 0.08); }

.session-badge-overnight { color: #818cf8; border-color: rgba(129, 140, 248, 0.35); background: rgba(129, 140, 248, 0.08); }

.session-badge-closed { color: var(--muted-drift); border-color: var(--border-faint); background: transparent; }
.session-badge-closed .dot { box-shadow: none; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid;
}

.badge-position {
  color: var(--signal-white);
  border-color: rgba(63, 224, 197, 0.35);
  background: rgba(63, 224, 197, 0.08);
}

.badge-watch {
  color: var(--muted-drift);
  border-color: var(--border-faint);
  background: rgba(255, 255, 255, 0.02);
}

/* Admin Member Usage: several accounts from one sign-up network. */
.badge-warn {
  color: var(--warn, #d9a13c);
  border-color: rgba(217, 161, 60, 0.4);
  background: rgba(217, 161, 60, 0.08);
  text-decoration: none;
}

/* Markets (Polymarket) category filter pills — wraps to fit every
   category in view at once rather than a scrolling row. */
.market-category-pill {
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease), background 0.15s var(--ease);
}

.market-category-pill:hover {
  border-color: var(--qubit-cyan);
  color: var(--qubit-green);
}

/* Markets (Polymarket) Buy Yes/No buttons — .btn's white-space: nowrap
   plus generous default padding means flex:1 alone doesn't stop long
   text ("Buy No 100¢") from pushing the button past the card's edge
   (flex children don't shrink below their content's natural width
   unless min-width is overridden). Tighter, compact variant scoped to
   just these buttons rather than changing .btn/.btn-sm globally. */
.market-buy-btn {
  min-width: 0;
  padding: 10px 8px;
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.market-category-count {
  opacity: 0.6;
  font-size: 10px;
}

.row-actions { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.row-actions > * { white-space: nowrap; }

.link-danger {
  color: var(--collapse-coral);
  font-size: 13px;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}
/* text-decoration, not font-weight — bolding on hover changes the
   text's rendered width, which inside a tight flex-wrap row (see
   .row-actions) can push the link just past the available line width,
   wrapping it to a new line, moving it out from under the cursor,
   un-hovering it, snapping back, re-hovering — a fast flicker loop
   confirmed live 2026-08-10 on crypto-paper-trading.php's narrow
   cards. Underline changes zero layout, same safe pattern
   .link-success:hover already uses. */
.link-danger:hover { text-decoration: underline; }

.link-muted { color: var(--muted-drift); font-size: 13px; }
.link-muted:hover { color: var(--signal-white); }

.link-success { color: var(--qubit-green); font-size: 13px; }
.link-success:hover { text-decoration: underline; }

/* Parallel to .link-danger/.link-muted/.link-success — a fourth
   semantic slot for an exit/sell action (added 2026-08-14 for the
   .seg-bar action row below). Amber rather than green/red: it's not
   a destructive action like Remove/Cancel, but it also shouldn't read
   as the same "safe navigation" green as Chart/Edit. */
.link-warn {
  color: var(--warn, #d9a13c);
  font-size: 13px;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}
.link-warn:hover { text-decoration: underline; }

button.link-muted {
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}

button.link-success {
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}

/* Card action row, as a joined segmented bar instead of loose text
   links — added 2026-08-14 ("Chart Sell Set target/stop Remove" was
   easy to miss, no clickable affordance). Add this class alongside the
   existing .row-actions/.mp-actions on the SAME element; every
   .link-success/.link-muted/.link-warn/.link-danger button or anchor
   already inside it is restyled into a colored segment automatically —
   no markup changes needed beyond the wrapper class (and swapping the
   affected Sell/Close buttons from .link-muted to the new .link-warn
   above). Scoped under .seg-bar so .link-success etc. keep their
   original plain-text look everywhere else on the site (marketing
   footers, chart.php's Earnings Tone link, and other places that never
   opted into this component).

   Forms wrapping a segment's <button> (Sell/Remove/Cancel — need a real
   <form> for the POST) get display:contents so the browser treats the
   form as transparent for layout purposes and the button inside becomes
   a direct flex child of .seg-bar, sized/bordered exactly like its
   sibling segments. */
.seg-bar {
  display: flex;
  width: 100%;
  gap: 0;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.seg-bar form { display: contents; }

.seg-bar > a,
.seg-bar > button,
.seg-bar > form > button {
  flex: 1;
  text-align: center;
  padding: 9px 8px;
  font-size: 12.5px;
  font-weight: 600;
  font-family: var(--font-body);
  line-height: 1;
  background: rgba(255, 255, 255, 0.02);
  border: none;
  border-right: 1px solid var(--border-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}

.seg-bar > a:last-child,
.seg-bar > button:last-child,
.seg-bar > form:last-child > button {
  border-right: none;
}

.seg-bar .link-success { color: var(--qubit-cyan); }
.seg-bar .link-success:hover { background: rgba(63, 224, 197, 0.14); text-decoration: none; }

/* "Add to Portfolio" (watchlist.php only) -- overrides .seg-bar's
   usual cyan-for-nav/info color with the site's real green accent,
   since this action's destination (adding a real position) reads
   better as the same green used for confirm/buy actions elsewhere
   than the nav-link cyan every other Chart/Edit segment uses. */
.seg-bar .link-add-portfolio { color: var(--qubit-green); }
.seg-bar .link-add-portfolio:hover { background: rgba(var(--qubit-green-rgb), 0.14); text-decoration: none; }

/* Chart/Edit split (2026-08-14) -- previously both shared plain
   .link-success (cyan). Chart now reads as the same green "go look at
   this" accent as .link-add-portfolio; Edit switches to white to read
   as an in-page toggle, matching .link-muted's color exactly (Set
   target/stop already uses .link-muted for the same reason). Both
   still carry .link-success alongside these so the existing
   :has(> button.link-muted) flex-weighting selectors keep matching. */
.seg-bar .link-chart { color: var(--qubit-green); }
.seg-bar .link-chart:hover { background: rgba(var(--qubit-green-rgb), 0.14); text-decoration: none; }

.seg-bar .link-edit { color: var(--signal-white); }
.seg-bar .link-edit:hover { background: rgba(255, 255, 255, 0.08); text-decoration: none; }

.seg-bar .link-muted { color: var(--signal-white); }
.seg-bar .link-muted:hover { background: rgba(255, 255, 255, 0.08); color: var(--signal-white); }

.seg-bar .link-warn:hover { background: rgba(217, 161, 60, 0.16); text-decoration: none; }

.seg-bar .link-danger:hover { background: rgba(185, 28, 28, 0.16); text-decoration: none; }

/* "Set target/stop" (.link-muted, the only long label ever in a
   .seg-bar) truncated under the equal 4-way flex:1 split above --
   "Chart"/"Sell"/"Remove" had leftover space while it ran out. Gives
   it proportionally more of the row, :has()-scoped so this only
   applies to bars that actually contain it (paper-trading.php stock/
   option, crypto-paper-trading.php) -- plain Chart/Edit/Remove bars
   (portfolio.php, crypto-portfolio.php) are unaffected. "Remove" (6
   chars) needs more than "Chart"/"Sell" (5/4 chars) -- an earlier pass
   here reduced all three short siblings equally and ended up
   truncating "Remove" instead; left at its original flex:1 baseline
   this time since it was never a problem before. Confirmed live
   2026-08-14. */
.seg-bar:has(> button.link-muted) > a.link-success { flex: 0.8; }
.seg-bar:has(> button.link-muted) > form > button.link-warn { flex: 0.7; }
.seg-bar > button.link-muted { flex: 1.7; }

/* "Details" (portfolio.php's own .link-muted segment, added 2026-08-23
   for the ticker-detail modal) is a much shorter label than "Set
   target/stop" -- reset back to an even flex:1 share rather than
   inheriting that longer label's generous flex:1.7, which would leave
   Details looking oversized/empty relative to its text. Same
   specificity as the flex:1.7 rule above (two classes + one type each)
   -- wins on source order since it comes after, not specificity; keep
   it below that rule if either is ever edited. */
.seg-bar > button.js-open-ticker-detail { flex: 1; }

.form-inline-card { padding: 24px; margin-bottom: 24px; }

.form-row-3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr auto;
  gap: 14px;
  align-items: start;
}

@media (max-width: 700px) {
  .form-row-3 { grid-template-columns: 1fr; }
  .data-table { display: block; overflow-x: auto; white-space: nowrap; }
}

/* ---- News --------------------------------------------------------------- */

.news-list { display: flex; flex-direction: column; }

.news-item {
  display: block;
  padding: 16px 0;
  border-bottom: 1px solid var(--border-faint);
}
.news-item:last-child { border-bottom: none; }

.news-item .headline {
  font-size: 15px;
  font-weight: 600;
  color: var(--signal-white);
  margin-bottom: 4px;
  display: block;
}
.news-item .headline:hover { color: var(--qubit-green); }

.news-item .meta {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--muted-drift);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.news-item .summary {
  font-size: 13.5px;
  color: var(--muted-drift);
  margin-top: 6px;
  margin-bottom: 0;
}

/* Was scoped under a .ticker-news-row wrapper that no longer exists in
   news.php's markup (removed in an earlier refactor) — .ticker-tag
   itself is still live (news.php's ticker-digest-head), so it was
   rendering fully unstyled until rescoped here. */
.ticker-digest-head .ticker-tag {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--qubit-green);
  min-width: 56px;
}

/* ---- Chart page ----------------------------------------------------------- */

.chart-header {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 20px 24px;
  margin-bottom: 24px;
}

.chart-header-ticker-group {
  display: flex;
  align-items: center;
  gap: 10px;
}

.chart-header .ticker-symbol {
  font-family: var(--font-mono);
  font-size: 32px;
  font-weight: 700;
}

.chart-header-price-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

.chart-header .price { font-family: var(--font-mono); font-size: 22px; }

/* position:relative makes this the containing block for the toolbar
   dropdowns' position:absolute panels (see setupToolbarDropdown() in
   chart.php/crypto-chart.php) — without it, the browser would anchor
   them to whichever ancestor IS positioned instead, silently breaking
   their computed top/right offsets. */
.chart-container { padding: 24px; margin-bottom: 24px; position: relative; }

.chart-canvas-wrap { position: relative; height: 340px; }

/* Desktop's chart.php/crypto-chart.php gets more vertical room —
   KLineChart's sub-pane indicators (RSI/MACD/VOL/etc, see
   .indicator-picker-panel below) split this same container height
   among the main candle pane and each active sub-pane, so a fixed
   340px (fine for a candles-only chart) gets cramped fast once 2-3
   indicators are active. Mobile's simpler static instance keeps the
   original 340px — it has no indicator picker. */
@media (min-width: 860px) {
  .chart-canvas-wrap.js-desktop-chart { height: 520px; }
}

/* ---- 3D price/volume/time view (chart.php/crypto-chart.php, 2026-08-24)
   — Plotly (gl3d partial build) rendered into this div in place of the
   KLineChart canvas when the 3D toggle is active. Same height as the
   2D canvas it swaps with (340px mobile-width fallback, 520px desktop)
   so the surrounding panel doesn't jump size when toggling. Flex
   column so chart.php's Pro-only sub-tab bar (Price/Volume/Time vs.
   Volatility Surface — crypto-chart.php has no sub-tabs, no options
   exist there) takes its own height and #chart3dPlot fills the rest —
   Plotly needs an actual plot div distinct from this wrapper once a
   sub-tab bar sits above it. */
.chart3d-wrap { height: 340px; display: flex; flex-direction: column; }
@media (min-width: 860px) {
  .chart3d-wrap { height: 520px; }
}
.chart3d-plot { flex: 1; min-height: 0; }

.chart3d-subtabs {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
  flex-shrink: 0;
}
.chart3d-subtab {
  background: none;
  border: 1px solid var(--border-faint);
  color: var(--muted-drift);
  font-family: inherit;
  font-size: 12px;
  padding: 5px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.chart3d-subtab:hover { color: var(--signal-white); }
.chart3d-subtab.is-active { color: var(--qubit-cyan); border-color: var(--qubit-cyan); }

/* Shown inside #chart3dPlot while the volatility surface is fetching,
   or when there wasn't enough live option pricing to build one. */
.chart3d-status {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--muted-drift);
  font-size: 13px;
  text-align: center;
  padding: 0 24px;
}

/* ---- 3D portfolio/watchlist heatmap (portfolio-heatmap.php, 2026-08-24)
   -------------------------------------------------------------------- */
.heatmap3d-wrap { height: 460px; display: flex; flex-direction: column; }
@media (min-width: 860px) {
  .heatmap3d-wrap { height: 640px; }
}
.heatmap3d-plot { flex: 1; min-height: 0; }
.heatmap3d-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }

/* Two-way chip<->box hover link (see wireHoverLink() in
   portfolio-heatmap.php) — glows whichever chip's box is currently
   hovered, in either direction. */
.badge.is-hover-linked {
  box-shadow: 0 0 0 1px currentColor, 0 0 10px 1px currentColor;
}

/* ---- Chart toolbar dropdowns (chart.php/crypto-chart.php desktop) —
   shared by the Indicators picker (2026-08-24), and the Draw tools +
   Candle style pickers added the same day. Generic name since all
   three reuse this exact panel look; was .indicator-picker-* before
   the latter two existed. -------------------------------------------
   z-index alone on .chart-dropdown-panel isn't enough — it only wins
   against siblings WITHIN this wrap's own stacking context.
   .chart-canvas-wrap (KLineChart's canvas) is a later sibling of this
   wrap's ANCESTOR (.chart-toolbar), also position:relative with no
   z-index of its own, so without an explicit z-index HERE too, it
   paints on top purely by DOM order — confirmed live, the chart's
   price-axis labels showed through the open dropdown. In practice
   every one of these panels is also detached to <body> as
   position:fixed by JS anyway (see setupToolbarDropdown() in both
   files) — z-index alone wasn't reliable even with this wrap-level
   fix, KLineChart's own canvas layers still won sometimes. This
   wrap-level z-index stays as defense in depth for the brief moment
   before JS detaches the panel. */
.chart-dropdown-wrap { position: relative; z-index: 30; }

.chart-dropdown-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  width: 260px;
  max-height: 380px;
  overflow-y: auto;
  background: var(--panel-raised);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 10px 0;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}

.chart-dropdown-group-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted-drift);
  padding: 8px 14px 6px;
}

.chart-dropdown-item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 14px;
  font-size: 13px;
  cursor: pointer;
  user-select: none;
  background: none;
  border: none;
  width: 100%;
  text-align: left;
  color: var(--signal-white);
  font-family: inherit;
}

.chart-dropdown-item:hover { background: var(--panel); }
.chart-dropdown-item input { accent-color: var(--qubit-cyan); flex-shrink: 0; }
.chart-dropdown-item.is-active { color: var(--qubit-cyan); }

.chart-dropdown-divider { height: 1px; background: var(--border-faint); margin: 8px 0; }

.chart-toolbar-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border-faint);
  color: var(--muted-drift);
  border-radius: var(--radius-sm);
  width: 30px;
  height: 30px;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
  flex-shrink: 0;
}
.chart-toolbar-icon-btn:hover { color: var(--signal-white); border-color: var(--qubit-cyan); }
.chart-toolbar-icon-btn svg { width: 14px; height: 14px; }
.chart-toolbar-icon-btn.is-active { color: var(--qubit-cyan); border-color: var(--qubit-cyan); }

/* ---- Full-screen chart mode (2026-08-24) — toggles the real browser
   Fullscreen API on .chart-container itself (not just the canvas), so
   the toolbar and its dropdowns stay usable while fullscreen. Sized
   via :fullscreen + flex rather than a JS-toggled class/calc'd height
   — this fires whenever the browser is ACTUALLY fullscreen for this
   element, so it can't drift out of sync if the user exits via Esc
   instead of the toggle button. */
.chart-container:fullscreen {
  display: flex;
  flex-direction: column;
  background: var(--void);
  padding: 16px;
  margin-bottom: 0;
}
.chart-container:fullscreen .chart-canvas-wrap,
.chart-container:fullscreen .chart3d-wrap { flex: 1; height: auto; }

/* ---- Market History search results ----------------------------------------
   Sits beside the input (normal document flow), not an absolutely-positioned
   overlay below it — a floated dropdown had no way to respect the layout of
   whatever panel came after it and visually overlapped it. */

.mh-search-results {
  flex: 1 1 260px;
  min-width: 220px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--panel-raised);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
}

.mh-search-item {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--border-soft);
}

.mh-search-item:last-child { border-bottom: none; }
.mh-search-item:hover { background: var(--panel); }

.mh-search-item-symbol { font-family: var(--font-mono); font-weight: 700; font-size: 13.5px; }
.mh-search-item-name { flex: 1; font-size: 13px; color: var(--muted-drift); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mh-search-item-type { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; color: var(--muted-drift); }

.mh-search-empty { padding: 12px 14px; font-size: 13px; color: var(--muted-drift); }

.timeframe-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.chart-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.timeframe-tab {
  appearance: none;
  -webkit-appearance: none;
  padding: 6px 14px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 500;
  color: #c9c5b8;
  background: transparent;
  border: 1px solid var(--border-faint);
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease), background 0.15s var(--ease);
}

.timeframe-tab:hover { color: var(--signal-white); border-color: var(--border-soft); }

.timeframe-tab.active {
  color: var(--signal-white);
  background: rgba(var(--qubit-green-rgb), 0.12);
  border-color: rgba(var(--qubit-green-rgb), 0.35);
  font-weight: 700;
}

.ticker-card {
  display: block;
  padding: 20px;
  position: relative;
  /* Explicit (not auto) so this card forms its own real stacking
     context — without it, a positioned-but-z-index:auto ancestor later
     in the DOM (e.g. <footer class="app-footer">, also position:relative)
     wins the tiebreak by DOM order and paints its content ON TOP of
     this card's own z-indexed children (.mp-target-panel,
     .mp-live-corner), regardless of how high THEIR z-index is set —
     confirmed live 2026-08-14: the "Set target/stop" panel on the last
     row of a grid rendered underneath the footer's ticker strip until
     this was added. See .mp-target-panel below for the other half of
     that fix (it also needs a guaranteed-opaque background, since
     dashboard-skin.css's --panel-raised is intentionally translucent).
  */
  z-index: 1;
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}

/* Markets.php's inline stake form grows a card's real height beyond
   what the grid row above it laid out for, so the expanded card's own
   content (the Stake $ input / Place button) can paint underneath the
   next row's card instead of on top of it. Elevating the open card's
   stacking context fixes this regardless of the underlying grid-row
   sizing quirk. See togglePolyStake() in markets.php. */
.js-poly-card.is-stake-open {
  z-index: 30;
}

.card-refresh-btn {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border-faint);
  border-radius: 50%;
  color: var(--muted-drift);
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
  z-index: 2;
}

.card-refresh-btn:hover { color: var(--signal-white); border-color: var(--qubit-cyan); }

.card-refresh-btn svg { width: 13px; height: 13px; }

.card-refresh-btn.is-refreshing svg { animation: spin 0.7s linear infinite; }

.ticker-card:hover {
  border-color: var(--qubit-cyan);
  transform: translateY(-2px);
}

.ticker-card-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.card-timeframes {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border-faint);
}

.timeframe-tab-mini {
  padding: 3px 8px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--muted-drift);
  border: 1px solid var(--border-faint);
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}

.timeframe-tab-mini:hover {
  color: var(--signal-white);
  background: rgba(var(--qubit-green-rgb), 0.12);
  border-color: rgba(var(--qubit-green-rgb), 0.35);
}

.sparkline-wrap {
  margin-top: 12px;
  line-height: 0;
}

/* Bordered container wrapping a live 1s area chart + a date/time stamp
   — portfolio.php's stock position cards (2026-08-24 redesign, see
   CHANGELOG.md). The chart itself (.area-chart, rendered by
   renderAreaChartSvg() in portfolio.php's own script, ported from
   crypto-watchlist.php's identical live sparkline) already draws its
   own highlighted current-price tag, so no separate price element is
   needed inside this container — the gold .js-price lives back up in
   .mp-head instead. .sparkline-wrap's own margin-top is zeroed here
   since this container supplies its own spacing instead. */
.mp-chart-container {
  margin-top: 12px;
  padding: 10px 12px 8px;
  border: 1px solid var(--border-faint);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.015);
}
.mp-chart-container .sparkline-wrap { margin-top: 0; }
.mp-chart-meta {
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px solid var(--border-faint);
  display: flex;
  justify-content: space-between;
  font-size: 10.5px;
  color: var(--muted-drift);
}

.sentiment-card-section {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border-faint);
  text-align: center;
}

/* Matches .eyebrow/.eyebrow-live's look exactly (mono, uppercase,
   green, pulsing dot) rather than duplicating that markup on all 4
   pages this class appears on (watchlist.php, charts.php,
   crypto-watchlist.php, crypto-chart.php) -- one shared class, one
   place to keep it in sync. text-align: left overrides the inherited
   center alignment from the parent .sentiment-card-section (which
   centers the gauge/badges below it) -- safe here since .sentiment-label
   is block-level, so this only affects its own text, not the section's
   layout. */
.sentiment-label {
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--qubit-green);
  text-align: left;
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.sentiment-label::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--qubit-green);
  box-shadow: 0 0 8px var(--qubit-green);
  animation: live-pulse 2s ease-in-out infinite;
  flex-shrink: 0;
}

.sentiment-bar {
  display: flex;
  gap: 3px;
  height: 7px;
}

.sentiment-seg {
  flex: 1 1 0;
  height: 100%;
  border-radius: 3px;
  background: var(--seg-color);
  transition: opacity 0.2s ease;
}

.sentiment-seg-lit { opacity: 1; }
.sentiment-seg-dim { opacity: 0.2; }

.sentiment-seg-current {
  box-shadow: 0 0 9px 1px var(--seg-color), 0 0 0 1px rgba(255, 255, 255, 0.35) inset;
}

.sentiment-zones {
  display: flex;
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sentiment-zone {
  flex: 1;
  color: var(--muted-drift);
  opacity: 0.45;
  font-weight: 500;
}

.sentiment-zone:nth-child(1) { text-align: left; }
.sentiment-zone:nth-child(2) { text-align: center; }
.sentiment-zone:nth-child(3) { text-align: right; }

.sentiment-zone-active {
  opacity: 1;
  font-weight: 700;
  text-shadow: 0 0 10px currentColor;
}

.sentiment-value {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin-top: 8px;
}

.sentiment-caption {
  margin-top: 8px;
  font-size: 11px;
  color: var(--muted-drift);
}

.sentiment-breakdown {
  margin-top: 4px;
  font-size: 11px;
  color: var(--muted-drift);
}

/* Extends a few px into .ticker-card's own 20px side padding (rather
   than shrinking that padding on .ticker-card itself, which is shared
   by portfolio/paper-trading/crypto/charts/markets cards too) so all 3
   badges have room to stay on one line even once a busy ticker's counts
   go double-digit (e.g. "69 Bullish") at their regular size. Confirmed
   live 2026-08-14 that .ticker-card has no overflow:hidden, so this
   doesn't get clipped. */
/* margin-bottom added on top of the section's own 18px header spacing
   (.eyebrow's margin-top, matching "Technical signals"' identical gap
   elsewhere on the same card) -- the colored pill badges read as more
   crowded than plain text at the same pixel gap, so this section gets
   a bit more room. Shared component, so this applies everywhere
   render_sentiment_count_badges() renders. */
.sentiment-counts {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px;
  margin: 8px -6px 10px;
}

.sentiment-count-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0;
  padding: 2px 6px;
  border-radius: 999px;
  border: 1px solid;
  white-space: nowrap;
}

.sentiment-count-bullish {
  color: var(--qubit-green);
  border-color: rgba(var(--qubit-green-rgb), 0.35);
  background: rgba(var(--qubit-green-rgb), 0.1);
}

.sentiment-count-bearish {
  color: var(--collapse-coral);
  border-color: rgba(var(--collapse-coral-rgb), 0.35);
  background: rgba(var(--collapse-coral-rgb), 0.1);
}

.sentiment-count-neutral {
  color: var(--muted-drift);
  border-color: var(--border-faint);
  background: rgba(255, 255, 255, 0.03);
}

.sentiment-unavailable {
  padding: 18px 0 6px;
  color: var(--muted-drift);
  font-size: 12px;
}

/* ---- Contact page ------------------------------------------------------- */

.contact-section { padding: 60px 0 90px; }

.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1.25fr;
  gap: 48px;
  align-items: start;
}

.contact-info h1 { font-size: 32px; margin-bottom: 14px; }
.contact-info .lead { margin-bottom: 0; }

.contact-trust {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 34px;
}

.contact-trust-item {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  font-size: 13.5px;
  color: var(--muted-drift);
}

.contact-trust-item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--qubit-cyan);
  margin-top: 2px;
}

/* Honeypot — off-screen rather than display:none, since some bots skip
   fields with display:none but still fill in anything visually hidden
   by being positioned off-canvas. Real visitors never see or reach it. */
.hp-field {
  position: absolute;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.g-recaptcha { margin-bottom: 20px; }

.plan-card {
  padding: 26px 24px;
  text-align: center;
  position: relative;
  overflow: hidden;
}

/* Same quantum-field/glow treatment as .feature-vertical, scaled down to
   fit a compact card instead of a full banner. */
.plan-card .quantum-glow-a,
.plan-card .quantum-glow-b {
  width: 160px;
  height: 160px;
  filter: blur(36px);
}
.plan-card .quantum-glow-a { top: -40px; left: -30px; }
.plan-card .quantum-glow-b { bottom: -50px; right: -30px; }

.plan-card-content { position: relative; z-index: 1; }

.plan-price {
  font-family: var(--font-mono);
  font-size: 34px;
  font-weight: 700;
  color: var(--signal-white);
}

.plan-glyph {
  width: 128px;
  height: 128px;
  border-radius: 26px;
  background: linear-gradient(135deg, rgba(63, 224, 197, 0.16), rgba(var(--qubit-violet-rgb), 0.16));
  border: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 18px;
}

.plan-glyph svg { width: 64px; height: 64px; }

/* ---- Signals / predictions ------------------------------------------ */

.signal-disclaimer {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 14px 18px;
  font-size: 13.5px;
  color: var(--muted-drift);
  margin-bottom: 24px;
  max-width: 70ch;
}

.consensus-bar {
  display: flex;
  height: 10px;
  border-radius: 999px;
  overflow: hidden;
  margin-bottom: 10px;
  background: var(--void);
}

.consensus-bar span { display: block; height: 100%; }

.consensus-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  font-size: 12px;
  font-family: var(--font-mono);
  color: var(--muted-drift);
}

.consensus-legend .dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 5px;
}

.signal-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-faint);
  font-size: 13.5px;
}

.signal-row:last-child { border-bottom: none; }

.signal-row .label { color: var(--muted-drift); flex-shrink: 0; }

/* font-size dropped from the row's own 13.5px 2026-08-15 — "20d / 50d
   avg" (label) plus its dollar-value pair routinely exceeded a
   ticker-card's actual signal-row width (~195px) combined, pushing
   both onto a second line mid-number. white-space/overflow/ellipsis
   is a safety net for the rare 4-digit-dollar ticker this size still
   can't fit, rather than wrapping again. */
.signal-row .value {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.signal-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid;
}

.signal-badge-bullish { color: var(--qubit-green); border-color: rgba(22, 163, 74, 0.35); background: rgba(22, 163, 74, 0.08); }
.signal-badge-bearish { color: var(--collapse-coral); border-color: rgba(var(--collapse-coral-rgb), 0.35); background: rgba(var(--collapse-coral-rgb), 0.08); }
.signal-badge-neutral { color: var(--muted-drift); border-color: var(--border-faint); background: rgba(255, 255, 255, 0.02); }

.btn-refresh {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: transparent;
  border: 1px solid var(--border-faint);
  color: var(--muted-drift);
  border-radius: var(--radius-sm);
  padding: 6px 13px;
  font-family: var(--font-mono);
  font-size: 12px;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
  flex-shrink: 0;
}

.btn-refresh:hover { color: var(--signal-white); border-color: var(--qubit-cyan); }

.btn-refresh svg { width: 13px; height: 13px; }

.btn-refresh.is-refreshing svg { animation: spin 0.7s linear infinite; }

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ---- Live badge ----------------------------------------------------------- */

.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted-drift);
  white-space: nowrap;
}

.live-badge .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--qubit-green);
  box-shadow: 0 0 6px var(--qubit-green);
  animation: live-pulse 2s ease-in-out infinite;
}

@keyframes live-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* Muted, static dot for cards/rows whose live connection hasn't
   confirmed yet (or has dropped) — visually distinct from the green
   pulsing dot once streaming is actually confirmed. */
.live-badge.is-pending .dot {
  background: var(--muted-drift);
  box-shadow: none;
  animation: none;
}

/* =========================================================================
   App footer (dashboard, portfolio, charts, etc. — the logged-in shell)
   ========================================================================= */

.app-footer {
  position: relative;
  margin-top: 56px;
  padding-top: 36px;
  overflow: hidden;
}

.app-footer::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--qubit-cyan) 20%, var(--qubit-violet) 50%, var(--qubit-cyan) 80%, transparent);
  background-size: 200% 100%;
  animation: footer-shimmer 6s linear infinite;
  opacity: 0.55;
}

@keyframes footer-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.app-footer-top {
  display: grid;
  grid-template-columns: minmax(220px, 0.8fr) 2.2fr;
  gap: 28px;
  padding-top: 26px;
  padding-bottom: 32px;
  align-items: start;
}

.app-footer-brand .brand { margin-bottom: 14px; }

.app-footer-brand p {
  color: var(--muted-drift);
  font-size: 13px;
  max-width: 36ch;
  margin-bottom: 18px;
}

.footer-nudge { margin-top: 14px; }
.footer-nudge a { color: var(--link-accent); font-size: 12.5px; text-decoration: none; }
.footer-nudge a:hover { text-decoration: underline; }

.app-footer-heading {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-drift);
  margin-bottom: 14px;
}

/* ---- Footer widgets (replaced the redundant nav columns, 2026-08-11) --- */

.footer-ticker-strip {
  overflow: hidden;
  padding: 14px 0 22px;
  border-bottom: 1px solid var(--border-faint);
  font-family: var(--font-mono);
  font-size: 12.5px;
  /* Explicit whole-pixel line-height. 12.5px at the default ratio computes
     to 19.375px, which made the strip 56.375px tall — so the 1px bottom
     border landed on a sub-pixel boundary and rendered soft and visibly
     misaligned against the section below it. 20px keeps the whole box on
     integer pixels. */
  line-height: 20px;
}

/* The PUBLIC marketing strip only (index.php). It sits inside .container,
   which is 1132px in a 1432px viewport, while the .hero.hero-photo below
   it breaks out to full width — so the strip's 1px bottom border stopped
   ~150px short at each end and left a visible step where the hero's edge
   carried on without it. Same breakout the hero itself uses, so the two
   edges now meet across the whole viewport.

   Scoped by id: the member footer's strip lives inside .app-footer, which
   is already full-bleed, and must NOT get this. */
#publicTickerStrip {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-inline: 24px;
}

/* Always a continuously-scrolling ticker tape (right to left), at every
   viewport width — this originally only scrolled below 700px and wrapped
   to multiple lines on desktop, but that was never actually requested;
   corrected 2026-08-12 to scroll everywhere. Both tracks render the SAME
   content back-to-back (the duplicate carries aria-hidden in the markup,
   so screen readers only ever announce one real copy) — that's what
   makes translateX(-50%) loop with no visible seam or reset. */
.footer-ticker-marquee {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  width: max-content;
  /* Gap MUST be 0 here. The animation shifts translateX(-50%), i.e. half
     the marquee — but a seamless loop needs exactly one track plus the
     seam. With a gap G on this flex container the marquee is 2W + G, so
     -50% moves W + G/2 and the tape snaps back G/2 every cycle (measured:
     11px of an 22px gap). The seam is now carried by the track's own
     padding-right instead, making the marquee exactly 2W and -50% exactly
     W. An earlier pass tried to fix this stutter by matching the two gap
     values; that was the wrong theory — equal gaps do not cancel the
     G/2 error. */
  gap: 0;
  /* Duration scales with item count (was 26s for 7 items, now 40s for
     11) so the scroll SPEED stays roughly constant rather than the
     track getting proportionally faster every time a ticker is added —
     this animates a fixed -50% of the track's own width regardless of
     how long that track is, so a fixed duration means more items =
     faster motion, not the same speed over more content. */
  animation: footer-ticker-scroll 40s linear infinite;
}
.footer-ticker-track { display: flex; align-items: center; gap: 22px; flex-shrink: 0;
  /* Carries the seam between the two duplicated tracks; must equal the
     item gap above or the spacing visibly changes at the loop point. */
  padding-right: 22px; }

.footer-ticker-item { display: inline-flex; align-items: center; gap: 7px; color: var(--muted-drift); white-space: nowrap; }
.footer-ticker-symbol { font-weight: 700; color: var(--signal-white); letter-spacing: 0.04em; }
.footer-ticker-item.is-loading .footer-ticker-value { opacity: 0.5; }
.footer-ticker-item.is-unavailable .footer-ticker-value { color: var(--muted-drift); }

@keyframes footer-ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .footer-ticker-marquee { animation: none; }
}

.footer-widget-grid { gap: 16px; align-items: stretch; }
.footer-widget-grid .panel { padding: 18px 20px; }

/* Quote + On This Day live as plain text in the brand column (not
   boxed cards) — user-requested placement, 2026-08-11. */
.footer-quote {
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--signal-white);
  font-style: italic;
  max-width: 34ch;
}

/* Market Quote + On This Day headings — the two plain-text sections in
   the brand column — read as white, not the shared muted-gray
   .app-footer-heading default (which the widget-grid cards keep). */
.app-footer-brand .app-footer-heading { color: var(--signal-white); }
.footer-quote-author {
  display: block;
  font-style: normal;
  font-size: 12px;
  color: var(--muted-drift);
  margin-bottom: 6px;
}

.footer-otd-text { font-size: 12.5px; color: var(--muted-drift); line-height: 1.6; margin: 14px 0 0; max-width: 36ch; }
.footer-otd-text .mono { color: var(--signal-white); }
.footer-otd-text .app-footer-heading { margin-bottom: 4px; }

/* Compact one-line prediction-call summary — deliberately not the full
   render_prediction_call_card() component (that's built for
   predictions-log.php's own page; reusing it here made this card far
   taller than its siblings and stretched the whole grid row). */
.footer-latest-call-summary {
  display: block;
  font-size: 14px;
  color: var(--signal-white);
  text-decoration: none;
}
.footer-latest-call-summary:hover { color: var(--link-accent); }

.app-footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
  padding: 20px 0 28px;
  border-top: 1px solid var(--border-faint);
  color: var(--muted-drift);
  font-size: 12.5px;
}


.back-to-top {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 1px solid var(--border-faint);
  border-radius: 999px;
  padding: 7px 14px;
  color: var(--muted-drift);
  font-size: 12px;
  font-family: var(--font-body);
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease), transform 0.15s var(--ease);
}

.back-to-top:hover {
  color: var(--signal-white);
  border-color: var(--qubit-cyan);
  transform: translateY(-1px);
}

@media (max-width: 900px) {
  .app-footer-top { grid-template-columns: 1fr; }
  /* Both gaps together, not just the marquee's own — the marquee's gap
     is the SEAM between the two duplicated tracks, the track's is the
     gap between items within one; these have to match or the loop
     visibly stutters right at the seam (same fix already needed once
     before, when this only applied below 700px). */
  .footer-ticker-track {
    gap: 16px;
    padding-right: 16px;
  }
}

@media (max-width: 560px) {
  .app-footer-bottom { justify-content: center; text-align: center; }
}

/* =========================================================================
   Admin panel
   Deliberately brown-forward (vs. the marketing pages' blue and the
   member app's teal) so it's visually unmistakable which area you're in.
   ========================================================================= */

.admin-shell,
.admin-login-screen {
  --qubit-violet: #b8763f;
  --qubit-violet-rgb: 184, 118, 63;
}

.admin-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--qubit-violet);
  border: 1px solid rgba(var(--qubit-violet-rgb), 0.4);
  background: rgba(var(--qubit-violet-rgb), 0.08);
  padding: 4px 11px;
  border-radius: 999px;
}

/* ---- Admin login ---------------------------------------------------- */

.admin-login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background:
    radial-gradient(ellipse 90% 60% at 50% -10%, rgba(var(--qubit-violet-rgb), 0.16) 0%, transparent 60%),
    var(--void);
}

.admin-login-card { width: 100%; max-width: 400px; }

.admin-login-card .brand { margin-bottom: 22px; }

.admin-login-card h1 { font-size: 22px; margin-bottom: 6px; }

.admin-login-card .subhead {
  color: var(--muted-drift);
  font-size: 14px;
  margin-bottom: 28px;
}

/* ---- Admin shell (sidebar + main) ------------------------------------ */

.admin-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
}

.admin-sidebar {
  border-right: 1px solid var(--border-faint);
  padding: 28px 20px;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(var(--qubit-violet-rgb), 0.1), transparent 45%), var(--deep-field);
  position: relative;
}

.admin-sidebar::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(180deg, transparent, rgba(var(--qubit-violet-rgb), 0.6), transparent);
}

.admin-sidebar .brand { margin-bottom: 6px; }

.admin-sidebar-tag {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--qubit-violet);
  margin-bottom: 34px;
}

.admin-nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 14.5px;
  color: var(--muted-drift);
  border-left: 2px solid transparent;
  margin-bottom: 4px;
  transition: color 0.15s var(--ease), background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.admin-nav-link:hover { color: var(--signal-white); background: var(--panel); }

.admin-nav-link.active {
  color: var(--signal-white);
  background: var(--panel);
  border-left-color: var(--qubit-violet);
  box-shadow: inset 0 0 16px rgba(var(--qubit-violet-rgb), 0.12);
}

.admin-nav-link .nav-icon { width: 16px; height: 16px; flex-shrink: 0; display: inline-flex; opacity: 0.8; }
.admin-nav-link .nav-icon svg { width: 100%; height: 100%; }
.admin-nav-link.active .nav-icon { opacity: 1; color: var(--qubit-violet); }

.admin-sidebar-footer { margin-top: auto; }

.admin-main { padding: 32px 40px 60px; min-width: 0; }

.admin-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 32px;
}

.admin-topbar h1 { font-size: 22px; margin: 0; }

.admin-mobile-topbar {
  display: none;
  align-items: center;
  gap: 14px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-faint);
  background: var(--deep-field);
  position: sticky;
  top: 0;
  z-index: 999;
}

.admin-menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  color: var(--signal-white);
}

.admin-menu-toggle svg { width: 20px; height: 20px; }
.admin-menu-toggle:hover { border-color: var(--qubit-violet); }

.admin-sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(6, 7, 15, 0.65);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease;
}

/* ---- Admin dashboard widgets ------------------------------------------ */

.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  margin-bottom: 28px;
}

.admin-stat-card { padding: 20px; }

.admin-stat-card .label {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-drift);
  margin-bottom: 8px;
}

.admin-stat-card .value {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 600;
}

.admin-section {
  margin-bottom: 32px;
}

.admin-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.admin-section-head h2 { font-size: 17px; margin: 0; }

.activity-row {
  display: flex;
  gap: 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-faint);
  font-size: 13.5px;
}
.activity-row:last-child { border-bottom: none; }
.activity-row .who { color: var(--signal-white); font-weight: 600; flex-shrink: 0; }
.activity-row .action { color: var(--muted-drift); flex: 1; }
.activity-row .when { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted-drift); flex-shrink: 0; white-space: nowrap; }

/* ---- Admin forms / settings groups ------------------------------------- */

.settings-group { padding: 22px 24px; margin-bottom: 20px; }

.settings-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}

.settings-group-head h3 { font-size: 15.5px; margin: 0; }
.settings-group-head p { font-size: 12.5px; margin: 4px 0 0; }

.env-field { margin-bottom: 18px; }
.env-field:last-child { margin-bottom: 0; }

.env-field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted-drift);
  margin-bottom: 4px;
}

.env-field .env-help {
  font-size: 12px;
  color: var(--muted-drift);
  margin-bottom: 8px;
}

.env-input-row { display: flex; gap: 8px; }

.env-input-row input {
  flex: 1;
  min-width: 0;
  background: var(--void);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  color: var(--signal-white);
  font-family: var(--font-mono);
  font-size: 14px;
}

.env-input-row input:focus { border-color: var(--qubit-violet); }
.env-input-row input:focus:not(:focus-visible) { outline: none; }

.env-toggle-visibility {
  flex-shrink: 0;
  width: 42px;
  background: transparent;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  color: var(--muted-drift);
}

.env-toggle-visibility:hover { color: var(--signal-white); border-color: var(--qubit-violet); }

/* ---- Plan cards (admin management view) -------------------------------- */

.admin-plan-card { padding: 20px; position: relative; }
.admin-plan-card.is-inactive { opacity: 0.55; }

.admin-plan-card .featured-tag {
  position: absolute;
  top: 16px;
  right: 16px;
}

/* =========================================================================
   Admin panel responsive
   ========================================================================= */

@media (max-width: 900px) {
  .admin-shell { grid-template-columns: 1fr; }
  .admin-main { padding: 24px 20px 40px; }
  .admin-mobile-topbar { display: flex; }

  .admin-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 280px;
    max-width: 82vw;
    z-index: 1001;
    transform: translateX(-100%);
    transition: transform 0.28s var(--ease);
    overflow-y: auto;
  }

  .admin-sidebar.is-open {
    transform: translateX(0);
    box-shadow: 0 0 60px rgba(0, 0, 0, 0.55);
  }

  .admin-sidebar-overlay { display: block; }
  .admin-sidebar-overlay.is-open { opacity: 1; visibility: visible; }
}

@media (max-width: 560px) {
  .env-input-row { flex-wrap: wrap; }
}

/* =========================================================================
   Mobile nav base styles (hidden by default; media queries below turn them
   on). These MUST come before the @media blocks — equal-specificity CSS
   rules are resolved by source order, not by which one is inside a media
   query, so a later unconditional rule would silently beat an earlier
   conditional one.
   ========================================================================= */

.app-mobile-topbar {
  display: none;
  align-items: center;
  gap: 14px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-faint);
  background: var(--deep-field);
  position: sticky;
  top: 0;
  z-index: 999;
}

/* Backdrop behind the mobile sidebar drawer. display:none by default (and
   position:fixed once shown) so it never becomes a stray grid item inside
   .app-shell on desktop. */
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(6, 7, 15, 0.65);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease;
}

.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  color: var(--signal-white);
}

.menu-toggle svg { width: 20px; height: 20px; }
.menu-toggle:hover { border-color: var(--qubit-cyan); }

/* =========================================================================
   Responsive
   ========================================================================= */

@media (max-width: 900px) {
  .auth-layout { grid-template-columns: 1fr; }
  .auth-visual { display: none; }
  .app-shell { grid-template-columns: 1fr; }
  .app-main { padding: 24px 20px 40px; }

  /* Sidebar becomes an off-canvas drawer, opened via the mobile topbar */
  .app-mobile-topbar { display: flex; }

  .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 280px;
    max-width: 82vw;
    z-index: 1001;
    transform: translateX(-100%);
    transition: transform 0.28s var(--ease);
    overflow-y: auto;
  }

  .app-sidebar.is-open {
    transform: translateX(0);
    box-shadow: 0 0 60px rgba(0, 0, 0, 0.55);
  }

  .sidebar-overlay { display: block; }
  .sidebar-overlay.is-open { opacity: 1; visibility: visible; }
}

@media (max-width: 560px) {
  .feature-grid { grid-template-columns: 1fr; }
  .field-row { grid-template-columns: 1fr; }

  /* Marketing nav collapses into a hamburger-triggered dropdown */
  .site-nav-actions { display: none; }
  .site-nav-toggle { display: inline-flex; }

  /* The 2x brand mark (56px icon / 36px text) never got a mobile pass —
     at this width it crowds the 40px hamburger button and can overflow
     a narrow phone screen. Scaled down proportionally, not just capped,
     so the icon:text ratio still reads the same as the desktop version.
     Doesn't need its own selector scope beyond plain .brand —
     .app-shell/.admin-shell's sidebar-context rules already win on
     specificity regardless of viewport, so this only ever applies to
     the marketing site-nav. */
  .brand { font-size: 16px; gap: 7px; }
  .brand-mark { width: 26px; height: 26px; border-radius: 7px; }

  .site-nav-links {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 8px;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    background: var(--panel);
    border: 1px solid var(--border-faint);
    border-radius: var(--radius-md);
    padding: 12px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    z-index: 50;
  }

  .site-nav-links.is-open { display: flex; }

  .site-nav-links a { padding: 11px 12px; border-radius: var(--radius-sm); }
  .site-nav-links a:hover { background: var(--panel-raised); }

  .site-nav-actions-mobile {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 8px;
    padding-top: 14px;
    border-top: 1px solid var(--border-faint);
  }
}

/* Prevent background scroll while the mobile sidebar drawer is open */
body.no-scroll { overflow: hidden; }

/* =========================================================================
   AI assistant — floating widget (member pages) + full assistant page
   ========================================================================= */

.ai-widget-toggle {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 1200;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--qubit-green), #15803d);
  border: none;
  color: var(--void);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 30px rgba(22, 163, 74, 0.35);
  transition: transform 0.15s var(--ease);
}

.ai-widget-toggle:hover { transform: scale(1.06); }
.ai-widget-toggle svg { width: 26px; height: 26px; }

.ai-widget-toggle .ai-unread-dot {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--collapse-coral);
  border: 2px solid var(--void);
  display: none;
}

.ai-widget-window {
  position: fixed;
  bottom: 92px;
  right: 24px;
  z-index: 1200;
  width: 380px;
  max-width: calc(100vw - 32px);
  height: 560px;
  max-height: calc(100vh - 130px);
  background: var(--deep-field);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
  display: none;
  flex-direction: column;
  overflow: hidden;
}

.ai-widget-window.is-open { display: flex; }

.ai-widget-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border-faint);
  background: linear-gradient(180deg, rgba(22, 163, 74, 0.07), transparent);
}

.ai-widget-header-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14.5px;
}

.ai-widget-header-title .ai-avatar {
  width: 32px;
  height: 32px;
  border-radius: 9px;
  background: linear-gradient(135deg, var(--qubit-green), #15803d);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  color: #e9fbf1;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
}

.ai-widget-header-actions { display: flex; align-items: center; gap: 6px; }

.ai-widget-icon-btn {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--muted-drift);
  border-radius: var(--radius-sm);
}

.ai-widget-icon-btn:hover { color: var(--signal-white); background: var(--panel); }
.ai-widget-icon-btn svg { width: 16px; height: 16px; }

.ai-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ai-message { display: flex; gap: 9px; max-width: 100%; }
.ai-message-user { flex-direction: row-reverse; }

.ai-message-avatar {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
}

.ai-message-assistant .ai-message-avatar { background: linear-gradient(135deg, var(--qubit-green), #15803d); color: var(--void); }
.ai-message-user .ai-message-avatar { background: var(--panel-raised); color: var(--signal-white); }

.ai-message-bubble {
  padding: 10px 13px;
  border-radius: var(--radius-md);
  font-size: 13.5px;
  line-height: 1.5;
  max-width: 80%;
  white-space: pre-wrap;
  word-break: break-word;
}

.ai-message-assistant .ai-message-bubble { background: var(--panel); border: 1px solid var(--border-faint); border-top-left-radius: 4px; }
.ai-message-user .ai-message-bubble { background: rgba(63, 224, 197, 0.12); border: 1px solid rgba(63, 224, 197, 0.25); border-top-right-radius: 4px; }

.ai-message-bubble.is-error { border-color: rgba(var(--alert-coral-rgb), 0.35); color: var(--collapse-coral); }

.ai-typing-dots { display: inline-flex; gap: 4px; padding: 4px 0; }
.ai-typing-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted-drift);
  animation: ai-typing-bounce 1.2s ease-in-out infinite;
}
.ai-typing-dots span:nth-child(2) { animation-delay: 0.15s; }
.ai-typing-dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes ai-typing-bounce {
  0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

.ai-empty-state {
  margin: auto;
  text-align: center;
  color: var(--muted-drift);
  font-size: 13px;
  padding: 20px;
}

.ai-input-row {
  display: flex;
  gap: 8px;
  padding: 12px;
  border-top: 1px solid var(--border-faint);
}

.ai-input-row textarea {
  flex: 1;
  resize: none;
  min-height: 40px;
  max-height: 100px;
  background: var(--void);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  color: var(--signal-white);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.4;
}

.ai-input-row textarea:focus { border-color: var(--muted-drift); }
.ai-input-row textarea:focus:not(:focus-visible) { outline: none; }

.ai-send-btn {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--qubit-green), #15803d);
  border: none;
  color: var(--void);
  display: flex;
  align-items: center;
  justify-content: center;
}

.ai-send-btn:disabled { opacity: 0.5; }
.ai-send-btn svg { width: 17px; height: 17px; }

/* ---- AI Assistant hero banner --------------------------------------------
   Decorative electric-brain visual is intentionally cyan/blue duotone via
   hardcoded hex rather than var(--qubit-violet), since dashboard-skin.css
   retires violet to a deep teal inside .app-shell — too close to cyan to
   keep the two-tone network readable here. */

.ai-hero {
  position: relative;
  overflow: visible;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-faint);
  background: var(--deep-field);
  padding: 23px 36px;
  margin-bottom: 24px;
}

.ai-hero-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
}

.ai-hero-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  animation: ai-hero-pulse 6s ease-in-out infinite;
}

.ai-hero-glow-a {
  width: 420px; height: 420px;
  /* Nudged toward the brain's new position (top/right, not transform —
     .ai-hero-glow's animation already animates transform: scale(), and a
     static transform here would just get overridden by that every
     frame). Only a partial shift, not the brain's full -200px/+10px —
     these blobs had their own offset from the brain by design, and
     matching the brain's shift exactly put this one too far left,
     off past the brain into the text column. */
  top: -160px; right: 40px;
  background: radial-gradient(circle, #3fe0c5, transparent 70%);
}

.ai-hero-glow-b {
  width: 300px; height: 300px;
  /* right:227px centers this horizontally on the brain's current
     translate(-200px, ...) position, worked out from the brain column's
     fixed 280px width/placement — not just another incremental nudge. */
  bottom: -170px; right: 227px;
  /* Site's actual cyan/green brand accent, not a generic green — same
     token .ai-hero-glow-a uses. Own keyframes (not the shared
     ai-hero-pulse) so its fade speed/opacity can differ from glow-a's
     without touching that one. */
  background: radial-gradient(circle, var(--qubit-cyan), transparent 70%);
  animation: ai-hero-pulse-slow 30s linear infinite;
  animation-delay: 2s;
  /* Without this, the 2s delay above renders at the element's default
     opacity (1 — fully opaque, nothing else sets a base opacity here)
     before the animation starts, then snaps down to the 0% keyframe's
     0.07 the instant it kicks in — a 2s flash at full brightness
     followed by an abrupt drop, not an actual fast fade. */
  animation-fill-mode: backwards;
}

@keyframes ai-hero-pulse-slow {
  0%, 100% { opacity: 0.07; transform: scale(1); }
  50% { opacity: 0.2; transform: scale(1.15); }
}

@keyframes ai-hero-pulse {
  0%, 100% { opacity: 0.16; transform: scale(1); }
  50% { opacity: 0.32; transform: scale(1.15); }
}

.ai-hero-inner {
  position: relative;
  z-index: 1;
  display: grid;
  /* Fixed width for the brain column (just past its own 260px cap) instead
     of a fraction, so the text column gets whatever's left over — that's
     what actually gives the paragraph real width to wrap into, rather than
     being capped to roughly half the card regardless of how much empty
     space sits to its right. */
  grid-template-columns: minmax(0, 1fr) 280px;
  align-items: center;
  gap: 20px;
}

.ai-hero-copy {
  max-width: 915px;
}

.ai-hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--qubit-green);
  margin-bottom: 10px;
}

.ai-hero-eyebrow .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--qubit-green);
  box-shadow: 0 0 8px var(--qubit-green);
  animation: ai-dot-pulse 2s ease-in-out infinite;
}

@keyframes ai-dot-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.ai-hero h1 {
  font-family: var(--font-display);
  font-size: clamp(20px, 2.1vw, 29px);
  font-weight: 800;
  line-height: 1.15;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: var(--signal-white);
}

.ai-hero h1 .accent { color: var(--qubit-green); }

.ai-history-panel .eyebrow { color: var(--qubit-green); }
.ai-history-panel .eyebrow::before {
  background: var(--qubit-green);
  box-shadow: 0 0 8px var(--qubit-green);
}

.prediction-call .eyebrow { color: var(--qubit-green); }
.prediction-call .eyebrow::before {
  background: var(--qubit-green);
  box-shadow: 0 0 8px var(--qubit-green);
}

.ai-hero p.lead {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted-drift);
  max-width: none;
  margin: 0 0 10px;
}

.ai-hero-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-bottom: 10px;
}

.ai-hero-tag {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--signal-white);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border-faint);
  border-radius: 100px;
  padding: 4px 11px;
}

.ai-hero-disclaimer {
  display: flex;
  gap: 7px;
  align-items: flex-start;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--muted-drift);
  max-width: 58ch;
}

.ai-hero-disclaimer svg {
  flex-shrink: 0;
  width: 14px; height: 14px;
  margin-top: 2px;
  color: var(--collapse-coral);
  opacity: 0.85;
}

.ai-brain-wrap {
  position: relative;
  z-index: 1;
  aspect-ratio: 1 / 1;
  max-width: 260px;
  margin-inline: auto;
  transform: translate(-200px, 10px);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Was unconstrained-width before, so aspect-ratio made the wrapper (and
     therefore the whole card, via align-items: center) match the grid
     column's full width — 500+px tall on a wide screen. Capping max-width
     here (not just on the svg) is the real fix; margin-block bleed is now
     modest since there's much less height to bleed off in the first place. */
  margin-block: -55px;
}

.ai-brain-wrap svg { width: 100%; height: 100%; max-width: 260px; }

.brain-outline {
  animation: brain-outline-flicker 4s ease-in-out infinite;
}

@keyframes brain-outline-flicker {
  0%, 100% { opacity: 0.7; }
  45% { opacity: 0.9; }
  50% { opacity: 0.55; }
  55% { opacity: 0.9; }
}

.brain-node {
  animation: brain-node-pulse 1.6s ease-in-out infinite;
  transform-origin: center;
  transform-box: fill-box;
}
.brain-node:nth-child(2n) { animation-delay: 0.15s; }
.brain-node:nth-child(3n) { animation-delay: 0.3s; }
.brain-node:nth-child(4n) { animation-delay: 0.5s; }
.brain-node:nth-child(5n) { animation-delay: 0.7s; }
.brain-node:nth-child(7n) { animation-delay: 0.9s; }

@keyframes brain-node-pulse {
  0%, 100% { opacity: 0.5; r: 2.6; }
  50% { opacity: 1; r: 4.4; }
}

.brain-core { animation: brain-core-pulse 1.4s ease-in-out infinite; }

@keyframes brain-core-pulse {
  0%, 100% { r: 6; opacity: 0.85; }
  50% { r: 9; opacity: 1; }
}

.brain-line {
  stroke-dasharray: 3 4;
  animation: brain-line-flow 1.4s linear infinite, brain-line-flicker 2.6s ease-in-out infinite;
}
.brain-line:nth-child(3n) { animation-duration: 1.1s, 3.1s; }
.brain-line:nth-child(3n+1) { animation-duration: 1.7s, 2.2s; animation-delay: 0.3s, 0.6s; }
.brain-line:nth-child(5n) { animation-duration: 0.9s, 1.9s; animation-delay: 0.1s, 0.4s; }

@keyframes brain-line-flow {
  to { stroke-dashoffset: -14; }
}

@keyframes brain-line-flicker {
  0%, 100% { opacity: 0.75; }
  40% { opacity: 0.25; }
  50% { opacity: 0.9; }
  60% { opacity: 0.3; }
}

@media (prefers-reduced-motion: reduce) {
  .ai-hero-glow, .ai-hero-eyebrow .dot, .brain-node, .brain-core, .brain-line, .brain-outline {
    animation: none !important;
  }
}

@media (max-width: 860px) {
  .ai-hero-inner { grid-template-columns: 1fr; }
  .ai-hero h1 { white-space: normal; }
  .ai-brain-wrap { max-width: 260px; margin: 0 auto; transform: none; }
}

/* ---- Full assistant page ------------------------------------------------ */

.ai-page-shell {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 20px;
  height: calc(100vh - 220px);
  min-height: 500px;
}

.ai-history-panel {
  background: var(--deep-field);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-lg);
  padding: 16px;
  overflow-y: auto;
}

.ai-history-list { display: flex; flex-direction: column; gap: 4px; margin-top: 12px; }

.ai-history-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--muted-drift);
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}

.ai-history-item:hover { background: var(--panel); color: var(--signal-white); }
.ai-history-item.active { background: var(--panel); color: var(--signal-white); border-left: 2px solid var(--qubit-cyan); }
.ai-history-item-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }

.ai-history-item-delete {
  opacity: 0;
  background: none;
  border: none;
  color: var(--muted-drift);
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  transition: background 0.15s var(--ease), color 0.15s var(--ease), transform 0.15s var(--ease);
}

.ai-history-item:hover .ai-history-item-delete { opacity: 1; }
.ai-history-item-delete:hover { color: var(--collapse-coral); background: rgba(var(--alert-coral-rgb), 0.14); transform: scale(1.08); }
.ai-history-item-delete svg { width: 18px; height: 18px; }

.ai-chat-panel {
  background: var(--deep-field);
  border: 1px solid var(--border-faint);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.ai-chat-panel .ai-messages { padding: 24px; }
.ai-chat-panel .ai-message-bubble { font-size: 14.5px; max-width: 72%; }
.ai-chat-panel .ai-input-row { padding: 16px 20px; }

@media (max-width: 900px) {
  .ai-page-shell { grid-template-columns: 1fr; height: auto; }
  .ai-history-panel { max-height: 220px; }
  .ai-chat-panel { height: 560px; }

  .ai-widget-window {
    right: 12px;
    left: 12px;
    width: auto;
    max-width: none;
    bottom: 84px;
  }

  .ai-widget-toggle { right: 16px; bottom: 16px; }
}

/* ---- Chart page mobile view (public/chart.php) --------------------------
   .chart-mobile-view is hidden by default (desktop shows
   .chart-desktop-view, the page's original layout, completely
   untouched); the media query below swaps which one is visible below
   700px. Two independent trees rather than one reflowed layout, since
   the mobile version needs its own LightweightCharts instance (area
   chart on 1D, candlesticks otherwise) distinct from the desktop
   chart's. */
.chart-mobile-view { display: none; }

.cmv-search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel-raised);
  border: 1px solid var(--border-faint);
  border-radius: 999px;
  padding: 10px 16px;
  margin-bottom: 18px;
}
.cmv-search-icon { width: 16px; height: 16px; color: var(--muted-drift); flex-shrink: 0; }
.cmv-search-input {
  background: none;
  border: none;
  outline: none;
  color: var(--signal-white);
  font-size: 14px;
  flex: 1;
  min-width: 0;
}
.cmv-search-input::placeholder { color: var(--muted-drift); }
.cmv-search-close { color: var(--muted-drift); font-size: 20px; line-height: 1; text-decoration: none; flex-shrink: 0; }

.cmv-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.cmv-ticker { font-size: 20px; font-weight: 700; color: var(--signal-white); }
.cmv-company { font-size: 12.5px; color: var(--muted-drift); margin-top: 2px; }
.cmv-trade-btn { padding: 8px 20px; font-size: 14px; flex-shrink: 0; }

.cmv-price-row { display: flex; align-items: baseline; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.cmv-price { font-size: 22px; font-weight: 700; }

.cmv-session-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 8px 14px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  background: var(--panel-raised);
  font-size: 10px;
  font-weight: 600;
}
.cmv-session-icon { width: 15px; height: 15px; flex-shrink: 0; }
.cmv-session-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: #fb923c; box-shadow: 0 0 6px #fb923c; }
.cmv-session-label { white-space: nowrap; }
.cmv-session-price { color: var(--signal-white); font-weight: 500; margin-left: 4px; }
.cmv-session-badge .mono:not(.cmv-session-price) { font-weight: 500; }

/* Overnight: moon icon + blue label, matching a real broker app's own
   overnight badge exactly (reference screenshot, 2026-08-10) — the
   other two states (pre-market/after-hours) keep the original plain
   dot + orange treatment, same tokens the shared .session-badge
   elsewhere already uses for those two. */
.cmv-session-badge-overnight .cmv-session-icon,
.cmv-session-badge-overnight .cmv-session-label { color: #818cf8; }
.cmv-session-badge-pre_market .cmv-session-label,
.cmv-session-badge-after_hours .cmv-session-label { color: #fb923c; }

.cmv-icon-row { display: flex; gap: 10px; margin-top: 16px; }
.cmv-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--border-faint);
  background: transparent;
  color: var(--muted-drift);
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.cmv-icon-btn svg { width: 18px; height: 18px; }
.cmv-icon-btn:hover { border-color: var(--qubit-cyan); color: var(--signal-white); }
.cmv-icon-btn.is-active { color: #fbbf24; border-color: rgba(251, 191, 36, 0.4); }
.cmv-icon-btn.is-refreshing svg { animation: spin 0.7s linear infinite; }

.cmv-tabs { display: flex; gap: 22px; margin-top: 20px; border-bottom: 1px solid var(--border-faint); }
.cmv-tab {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-drift);
  padding-bottom: 10px;
}
.cmv-tab.active { color: var(--qubit-green); border-bottom: 2px solid var(--qubit-green); }

.cmv-stats-panel { padding: 6px 18px; margin-top: 16px; margin-bottom: 24px; }
.cmv-stat-line {
  display: flex;
  gap: 16px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border-faint);
}
.cmv-stat-line:last-child { border-bottom: none; }
.cmv-stat-cell { flex: 1; min-width: 0; }
.cmv-stat-label { font-size: 12.5px; color: var(--muted-drift); margin-bottom: 3px; }
.cmv-stat-value { font-size: 14px; color: var(--signal-white); }

@media (max-width: 700px) {
  .chart-desktop-view { display: none; }
  .chart-mobile-view { display: block; }
}

/* ---- Reduced motion ------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ---- Feature-card tier chips ------------------------------------------
   Sits inline after the card's <h3>. The tier shown is generated from the
   real Auth::requirePlan()/requireQSuite() gate on the page that serves
   that feature (see build_features.py's TIER map) — a prospect should not
   be able to read a card, sign up on Free, and hit a wall. Cards with no
   chip are available on Free. */
.tier-chip {
  display: inline-block;
  margin-left: 7px;
  vertical-align: middle;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid currentColor;
  line-height: 1.5;
  white-space: nowrap;
}
.tier-chip.tier-basic  { color: var(--qubit-violet); background: rgba(var(--qubit-violet-rgb), 0.10); }
.tier-chip.tier-pro    { color: var(--link-accent); background: rgba(212, 175, 55, 0.10); }
.tier-chip.tier-qsuite { color: var(--qubit-cyan); background: rgba(63, 224, 197, 0.10); }

/* ---- FAQ accordion (faq.php) ------------------------------------------
   Native <details>/<summary> so it works with no JS and stays keyboard
   accessible for free. The marker is drawn with a rotating pseudo-element
   because the default triangle can't be styled consistently. */
.faq-item {
  padding: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.faq-item summary {
  cursor: pointer;
  list-style: none;
  padding: 20px 52px 20px 24px;
  font-size: 16.5px;
  font-weight: 600;
  color: var(--signal-white);
  position: relative;
  transition: color 0.15s var(--ease);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover { color: var(--qubit-cyan); }
.faq-item summary:focus-visible {
  outline: 2px solid var(--qubit-cyan);
  outline-offset: -2px;
}
.faq-item summary::after {
  content: "";
  position: absolute;
  right: 24px;
  top: 50%;
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--muted-drift);
  border-bottom: 2px solid var(--muted-drift);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 0.18s var(--ease), border-color 0.15s var(--ease);
}
.faq-item[open] summary::after {
  transform: translateY(-20%) rotate(-135deg);
  border-color: var(--qubit-cyan);
}
.faq-item p {
  margin: 0;
  padding: 0 24px 22px;
  color: var(--muted-drift);
  font-size: 14.5px;
  line-height: 1.65;
  max-width: 68ch;
}
@media (prefers-reduced-motion: reduce) {
  .faq-item summary::after { transition: none; }
}
