/* ============================================================================
 * teenyverse.css — THE design system, built into one file.
 * Single source of truth. The app links THIS first; legacy CSS layers on top
 * until each piece is migrated onto it, then deleted.
 *
 * Built from the teenyverse design-system project (do not hand-edit — edit the
 * source tokens/*.css + components.css and re-export). Neon-on-navy, lowercase,
 * curated accent schemes, cyberpurple + cyberpink themes (effects intact).
 * Generated 2026-06-24.
 * ========================================================================== */


/* ============================================================================
   FONTS — Space Grotesk · Poppins · Roboto Mono
   (source: tokens/fonts.css)
   ============================================================================ */
/* teenyverse — webfonts
 * All three families ship from Google Fonts, matching the live admin shell.
 * Space Grotesk  → logo / display      (lowercase, tight)
 * Poppins        → body / UI           (light 300 default)
 * Roboto Mono    → data, device ids, code
 */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&family=Roboto+Mono:wght@400;500;600&display=swap');

/* ============================================================================
   TYPOGRAPHY TOKENS
   (source: tokens/typography.css)
   ============================================================================ */
/* teenyverse — typography tokens
 * Space Grotesk (display) · Poppins (body) · Roboto Mono (data)
 * House rule: UI text is lowercase. Headings, nav, badges, buttons all
 * lower-case via text-transform; copy is sentence/lowercase in voice.
 */
:root {
  /* Families */
  --font-display: 'Space Grotesk', system-ui, sans-serif;   /* logo, hero, page identity */
  --font-body: 'Poppins', system-ui, sans-serif;            /* everything else */
  --font-mono: 'Roboto Mono', ui-monospace, monospace;      /* device ids, telemetry, code */

  /* Weights — Poppins body defaults to LIGHT (300), the platform's resting weight */
  --weight-light: 300;
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  /* Type scale (relative to 14px base, the admin body size) */
  --text-base-size: 14px;        /* body root */
  --leading-body: 1.75;          /* generous — light weight needs air */
  --leading-tight: 1.2;

  --fs-micro: 0.66rem;    /* arc-stage badges, finest labels */
  --fs-tag: 0.7rem;       /* tags, eyebrow labels */
  --fs-caption: 0.78rem;  /* meta, secondary */
  --fs-small: 0.85rem;    /* dense UI, table body */
  --fs-body: 0.9rem;      /* default reading size */
  --fs-base: 1rem;        /* 14px */
  --fs-h3: 1.1em;
  --fs-h2: 1.25em;
  --fs-h1: 1.5em;
  --fs-display: 2.8em;    /* slime-tank login title scale */

  /* Tracking — uppercase eyebrows + neon titles get letter-spacing */
  --tracking-tag: 0.06em;
  --tracking-eyebrow: 0.1em;
  --tracking-display: 2px;
}

/* ============================================================================
   SPACING · RADIUS · SHADOW · MOTION TOKENS
   (source: tokens/spacing.css)
   ============================================================================ */
/* teenyverse — spacing, radius, shadow, motion tokens */
:root {
  /* ---- Spacing (admin shell works in em; these are the common steps) ---- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --gap-card: 1em;        /* default gap between dashboard cards */
  --page-max: 1200px;     /* main content max-width */
  --page-pad: 1.5em;

  /* ---- Radius ---- */
  --radius-xs: 4px;       /* tags, type-badges, dropdowns */
  --radius-sm: 6px;       /* cards, buttons, inputs */
  --radius-md: 8px;       /* stat cards */
  --radius-lg: 16px;      /* login / hero cards */
  --radius-pill: 999px;   /* badges, status pills, search inputs */

  /* ---- Shadow (light theme — restrained, soft) ---- */
  --shadow-card: 0 1px 4px rgba(0, 0, 0, 0.08);
  --shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.08);
  --shadow-pop: 0 4px 12px rgba(0, 0, 0, 0.3);   /* dropdowns / menus */

  /* ---- Header accent strip ---- */
  --strip-grad: linear-gradient(90deg, var(--accent) 0%, var(--accent-light) 100%);

  /* ---- Motion ---- */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); /* @kind other */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); /* @kind other */
  --dur-fast: 0.15s; /* @kind other */
  --dur-base: 0.2s; /* @kind other */
  --dur-slow: 0.3s; /* @kind other */
}

/* Dark theme deepens shadows + adds the neon glow vocabulary */
.dark, [data-theme="dark"], html.dark-theme {
  --shadow-card: 0 2px 10px rgba(0, 0, 0, 0.35);
  --shadow-hover: 0 6px 22px rgba(0, 0, 0, 0.5);
  --shadow-pop: 0 4px 12px rgba(0, 0, 0, 0.45);
}

/* ============================================================================
   COLOR TOKENS · light/dark · accent schemes · cyberpurple · CYBERPINK
   (source: tokens/colors.css)
   ============================================================================ */
/* teenyverse — color tokens
 * Two-theme system faithful to the admin shell. :root is the LIGHT theme
 * base; .dark / [data-theme="dark"] / html.dark-theme apply the signature
 * neon-on-navy look (the brand's memorable face). Accent is swappable via
 * the scheme palette below — default is neon-blue (#00d4ff).
 */
:root {
  /* ---- Accent (default: neon blue) ---- */
  --accent: #00d4ff;
  --accent-hover: #00b8e6;
  --accent-light: #33ddff;
  --accent-bg: rgba(0, 212, 255, 0.12);
  --accent-glow: 0 0 10px rgba(0, 212, 255, 0.5);

  /* ---- Text (light theme) ---- */
  --text-primary: #333;
  --text-secondary: #666;
  --text-muted: #888;
  --text-disabled: #aaa;
  --text-on-accent: #fff;

  /* ---- Surfaces (light theme) ---- */
  --bg-primary: #fff;        /* cards */
  --bg-secondary: #f5f5f5;   /* page */
  --bg-tertiary: #f0f0f0;
  --bg-hover: #e8e8e8;
  --bg-deep: #f8f8f8;
  --surface-card: var(--bg-primary);
  --surface-page: var(--bg-secondary);

  /* ---- Borders (light theme) ---- */
  --border-light: #eee;
  --border-default: #ddd;
  --border-dark: #ccc;
  --border: var(--border-default);

  /* ---- Header chrome (constant across themes) ---- */
  --header-bg: #1a1a2e;

  /* ---- Status ---- */
  --success: #2e7d32;  --success-light: #3dff88;  --success-bg: rgba(76, 175, 80, 0.15);
  --error: #c62828;    --error-light: #ef5350;    --error-bg: rgba(244, 67, 54, 0.15);
  --danger: var(--error);
  --warning: #f57c00;  --warning-light: #ffb74d;  --warning-bg: rgba(255, 193, 7, 0.15);
  --info: #1976d2;     --info-light: #3da5ff;     --info-bg: rgba(33, 150, 243, 0.15);

  /* ---- Neon data palette (stat cards / charts) — one luminous family,
     no pastels: every series reads as neon-on-navy, the brand signature ---- */
  --neon-blue: #2f9bff;    /* control */
  --neon-cyan: #00e5d4;    /* sensing */
  --neon-peach: #ff9e3d;   /* events */
  --neon-violet: #b14dff;  /* investigation */
  --neon-pink: #ff3d8b;    /* anomaly */
  --neon-green: #2bff88;   /* nominal */
  --neon-gold: #ffd23d;    /* scientist tier */
  --neon-bubble: #39ff14;  /* slime-tank bubbles */
}

/* ============ DARK THEME — the signature look ============ */
.dark,
[data-theme="dark"],
html.dark-theme {
  --text-primary: #e0e0e0;
  --text-secondary: #bdbdbd;
  --text-muted: #888;
  --text-disabled: #666;

  --bg-primary: #1f2940;     /* cards */
  --bg-secondary: #2a3a55;
  --bg-tertiary: #3a4a65;
  --bg-hover: #3a4a65;
  --bg-deep: #16213e;        /* deepest navy — page void */
  --surface-card: var(--bg-primary);
  --surface-page: var(--bg-deep);

  --border-light: #3a4a65;
  --border-default: #4a5a75;
  --border-dark: #5a6a85;
  --border: var(--border-light);

  --success-light: #3dff88;
  --error-light: #ef5350;
  --warning-light: #ffc107;
  --info-light: #3da5ff;
}

/* ============ ACCENT SCHEMES ============
 * Swap the accent family by adding one class to <html> or <body>.
 * Sampled from the live shell's three scheme tiers.
 */
.scheme-neon-blue    { --accent:#00d4ff; --accent-hover:#00b8e6; --accent-light:#33ddff; --accent-bg:rgba(0,212,255,.15);  --accent-glow:0 0 10px rgba(0,212,255,.5); }
.scheme-hot-pink     { --accent:#ff1493; --accent-hover:#e60073; --accent-light:#ff69b4; --accent-bg:rgba(255,20,147,.15); --accent-glow:0 0 10px rgba(255,20,147,.5); }
.scheme-neon-green   { --accent:#39ff14; --accent-hover:#32e010; --accent-light:#7fff00; --accent-bg:rgba(57,255,20,.15);  --accent-glow:0 0 10px rgba(57,255,20,.5); }
.scheme-electric-purple { --accent:#bf00ff; --accent-hover:#a000d9; --accent-light:#d24dff; --accent-bg:rgba(191,0,255,.15); --accent-glow:0 0 10px rgba(191,0,255,.5); }
.scheme-dusk-violet  { --accent:#7c3aed; --accent-hover:#6d28d9; --accent-light:#a78bfa; --accent-bg:rgba(124,58,237,.15); --accent-glow:0 0 15px rgba(124,58,237,.6); }
.scheme-dusk-aqua    { --accent:#0891b2; --accent-hover:#0e7490; --accent-light:#22d3ee; --accent-bg:rgba(8,145,178,.15);  --accent-glow:0 0 15px rgba(8,145,178,.6); }
/* ============ CYBERPURPLE — the secret labs theme ============
 * Re-enabled from microverse/admin/labs/cyberpurple.css. A deep-space
 * violet/magenta/cyan neon mode. Apply `.theme-cyberpurple` to <html>,
 * <body>, or any wrapper — every token + component adopts it. Pair with
 * the nebula-grid canvas (components.css) for the full effect.
 */
.theme-cyberpurple {
  --accent: #c47cff;          /* neon purple */
  --accent-hover: #9945ff;
  --accent-light: #d4a5ff;
  --accent-bg: rgba(196, 124, 255, 0.15);
  --accent-glow: 0 0 20px rgba(196, 124, 255, 0.6);

  --text-primary: #f0f0ff;
  --text-secondary: #d0d0e8;
  --text-muted: rgba(196, 124, 255, 0.7);
  --text-disabled: rgba(196, 124, 255, 0.4);

  --bg-deep: #0d0221;         /* space void */
  --bg-primary: #150734;      /* card base */
  --bg-secondary: #1a0a3e;
  --bg-tertiary: #220b4a;
  --bg-hover: #220b4a;
  --surface-card: rgba(21, 7, 52, 0.8);
  --surface-page: var(--bg-deep);

  --border-light: rgba(196, 124, 255, 0.2);
  --border-default: rgba(196, 124, 255, 0.3);
  --border-dark: rgba(196, 124, 255, 0.45);
  --border: var(--border-light);

  --header-bg: rgba(21, 7, 52, 0.95);

  --success: #05d9e8;  --success-light: #05d9e8;
  --warning: #f9c80e;  --warning-light: #f9c80e;
  --error: #ff4545;    --error-light: #ff6b6b;
  --info: #7c4dff;     --info-light: #c47cff;

  --neon-bubble: #ff45ff;     /* magenta drips/bubbles in space */
  color-scheme: dark;
}

/* Cyberpurple canvas: deep-space nebula wash. Apply to a page/wrapper that
 * also carries .theme-cyberpurple — gives the subtle layered background the
 * labs wing rides on. */
.theme-cyberpurple {
  background-color: #0d0221;
  background-image:
    radial-gradient(ellipse at 15% 15%, rgba(196, 124, 255, 0.15) 0%, transparent 50%),
    radial-gradient(ellipse at 85% 25%, rgba(153, 69, 255, 0.10) 0%, transparent 45%),
    radial-gradient(ellipse at 50% 80%, rgba(255, 69, 255, 0.08) 0%, transparent 50%);
  background-attachment: fixed;
}

/* ============ CYBERPINK — cyberpunk, but pink ============
 * Merged from the live admin's cyberpink wing into the new token contract.
 * Keeps its signature --cpk-* palette (so the app's existing cyberpink chrome
 * keeps resolving) AND maps it onto the standard tokens (so new .tv-* /
 * surface components adopt it for free). Rose-black void, neon-pink + gold +
 * magenta, hex-dot lattice, warm nebula, gold→pink breathing strip.
 * Apply `.theme-cyberpink` to <html>, <body>, or any wrapper.
 */
.theme-cyberpink {
  /* signature palette (verbatim from the live cyberpink) */
  --cpk-void: #08000e;  --cpk-deep: #10031a;  --cpk-mid: #1a0828;  --cpk-light: #24103a;
  --cpk-rose: #b01050;  --cpk-neon-pink: #ff6eb4;  --cpk-hot-pink: #ff2d7b;
  --cpk-neon-coral: #ffb3d9;  --cpk-gold: #ffd700;  --cpk-gold-light: #ffe44d;  --cpk-magenta: #ff45ff;

  /* mapped onto the standard token contract */
  --accent: #ff6eb4;  --accent-hover: #ff2d7b;  --accent-light: #ffb3d9;
  --accent-bg: rgba(255, 110, 180, 0.12);  --accent-glow: 0 0 20px rgba(255, 110, 180, 0.6);

  --text-primary: #fff;  --text-secondary: #ffd4ea;  --text-muted: #ffb3d9;  --text-disabled: #ff8fc5;  --text-on-accent: #14000a;

  --bg-deep: #08000e;  --bg-primary: rgba(16, 3, 26, 0.8);  --bg-secondary: #08000e;  --bg-tertiary: rgba(26, 8, 40, 0.6);  --bg-hover: rgba(255, 110, 180, 0.1);
  --surface-card: var(--bg-primary);  --surface-page: var(--bg-deep);

  --border-light: rgba(255, 110, 180, 0.15);  --border-default: rgba(255, 110, 180, 0.25);  --border-dark: rgba(255, 110, 180, 0.4);  --border: var(--border-light);

  --header-bg: rgba(16, 3, 26, 0.95);

  --success: #ffd700;  --success-light: #ffe44d;  --success-bg: rgba(255, 215, 0, 0.15);
  --warning: #ff8c42;  --warning-light: #ffa76b;  --warning-bg: rgba(255, 140, 66, 0.15);
  --error: #ff4545;    --error-light: #ff6b6b;    --error-bg: rgba(255, 69, 69, 0.15);
  --info: #ff45ff;     --info-light: #ff7aff;     --info-bg: rgba(255, 69, 255, 0.15);

  --neon-bubble: #ff45ff;
  color-scheme: dark;
  background: var(--cpk-void);
}

/* Cyberpink effects — hex-dot lattice + warm pink/gold nebula. Fixed layers,
 * so they work whether .theme-cyberpink sits on <html> or a wrapper. */
.theme-cyberpink::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(circle, rgba(255, 110, 180, 0.07) 1px, transparent 1px),
    radial-gradient(circle, rgba(255, 110, 180, 0.07) 1px, transparent 1px);
  background-size: 48px 84px; background-position: 0 0, 24px 42px;
  animation: tv-hexPulse 12s ease-in-out infinite;
}
.theme-cyberpink::after {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(ellipse at 20% 15%, rgba(255, 45, 123, 0.12) 0%, transparent 50%),
    radial-gradient(ellipse at 80% 25%, rgba(255, 110, 180, 0.10) 0%, transparent 45%),
    radial-gradient(ellipse at 45% 85%, rgba(255, 215, 0, 0.05) 0%, transparent 50%);
}
@media (prefers-reduced-motion: reduce) { .theme-cyberpink::before { animation: none; } }

/* ============================================================================
   MOTION LIBRARY — keyframes + .tv-anim-* utilities
   (source: tokens/motion.css)
   ============================================================================ */
/* teenyverse — motion library
 * The system's animation vocabulary, distilled from ~100 keyframes across
 * the live admin (shell.css, admin.css, dashboard-system.css, discover-*,
 * workbench, tapestry, source-browser, analyze-root, …). Canonical,
 * reusable keyframes + utility classes. Durations/easing come from
 * spacing.css (--dur-*, --ease-out-expo). All decorative loops yield to
 * prefers-reduced-motion.
 *
 * Families: live/breathing · loading · entrance · data · ai · locate ·
 * texture · feedback.
 */

/* ============ LIVE / BREATHING (presence, health, "this is live") ============ */
/* ring pulse on a status dot — the single most-used motif (pulse-nominal,
   ds-pulse, health-pulse, status-pulse-anim, now-pulse, lt-live-pulse…) */
@keyframes tv-pulse-ring { 0%,100% { box-shadow: 0 0 0 0 var(--tv-pulse-color, rgba(57,255,20,.5)); } 50% { box-shadow: 0 0 0 5px transparent; } }
@keyframes tv-breathe { 0%,100% { opacity: .4; transform: scale(.95); } 50% { opacity: 1; transform: scale(1.05); } }
@keyframes tv-live-pulse { 0%,100% { transform: scale(1); opacity: .85; } 50% { transform: scale(1.18); opacity: 1; } }
@keyframes tv-accent-breath { 0%,100% { opacity: .65; } 50% { opacity: 1; } }
.tv-anim-pulse  { animation: tv-pulse-ring 2s ease-in-out infinite; }
.tv-anim-breathe{ animation: tv-breathe 2s ease-in-out infinite; }
.tv-anim-live   { animation: tv-live-pulse 1.8s ease-in-out infinite; }
.tv-anim-breath { animation: tv-accent-breath 3s ease-in-out infinite; }

/* ============ LOADING (waiting on data) ============ */
@keyframes tv-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@keyframes tv-spin { to { transform: rotate(360deg); } }
.tv-anim-shimmer { background: linear-gradient(90deg, rgba(255,255,255,.04) 0%, rgba(255,255,255,.10) 50%, rgba(255,255,255,.04) 100%); background-size: 200% 100%; animation: tv-shimmer 1.4s ease-in-out infinite; }
.tv-anim-spin { animation: tv-spin .7s linear infinite; }

/* ============ ENTRANCE (content arriving) ============ */
/* fade-up — the universal entrance (mc-card-fade-in, dp-msg-in, drail-rise,
   dsyn-rise, fresh-row-in, fadeIn, ds-fade, src-card-in, ty-*-in) */
/* Entrance utilities never gate VISIBILITY: the resting/base state is the
 * visible end-state (opacity:1, untransformed), and motion is layered on only
 * under prefers-reduced-motion: no-preference. So a frozen/suspended clock,
 * print, PDF export, or reduced-motion shows content — never a blank opacity:0.
 * fade-up animates transform only (a settle), so even a frozen animation at t=0
 * stays fully visible. */
@keyframes tv-fade-up { from { transform: translateY(7px); } to { transform: translateY(0); } }
@keyframes tv-toast-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes tv-highlight-pulse { 0% { box-shadow: 0 0 0 4px var(--accent-bg); } 100% { box-shadow: 0 0 0 0 transparent; } }
@media (prefers-reduced-motion: no-preference) {
  .tv-anim-fade-up { animation: tv-fade-up 320ms var(--ease-out-expo) both; }
  .tv-anim-toast-in { animation: tv-toast-in 300ms var(--ease-out-expo) both; }
}
.tv-anim-highlight { animation: tv-highlight-pulse 1.2s ease-out 1; }

/* ============ DATA (charts, sparklines drawing in) ============ */
/* draw: resting state is the fully-drawn stroke (dasharray set, offset 0 =
 * visible). The draw-on only runs under no-preference, so reduced-motion/print
 * show the finished sparkline rather than an empty path. */
@keyframes tv-draw { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }
.tv-anim-draw { stroke-dasharray: 1000; }
@media (prefers-reduced-motion: no-preference) {
  .tv-anim-draw { animation: tv-draw 1.1s var(--ease-out-expo) both; }
}

/* ============ AI / PARTNER (streaming reply) ============ */
@keyframes tv-stream-cursor { 0%,100% { opacity: 1; } 50% { opacity: 0; } }
@keyframes tv-thinking { 0%,80%,100% { opacity: .3; } 40% { opacity: 1; } }
.tv-anim-cursor { display: inline-block; width: 1.5px; animation: tv-stream-cursor 1s steps(1) infinite; }
.tv-anim-thinking { animation: tv-thinking 1200ms ease-in-out infinite; }

/* ============ LOCATE ("you are here", focus pull) ============ */
@keyframes tv-here-ring { 0% { box-shadow: 0 0 0 0 var(--tv-ring-color, rgba(196,124,255,.7)); opacity: 1; } 100% { box-shadow: 0 0 0 16px transparent; opacity: 0; } }
.tv-anim-here-ring { animation: tv-here-ring 2.4s ease-out infinite; }

/* ============ TEXTURE (ambient) ============ */
@keyframes tv-glint { 0%,100% { opacity: .4; transform: scale(1); } 50% { opacity: 1; transform: scale(1.5); } }
.tv-anim-glint { animation: tv-glint 3s ease-in-out infinite; }
/* (tv-hex-drift, tv-glowPulse, tv-particleFloat live in components.css with their layers) */

/* cyberpink ambient — hex-lattice pulse + the gold→pink breathing accent strip
 * (tv-glint above doubles as the rhinestone glint) */
@keyframes tv-hexPulse { 0%,100% { opacity: .6; } 50% { opacity: 1; } }
@keyframes tv-accentBreath { 0%,100% { opacity: .7; } 50% { opacity: 1; } }

/* ============ FEEDBACK (error, attention) ============ */
@keyframes tv-shake { 0%,100% { transform: translateX(0); } 20% { transform: translateX(-3px); } 40% { transform: translateX(3px); } 60% { transform: translateX(-2px); } 80% { transform: translateX(2px); } }
.tv-anim-shake { animation: tv-shake .4s ease-in-out 1; }

@media (prefers-reduced-motion: reduce) {
  .tv-anim-pulse, .tv-anim-breathe, .tv-anim-live, .tv-anim-breath,
  .tv-anim-shimmer, .tv-anim-spin, .tv-anim-cursor, .tv-anim-thinking,
  .tv-anim-here-ring, .tv-anim-glint { animation: none !important; }
}

/* ============================================================================
   BASE LAYER — element defaults (optional/opinionated)
   (source: tokens/base.css)
   ============================================================================ */
/* teenyverse — base layer
 * Minimal element defaults that encode house rules (lowercase headings,
 * light body weight, navy void background). Components/cards still set
 * their own surface; this just gives a sane on-brand canvas.
 */
:root {
  color-scheme: light;
}
.dark, [data-theme="dark"], html.dark-theme { color-scheme: dark; }

body {
  font-family: var(--font-body);
  font-size: var(--text-base-size);
  font-weight: var(--weight-light);
  line-height: var(--leading-body);
  color: var(--text-primary);
  background: var(--surface-page);
  -webkit-font-smoothing: antialiased;
}

/* House rule: UI headings are lowercase, Poppins semibold */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-body);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  line-height: var(--leading-tight);
  text-transform: lowercase;
  margin: 0 0 0.4em;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

a { color: var(--accent); text-decoration: none; transition: color var(--dur-base); }
a:hover { color: var(--accent-light); }

code, kbd, samp { font-family: var(--font-mono); }

::selection { background: var(--accent-bg); }

/* ============================================================================
   COMPONENT PRIMITIVES — .tv-* (button/badge/tag/pill/card/stat/input) + NeonField/galaxy/hex atmosphere
   (source: tokens/components.css)
   ============================================================================ */
/* teenyverse — component primitives
 * Faithful to the live admin shell, which styles primitives via global
 * classes (.btn, .badge, .card, .stat-card …). React wrappers render
 * these classes so hover/active/disabled states come for free and stay
 * on-brand in both light and dark themes.
 */

/* ============ Button ============ */
.tv-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--bg-deep);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-weight: var(--weight-medium);
  font-size: var(--fs-small);
  text-transform: lowercase;
  cursor: pointer;
  transition: background var(--dur-base), border-color var(--dur-base), color var(--dur-base), transform var(--dur-base), filter var(--dur-base);
}
.tv-btn:hover { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }
.tv-btn:active { transform: translateY(1px); }
.tv-btn svg, .tv-btn i { font-size: 0.95em; }

.tv-btn--primary { background: var(--accent); border-color: var(--accent); color: var(--text-on-accent); }
.tv-btn--primary:hover { background: var(--accent); border-color: var(--accent); color: var(--text-on-accent); filter: brightness(1.1); }
html.dark-theme .tv-btn--primary, .dark .tv-btn--primary, [data-theme="dark"] .tv-btn--primary { color: #06121e; }

.tv-btn--ghost { background: transparent; border-color: transparent; color: var(--text-secondary); }
.tv-btn--ghost:hover { background: var(--accent-bg); border-color: transparent; color: var(--accent); }

.tv-btn--danger { color: var(--error); }
.tv-btn--danger:hover { background: var(--error-bg); border-color: var(--error); color: var(--error); }

.tv-btn--sm { padding: 0.3rem 0.6rem; font-size: var(--fs-tag); }
.tv-btn--lg { padding: 0.7rem 1.2rem; font-size: var(--fs-body); }

.tv-btn:disabled, .tv-btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

/* glow on primary in dark neon schemes */
.dark .tv-btn--primary, [data-theme="dark"] .tv-btn--primary, html.dark-theme .tv-btn--primary {
  box-shadow: var(--accent-glow);
}

/* Circular icon button (login slime-button lineage, toned down) */
.tv-iconbtn {
  width: 2.4em; height: 2.4em; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-deep); color: var(--text-secondary);
  cursor: pointer; transition: all var(--dur-base);
}
.tv-iconbtn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-bg); }
.dark .tv-iconbtn:hover, [data-theme="dark"] .tv-iconbtn:hover, html.dark-theme .tv-iconbtn:hover { box-shadow: var(--accent-glow); }

/* ============ Badge (soft pill · lowercase · "what this is doing") ============ */
.tv-badge {
  display: inline-flex; align-items: center; gap: 0.3em;
  padding: 0.25em 0.75em;
  border-radius: var(--radius-pill);
  font-family: var(--font-body); font-size: var(--fs-tag); font-weight: var(--weight-medium);
  text-transform: lowercase;
  background: var(--bg-hover); color: var(--text-secondary);
}
.tv-badge--online   { background: var(--success-bg); color: var(--success-light); }
.tv-badge--offline  { background: rgba(158,158,158,.2); color: var(--text-secondary); }
.tv-badge--pending  { background: var(--warning-bg); color: var(--warning-light); }
.tv-badge--error    { background: var(--error-bg); color: var(--error-light); }
.tv-badge--info     { background: var(--info-bg); color: var(--info-light); }
.tv-badge--accent   { background: var(--accent-bg); color: var(--accent); }

/* ============ Tag (rounded square · UPPERCASE · "what this is") ============ */
.tv-tag {
  display: inline-block;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-xs);
  font-family: var(--font-body); font-size: var(--fs-tag); font-weight: var(--weight-semibold);
  text-transform: uppercase; letter-spacing: var(--tracking-tag);
  background: rgba(255,255,255,0.05); color: var(--text-muted); white-space: nowrap;
}
.dark .tv-tag, [data-theme="dark"] .tv-tag, html.dark-theme .tv-tag { background: rgba(255,255,255,0.05); }
:root .tv-tag { background: #f0f0f0; }
.dark .tv-tag, [data-theme="dark"] .tv-tag, html.dark-theme .tv-tag { background: rgba(255,255,255,0.06); }
.tv-tag--accent  { color: var(--accent); }
.tv-tag--success { color: var(--success-light); }
.tv-tag--warning { color: var(--warning-light); }
.tv-tag--danger  { color: var(--error-light); }
.tv-tag--info    { color: var(--info-light); }

/* ============ Status pill (leading dot · UPPERCASE · inline) ============ */
.tv-pill {
  display: inline-flex; align-items: center; gap: 0.35em;
  padding: 0.18em 0.55em; border-radius: var(--radius-pill);
  font-family: var(--font-body); font-size: var(--fs-tag); font-weight: var(--weight-semibold);
  text-transform: uppercase; letter-spacing: 0.04em;
  border: 1px solid transparent; background: var(--bg-secondary); color: var(--text-muted);
}
.tv-pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.tv-pill--green { color: var(--success-light); border-color: rgba(74,222,128,.25); }
.tv-pill--amber { color: var(--warning-light); border-color: rgba(251,191,36,.25); }
.tv-pill--red   { color: var(--error-light); border-color: rgba(248,113,113,.25); }
.tv-pill--blue  { color: var(--accent); border-color: rgba(96,165,250,.25); }

/* ============ Card ============ */
.tv-card {
  background: var(--surface-card);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.dark .tv-card, [data-theme="dark"] .tv-card, html.dark-theme .tv-card { border: 1px solid var(--border-light); }
.tv-card__head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5em;
  padding: 0.75em 1em; border-bottom: 1px solid var(--border-light);
}
.tv-card__head h3 { margin: 0; font-size: var(--fs-h3); font-weight: var(--weight-medium); display: flex; align-items: center; gap: 0.4em; }
.tv-card__head h3 .tv-ic { color: var(--accent); }
.tv-card__body { padding: 1em; }

/* ============ Stat card (neon · hover lift + accent strip) ============ */
.tv-stat {
  position: relative; overflow: hidden;
  background: var(--surface-card);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: var(--radius-md);
  padding: 1.1em 1.3em;
  display: flex; align-items: center; gap: 1em;
  transition: box-shadow var(--dur-slow), border-color var(--dur-slow), transform var(--dur-slow) var(--ease-out-expo);
}
.tv-stat::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, var(--accent), transparent); opacity: 0; transition: opacity var(--dur-slow) var(--ease-out-expo); }
.tv-stat:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-hover); }
.tv-stat:hover::before { opacity: 1; }
.tv-stat__icon { color: var(--accent); font-size: 1.9rem; flex: none; display: flex; }
.tv-stat__icon svg { width: 1.9rem; height: 1.9rem; }
.tv-stat__value { font-family: var(--font-body); font-size: 2em; font-weight: var(--weight-semibold); line-height: 1; color: var(--text-primary); }
.tv-stat__label { font-size: var(--fs-body); color: var(--text-secondary); text-transform: lowercase; }
.tv-stat--control { border-color: rgba(47,155,255,.4); } .tv-stat--control .tv-stat__icon { color: var(--neon-blue); }
.tv-stat--sensing { border-color: rgba(0,229,212,.4); } .tv-stat--sensing .tv-stat__icon { color: var(--neon-cyan); }
.tv-stat--events  { border-color: rgba(255,176,124,.4); } .tv-stat--events .tv-stat__icon { color: var(--neon-peach); }

/* ============ Input / search ============ */
.tv-input {
  width: 100%;
  padding: 0.5em 0.85em;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--bg-deep);
  color: var(--text-primary);
  font-family: var(--font-body); font-size: var(--fs-small); font-weight: var(--weight-light);
  transition: border-color var(--dur-base), box-shadow var(--dur-base);
}
.tv-input::placeholder { color: var(--text-muted); }
.tv-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
.tv-input--search { border-radius: var(--radius-pill); }
.dark .tv-input, [data-theme="dark"] .tv-input, html.dark-theme .tv-input { background: var(--bg-secondary); }

/* ============ NeonField — the drifting glowing-dot background ============
 * The signature dark-theme atmosphere, lifted verbatim from the admin shell
 * (.particles + .glow-overlay + scanlines). Self-contained: drop a
 * .tv-neonfield wrapper over any deep surface; it paints behind its content.
 * Accent-keyed, so it follows whatever .scheme-* is active.
 */
.tv-neonfield { position: relative; overflow: hidden; background: var(--bg-deep); }
.tv-neonfield > .tv-neonfield__content { position: relative; z-index: 2; }

.tv-neonfield__glow {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse at 15% 25%, color-mix(in srgb, var(--accent) 12%, transparent) 0%, transparent 45%),
    radial-gradient(ellipse at 85% 75%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 45%),
    radial-gradient(ellipse at 50% 50%, color-mix(in srgb, var(--accent) 5%, transparent) 0%, transparent 55%);
  animation: tv-glowPulse 10s ease-in-out infinite;
}
@keyframes tv-glowPulse { 0%,100% { opacity: .7; } 50% { opacity: 1; } }

.tv-neonfield__scan {
  position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: .5;
  background: repeating-linear-gradient(0deg, rgba(0,0,0,.06) 0, rgba(0,0,0,.06) 1px, transparent 1px, transparent 3px);
}

.tv-neonfield__particles { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.tv-particle {
  position: absolute; width: 3px; height: 3px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 8px var(--accent), 0 0 16px var(--accent);
  opacity: 0; animation: tv-particleFloat 20s infinite ease-in-out;
}
@keyframes tv-particleFloat {
  0%, 100% { transform: translateY(100%) scale(0); opacity: 0; }
  5%       { opacity: .8; transform: translateY(94%) scale(1); }
  95%      { opacity: .8; transform: translateY(6%) scale(1); }
  100%     { transform: translateY(-6%) scale(0); opacity: 0; }
}
/* deterministic spread + cadence for up to 12 dots */
.tv-particle:nth-child(1){left:8%;animation-delay:0s;animation-duration:22s}
.tv-particle:nth-child(2){left:18%;animation-delay:3s;animation-duration:18s}
.tv-particle:nth-child(3){left:29%;animation-delay:6s;animation-duration:24s}
.tv-particle:nth-child(4){left:39%;animation-delay:2s;animation-duration:20s}
.tv-particle:nth-child(5){left:48%;animation-delay:9s;animation-duration:17s}
.tv-particle:nth-child(6){left:57%;animation-delay:5s;animation-duration:21s}
.tv-particle:nth-child(7){left:66%;animation-delay:1.5s;animation-duration:19s}
.tv-particle:nth-child(8){left:74%;animation-delay:7s;animation-duration:23s}
.tv-particle:nth-child(9){left:82%;animation-delay:4s;animation-duration:16s}
.tv-particle:nth-child(10){left:88%;animation-delay:8s;animation-duration:25s}
.tv-particle:nth-child(11){left:93%;animation-delay:2.5s;animation-duration:18s}
.tv-particle:nth-child(12){left:97%;animation-delay:6.5s;animation-duration:20s}
@media (prefers-reduced-motion: reduce) {
  .tv-neonfield__glow, .tv-particle { animation: none; }
  .tv-particle { opacity: .5; top: 40%; }
}

/* ---- intensity variants (affect the drifting dots) ---- */
.tv-neonfield--subtle .tv-particle { width: 2px; height: 2px; box-shadow: 0 0 6px var(--accent); }
.tv-neonfield--subtle .tv-neonfield__glow { opacity: .55; }
.tv-neonfield--punchy .tv-particle { width: 5px; height: 5px; box-shadow: 0 0 10px var(--accent), 0 0 22px var(--accent), 0 0 36px var(--accent); }
.tv-neonfield--punchy .tv-neonfield__glow {
  background:
    radial-gradient(ellipse at 15% 25%, color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 45%),
    radial-gradient(ellipse at 85% 75%, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 45%),
    radial-gradient(ellipse at 50% 50%, color-mix(in srgb, var(--accent) 10%, transparent) 0%, transparent 55%);
}

/* ---- slime-bubble variant (the login-tank atmosphere) ---- */
.tv-bubble {
  position: absolute; bottom: -12%; border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, var(--bubble, var(--neon-bubble)), color-mix(in srgb, var(--bubble, var(--neon-bubble)) 70%, transparent));
  box-shadow: 0 0 12px color-mix(in srgb, var(--bubble, var(--neon-bubble)) 90%, transparent),
              0 0 30px color-mix(in srgb, var(--bubble, var(--neon-bubble)) 50%, transparent),
              inset 0 0 8px rgba(255,255,255,.3);
  opacity: 0; animation: tv-bubbleRise 14s infinite ease-in-out;
}
@keyframes tv-bubbleRise {
  0%   { transform: translateY(0) scale(.3); opacity: 0; }
  10%  { opacity: 1; transform: translateY(-12%) scale(1); }
  50%  { opacity: 1; transform: translateY(-55%) scale(1.1) translateX(10px); }
  90%  { opacity: .8; transform: translateY(-95%) scale(.9) translateX(-10px); }
  100% { transform: translateY(-115%) scale(.3); opacity: 0; }
}
.tv-bubble:nth-child(1){left:10%;width:20px;height:20px;animation-duration:15s}
.tv-bubble:nth-child(2){left:20%;width:14px;height:14px;animation-delay:2s;animation-duration:12s}
.tv-bubble:nth-child(3){left:34%;width:24px;height:24px;animation-delay:4s;animation-duration:18s}
.tv-bubble:nth-child(4){left:45%;width:10px;height:10px;animation-delay:1s;animation-duration:14s}
.tv-bubble:nth-child(5){left:55%;width:18px;height:18px;animation-delay:3s;animation-duration:16s}
.tv-bubble:nth-child(6){left:65%;width:12px;height:12px;animation-delay:5s;animation-duration:13s}
.tv-bubble:nth-child(7){left:75%;width:22px;height:22px;animation-delay:.5s;animation-duration:17s}
.tv-bubble:nth-child(8){left:85%;width:16px;height:16px;animation-delay:2.5s;animation-duration:11s}
.tv-bubble:nth-child(9){left:92%;width:8px;height:8px;animation-delay:4.5s;animation-duration:19s}
.tv-bubble:nth-child(10){left:5%;width:26px;height:26px;animation-delay:1.5s;animation-duration:20s}
.tv-bubble:nth-child(11){left:50%;width:13px;height:13px;animation-delay:6s;animation-duration:15s}
.tv-bubble:nth-child(12){left:28%;width:9px;height:9px;animation-delay:7.5s;animation-duration:13s}
@media (prefers-reduced-motion: reduce) {
  .tv-bubble { animation: none; opacity: .55; bottom: auto; top: 40%; }
}

/* ---- slime drips (hanging from the top edge) ---- */
.tv-neonfield__drips { position: absolute; top: 0; left: 0; right: 0; height: 200px; z-index: 1; pointer-events: none; overflow: hidden; }
.tv-drip {
  position: absolute; top: -50px; width: 30px; height: 80px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bubble, var(--neon-bubble)) 40%, transparent) 0%,
    color-mix(in srgb, var(--bubble, var(--neon-bubble)) 15%, transparent) 50%,
    transparent 100%);
  border-radius: 0 0 50% 50%; filter: blur(2px);
  animation: tv-drip 6s ease-in-out infinite;
}
@keyframes tv-drip {
  0%, 100% { transform: translateY(0) scaleY(1); opacity: .6; }
  50%      { transform: translateY(30px) scaleY(1.3); opacity: .85; }
}
.tv-drip:nth-child(1){left:15%;width:25px;animation-delay:0s}
.tv-drip:nth-child(2){left:35%;width:35px;height:100px;animation-delay:2s}
.tv-drip:nth-child(3){left:55%;width:20px;height:60px;animation-delay:1s}
.tv-drip:nth-child(4){left:75%;width:40px;height:90px;animation-delay:3s}
.tv-drip:nth-child(5){left:90%;width:28px;animation-delay:1.5s}
.tv-drip:nth-child(6){left:5%;width:18px;height:70px;animation-delay:2.5s}
@media (prefers-reduced-motion: reduce) { .tv-drip { animation: none; } }

/* ---- hex-grid overlay — the platform's "founding texture" ----
 * The login page's hexagon lattice, quoted across the app (detail-rail,
 * discover). Faint purple stroked hexes drifting slowly upward. Purple is
 * intentional and scheme-independent — it's the founding color. Override
 * the tile alpha/edge via --hex if you must.
 */
.tv-neonfield__hex {
  position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: .7;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='100'%3E%3Cpath d='M28 66L0 50L0 16L28 0L56 16L56 50L28 66L28 100' fill='none' stroke='rgba(196,124,255,0.14)' stroke-width='1'/%3E%3Cpath d='M28 0L28 34L0 50L0 84L28 100L56 84L56 50L28 34' fill='none' stroke='rgba(196,124,255,0.14)' stroke-width='1'/%3E%3C/svg%3E");
  background-size: 56px 100px;
  animation: tv-hex-drift 60s linear infinite;
}
@keyframes tv-hex-drift { from { background-position: 0 0; } to { background-position: 0 -100px; } }
@media (prefers-reduced-motion: reduce) { .tv-neonfield__hex { animation: none; } }

/* ---- perspective grid floor (the labs-wing background) ---- */
.tv-gridfloor {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  background-image:
    linear-gradient(90deg, rgba(196,124,255,0.05) 1px, transparent 1px),
    linear-gradient(rgba(196,124,255,0.05) 1px, transparent 1px);
  background-size: 60px 60px;
  animation: tv-grid-move 25s linear infinite;
}
@keyframes tv-grid-move {
  0% { transform: perspective(500px) rotateX(60deg) translateY(0); }
  100% { transform: perspective(500px) rotateX(60deg) translateY(60px); }
}
@media (prefers-reduced-motion: reduce) { .tv-gridfloor { animation: none; } }

/* ---- galaxy: the real dark-theme PAGE void ----
 * The near-black vertical gradient behind every dark page, lit by three
 * accent-keyed radial glows (so it reads purple under a purple accent,
 * cyan under cyan, …). This is the void; --bg-deep / --bg-primary are the
 * card surfaces that float on top of it. Pair with a faint accent grid.
 */
.tv-galaxy {
  background:
    radial-gradient(ellipse at 20% 20%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 50%),
    radial-gradient(ellipse at 80% 80%, color-mix(in srgb, var(--accent) 6%, transparent) 0%, transparent 50%),
    radial-gradient(ellipse at 50% 50%, color-mix(in srgb, var(--accent) 4%, transparent) 0%, transparent 60%),
    linear-gradient(180deg, #050510 0%, #0a0a1a 50%, #0f0f25 100%);
  background-attachment: fixed;
}
.tv-galaxy-grid { position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(90deg, color-mix(in srgb, var(--accent) 4%, transparent) 1px, transparent 1px),
    linear-gradient(color-mix(in srgb, var(--accent) 4%, transparent) 1px, transparent 1px);
  background-size: 60px 60px; }
