/* ============================================================
   theme.css · blog.syxscott.cn · Shared Design Tokens v1
   ------------------------------------------------------------
   Single source of truth for brand colour, semantic colour,
   spacing, radius, shadow, transition and type scale across
   index.html · status.html · visuals.html.

   Each page keeps its OWN personality (portfolio = light /
   academic, status = dark / engineering, visuals = dark /
   scientific) by consuming these canonical tokens through its
   local aliases — but the brand hue, semantic palette, spacing
   rhythm and motion timing are now identical everywhere.

   Load this file in <head> BEFORE the page's inline <style>
   so inline rules can reference the tokens.
   ============================================================ */

:root {
  /* ── Brand hue: teal / emerald, Earth Science + AI ──
     One family for chrome. The electric-blue secondaries were
     removed once they lost every consumer; the ops pages keep
     blue as a LOCAL accent with a warmed neutral ramp, so these
     shared tokens stay single-hue on purpose. --brand-violet* is
     the one exception: it feeds --acc2, the chart data series. */
  --brand-600: #0d5c63;   /* deep teal - primary on light bg  */
  --brand-500: #0a7285;   /* mid teal                  */
  --brand-400: #14b8a6;   /* emerald accent on dark bg    */
  --brand-300: #2dd4bf;   /* bright/hover on dark */
  --brand-050: #e6faf8;   /* tint / light fill    */

  --brand-violet: #7c3aed;  /* violet tertiary for AI/tech highlights   */
  --brand-violet-dark: #a78bfa; /* violet tertiary (dark) */
  --brand-teal: #0d9488;    /* teal for organic/fossil elements   */
  --brand-teal-dark: #5eead4;/* teal quaternary (dark)   */
  
  /* ── Warm accent for call-to-action elements ── */
  --brand-warm: #d97706;    /* amber for CTAs, highlights */
  --brand-warm-light: #fbbf24;

  /* ── Semantic palette — LIGHT background variants ──────── */
  --sem-success: #15803d;
  --sem-warning: #b45309;
  --sem-danger:  #dc2626;
  --sem-success-glow: rgba(21,128,61,.18);
  --sem-warning-glow: rgba(180,83,9,.18);
  --sem-danger-glow:  rgba(220,38,38,.18);

  /* ── Semantic palette — DARK background variants ───────── */
  --sem-success-d: #34d399;
  --sem-warning-d: #fbbf24;
  --sem-danger-d:  #fb7185;
  --sem-success-d-glow: rgba(52,211,153,.20);
  --sem-warning-d-glow: rgba(251,191,36,.20);
  --sem-danger-d-glow:  rgba(251,113,133,.20);

  /* ── Spacing rhythm (8px base) ─────────────────────────── */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  /* ── Radius scale ──────────────────────────────────────── */
  --radius-xs: 6px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-pill: 999px;

  /* ── Shadow language — LIGHT bg (subtle, cool) ─────────── */
  --shadow-sm: 0 1px 2px rgba(15,23,42,.05);
  --shadow-md: 0 4px 12px rgba(15,23,42,.08);
  --shadow-lg: 0 12px 32px rgba(15,23,42,.10);
  --shadow-xl: 0 24px 60px rgba(15,23,42,.12);

  /* ── Shadow language — DARK bg (deep, ambient) ─────────── */
  --shadow-sm-d: 0 2px 6px rgba(0,0,0,.30);
  --shadow-md-d: 0 8px 24px rgba(0,0,0,.40);
  --shadow-lg-d: 0 16px 44px rgba(0,0,0,.50);
  --shadow-xl-d: 0 24px 60px rgba(0,0,0,.58);
  --glow-brand: 0 0 40px rgba(20,184,166,.15);

  /* ── Motion timing ─────────────────────────────────────── */
  --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
  --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
  --transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);

  /* ── Type family ───────────────────────────────────────── */
  --font-heading: 'DM Serif Display', Georgia, serif;
  --font-body: 'Atkinson Hyperlegible', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;
  --font-dense: 'Atkinson Hyperlegible', system-ui, sans-serif;
      /* Outfit was removed: it duplicated Atkinson's job. Density is now
         carried by size/weight/tracking, not by a second sans. */

  /* ── Type scale (fluid-ish, rem) ───────────────────────── */
  --type-meta: 0.6875rem;   /* 11px */
  --type-small: 0.8125rem;  /* 13px */
  --type-body: 0.9375rem;   /* 15px */
  --type-h4: 1.125rem;      /* 18px */
  --type-h3: 1.375rem;      /* 22px */
  --type-h2: 1.75rem;       /* 28px */
  --type-h1: 2rem;          /* 32px */
}

/* ============================================================
   Shared site footer navigation
   Consistent cross-page nav + copyright line used by
   status.html and visuals.html (index.html has its own richer
   React footer that mirrors the same links).
   ============================================================ */
.site-footer-nav {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--stroke, rgba(128,128,128,.15));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.site-footer-nav .site-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}
.site-footer-nav .site-links a {
  color: var(--muted, inherit);
  text-decoration: none;
  font-size: var(--type-small);
  font-family: var(--font-dense);
  transition: color var(--transition-fast);
}
.site-footer-nav .site-links a:hover,
.site-footer-nav .site-links a[aria-current="page"] {
  color: var(--acc, var(--brand-400));
}
.site-footer-nav .site-copy {
  color: var(--muted2, var(--muted, inherit));
  font-size: var(--type-small);
}
@media (max-width: 600px) {
  .site-footer-nav { flex-direction: column; align-items: flex-start; }
}

/* ============================================================
   Language switcher (.lang-menu)
   ------------------------------------------------------------
   Self-drawn replacement for the native <select> that used to sit
   in every page header.

   WHY NOT A NATIVE <select>: `appearance:none` only ever restyles
   the CLOSED control. The popup list is painted by the OS (Win32 /
   macOS / GTK), so it ignored every token on this site — a flat
   system-grey list that could not follow the theme, and that went
   light-on-light over the portfolio's dark sections. Drawing the
   panel ourselves is what finally makes it obey the palette, the
   nav's dark-section flip and prefers-reduced-motion.

   Colour is expressed through eleven --lm-* aliases. The defaults
   below target the light portfolio surface; status.html and
   visuals.html restate them in their own :root (dark ops palette)
   and [data-theme="light"] blocks. Keep the two lists in sync when
   adding one.

   THE DEFAULTS SIT IN :where(:root) ON PURPOSE — that selector has
   ZERO specificity, so any page-level override beats them no matter
   which stylesheet the build loads last. A plain `:root` here ties
   with `[data-theme="dark"]` at (0,1,0) and loses or wins purely on
   source order, and vite inlines theme.css into a bundled CSS file
   emitted *after* the page's inline <style> — so a plain `:root`
   would silently beat the dark-mode override and the menu would stay
   white in dark mode.
   ============================================================ */
:where(:root) {
  --lm-bg:         rgba(255, 255, 255, 0.88);
  --lm-border:     rgba(13, 92, 99, 0.12);
  --lm-fg:         #1a2e2d;
  --lm-muted:      #6f8483;
  --lm-chip-bg:    rgba(13, 92, 99, 0.07);
  --lm-chip-fg:    #0d5c63;
  --lm-accent:     #0d5c63;
  --lm-accent-soft: rgba(13, 92, 99, 0.09);
  --lm-hover:      rgba(13, 92, 99, 0.06);
  --lm-shadow:     0 1px 2px rgba(15, 23, 42, .05), 0 14px 34px -10px rgba(13, 92, 99, .28);
}

.lang-menu {
  position: relative;
  flex: none;
}

/* Mount point for the vanilla control (status.html / visuals.html):
   lang-menu.js renders .lang-menu inside it. */
.lang-menu-slot {
  display: inline-flex;
  align-items: center;
}

/* ── Trigger ───────────────────────────────────────────────── */
.lang-menu__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2.125rem;
  padding: 0 0.4375rem 0 0.5625rem;
  font-family: var(--font-dense);
  font-size: var(--type-small);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  color: var(--lm-fg);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 10px;
  cursor: pointer;
  transition: background var(--transition-fast),
              border-color var(--transition-fast),
              color var(--transition-fast);
}
.lang-menu__trigger:hover {
  background: var(--lm-hover);
  border-color: var(--lm-border);
}
.lang-menu__trigger:focus-visible {
  outline: 2px solid var(--lm-accent);
  outline-offset: 2px;
}
.lang-menu__globe,
.lang-menu__chevron {
  flex: none;
  display: block;
}
.lang-menu__globe {
  opacity: 0.7;
  transition: opacity var(--transition-fast), color var(--transition-fast);
}
.lang-menu__chevron {
  opacity: 0.55;
  transition: transform var(--transition-base), opacity var(--transition-fast);
}
.lang-menu__trigger:hover .lang-menu__globe,
.lang-menu[data-open="true"] .lang-menu__globe {
  opacity: 1;
  color: var(--lm-accent);
}
.lang-menu[data-open="true"] .lang-menu__chevron {
  opacity: 1;
  transform: rotate(180deg);
}

/* ── Panel ─────────────────────────────────────────────────── */
.lang-menu__panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: 100;
  min-width: 9.75rem;
  max-width: min(14rem, calc(100vw - 2rem));
  margin: 0;
  padding: 0.3125rem;
  list-style: none;
  text-align: left;
  background: var(--lm-bg);
  border: 1px solid var(--lm-border);
  border-radius: 14px;
  box-shadow: var(--lm-shadow);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  transform-origin: top right;
}

/* ── Option rows ───────────────────────────────────────────── */
.lang-menu__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.4375rem;
  border-radius: 9px;
  cursor: pointer;
  color: var(--lm-fg);
  outline: none;
  transition: background var(--transition-fast);
}
.lang-menu__item:hover,
.lang-menu__item:focus-visible,
.lang-menu__item[data-active="true"] {
  background: var(--lm-hover);
}
.lang-menu__item[aria-selected="true"] {
  background: var(--lm-accent-soft);
}
.lang-menu__code {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.3125rem;
  padding: 0 0.3125rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--lm-chip-fg);
  background: var(--lm-chip-bg);
  border-radius: 6px;
  transition: color var(--transition-fast), background var(--transition-fast);
}
.lang-menu__item[aria-selected="true"] .lang-menu__code {
  color: var(--lm-accent);
  background: transparent;
}
.lang-menu__name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-dense);
  font-size: var(--type-small);
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lang-menu__item[aria-selected="true"] .lang-menu__name {
  font-weight: 600;
  color: var(--lm-accent);
}
.lang-menu__check {
  flex: none;
  opacity: 0;
  color: var(--lm-accent);
}
.lang-menu__item[aria-selected="true"] .lang-menu__check {
  opacity: 1;
}

/* ── Compact variant (narrow viewports, next to the burger) ── */
.lang-menu--compact .lang-menu__trigger {
  height: 1.9375rem;
  padding: 0 0.3125rem 0 0.4375rem;
  font-size: 0.75rem;
  gap: 0.25rem;
  border-radius: 9px;
}
.lang-menu--compact .lang-menu__panel {
  min-width: 8.5rem;
}

/* ── Entrance. Only when motion is welcome. ────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .lang-menu__panel {
    animation: lang-menu-in 190ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }
}
@keyframes lang-menu-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

/* ============================================================
   Canvas export bar — appears next to every chart canvas
   (visuals.html). Buttons: PNG / Cite
   ============================================================ */
.canvas-frame {
  position: relative;
  display: inline-block;
  width: 100%;
}
.canvas-frame canvas { display: block; }
.canvas-export-bar {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity var(--transition-fast);
  z-index: 5;
}
.canvas-frame:hover .canvas-export-bar,
.canvas-frame:focus-within .canvas-export-bar { opacity: 1; }
@media (max-width: 600px) { .canvas-export-bar { opacity: 1; } }
.canvas-export-btn {
  background: var(--plot-bg, #0c0f14);
  color: var(--text, #fff);
  border: 1px solid var(--stroke, rgba(255,255,255,.18));
  border-radius: 6px;
  padding: 4px 8px;
  font-family: var(--font-mono, monospace);
  font-size: 0.6875rem;
  font-weight: 600;
  cursor: pointer;
  letter-spacing: 0.02em;
  transition: all var(--transition-fast);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.canvas-export-btn:hover {
  border-color: var(--acc, #60a5fa);
  color: var(--acc, #60a5fa);
  transform: translateY(-1px);
}
.canvas-export-btn:focus-visible {
  outline: 2px solid var(--acc, #60a5fa);
  outline-offset: 2px;
}
.canvas-toast {
  position: fixed;
  bottom: 100px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--good, #34d399);
  color: #0a0c12;
  padding: 8px 16px;
  border-radius: 8px;
  font-family: var(--font-mono, monospace);
  font-size: 0.8125rem;
  font-weight: 600;
  z-index: 10000;
  box-shadow: 0 4px 20px rgba(0,0,0,.4);
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
.canvas-toast.show { opacity: 1; }

/* ============================================================
   SHARED COMPONENT STYLES
   Boot loader · scrollbar · selection · skip link
   Consumed by index.html · status.html · visuals.html
   ============================================================ */

 /* ── Boot loader (spinner) ─────────────────────────────── */
 #boot-loader {
   position: fixed;
   inset: 0;
   display: flex;
   align-items: center;
   justify-content: center;
   z-index: 99999;
   transition: opacity 0.35s ease;
   /* Page-specific background set per-page (--loader-bg token) */
   /* MUST NOT intercept input. The overlay is faded to opacity 0 and
      removed on a timer, but opacity:0 does not disable hit-testing, so
      a loader still in the DOM swallows every click on the page (theme
      toggle, tab bar, palette, RUN) -- verified dead on the live site.
      Never gate interaction on the removal timer firing. */
   pointer-events: none;
 }
 #boot-loader div {
   width: 36px;
   height: 36px;
   border: 3px solid var(--loader-track, rgba(13,92,99,.15));
   border-top-color: var(--loader-fg, #0d5c63);
   border-radius: 50%;
   animation: boot-spin 0.7s linear infinite;
 }
 @keyframes boot-spin { to { transform: rotate(360deg); } }

 /* ── Custom scrollbar ─────────────────────────────────── */
 ::-webkit-scrollbar { width: 7px; height: 7px; }
 ::-webkit-scrollbar-track { background: var(--scrollbar-track, #f0f7f6); border-radius: 4px; }
 ::-webkit-scrollbar-thumb { background: linear-gradient(180deg, #0d5c63, #14b8a6); border-radius: 4px; }
 ::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, #094449, #0d5c63); }
 [data-theme="dark"] ::-webkit-scrollbar-track { background: rgba(255,255,255,.05); }
 [data-theme="dark"] ::-webkit-scrollbar-thumb { background: linear-gradient(180deg, #14b8a6, #2dd4bf); }
 [data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, #0d5c63, #14b8a6); }

 /* ── Text selection ────────────────────────────────────── */
 ::selection {
   background: var(--selection-bg, rgba(13,92,99,.18));
   color: var(--selection-color, #1a2e2d);
 }

 /* ── Skip link (accessibility) ─────────────────────────── */
 .skip-link {
   position: absolute;
   left: -9999px;
   top: 0;
   z-index: 99999;
   padding: 12px 18px;
   background: var(--skip-bg, var(--acc, #60a5fa));
   color: var(--skip-color, #fff);
   font-weight: 600;
   border-radius: 0 0 8px 0;
   text-decoration: none;
   transition: none;
 }
 .skip-link:focus {
   position: fixed;
   left: 0 !important;
   top: 0 !important;
   transform: none !important;
 }
