/* ============================================================
   "Harbor Ledger" design tokens
   A calm operations ledger: blue-tinted ink and a soft steel-
   blue accent on cool near-white paper. Wide-tracked micro-
   labels, data set in mono on ledger baselines, soft 10px
   surfaces. Green is reserved for success states.
   Type: Bricolage Grotesque (display) · Work Sans (body)
         · Spline Sans Mono (data) — see fonts.css
   ============================================================ */
:root {
  /* Light theme (default) — "Raised Cards".
     This INVERTS the surface model. The page is a recessed ground at CIE L*
     93.5 and cards rise above it at 97.9; previously (and in every earlier
     Harbor light theme) cards sat BELOW the page. Near-white is therefore
     confined to card area instead of covering the whole viewport, which is
     what the glare actually was — the old page was L* 98.2 with only a 2.9 L*
     step down to a card, so the eye read one undifferentiated bright field.

     Dark inks are solved against the PAGE, which is now the darkest light
     surface and so the hardest one for them:
       body text 12.6:1 · secondary 5.2:1 · control borders 3.2:1
     Everything therefore clears by a wider margin on a card.

     Two long-standing defects are fixed on the way past:
       - modals were literally #ffffff, the brightest object on any screen.
       - --border-strong feeds --input-border and measured 1.56:1, under the
         3:1 WCAG 1.4.11 requires for form-control boundaries. Now 3.2:1.

     The brand accent (#29618e) is deliberately unchanged — it still clears AA
     on the recessed ground (5.58:1 page, 6.23:1 card), so this is a surface
     change, not a rebrand.

     IMPORTANT — why --background-color is the LIGHTEST value here, not the
     page. 100 CSS rules already use var(--background-color) as a tile/panel
     fill (.board-card is one), because under every previous light theme it was
     the lightest token. Inverting it in place turned those tiles into holes:
     .board-column paints --card-background and .board-card paints
     --background-color, so cards sank into their columns. Rather than rewrite
     100 rules, the page ground moved to its own token:
       --page-bg           recessed ground, painted by body (see App.css)
       --background-color  the default RAISED surface — meaning unchanged
     Dark mode sets --page-bg to the same value as --background-color, so it
     renders exactly as before; only light gains the extra depth level. */
  --font-body: 'Work Sans', -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Bricolage Grotesque', 'Work Sans', -apple-system, sans-serif;
  --font-mono: 'Spline Sans Mono', ui-monospace, SFMono-Regular, monospace;
  --radius: 10px;
  --radius-sm: 7px;

  /* Depth stack, darkest first:
     surface-2 91.0 · page-bg 93.5 · card-hover 92.5 · card-background 95.4
     · background-color 97.9 · modal 98.9
     A board column paints --card-background and a board card paints
     --background-color, so a card now sits +2.5 L* ABOVE its column, with the
     page below both. */
  --surface-2: #dfe6ee;
  --accent-tint: #dce8f4;
  --accent-tint-text: #16405f;
  --border-strong: #777f87;

  --page-bg: #e8edf3;
  --background-color: #f7f9fc;
  /* One level ABOVE the page, in both themes — the standout fill for list
     items and cards. --background-color cannot do this job: it is the raised
     surface in light but the page itself in dark, so a card painted with it
     vanished into the background there. Light raises by +4.4 L*, dark by
     +5.4 L*; the direction flips, the intent does not. */
  --surface-raised: #f7f9fc;
  /* Chat window surface: a step below the default raised surface so the
     dock/chat reads as its own panel (see dark block for the inverse). */
  --chat-surface: #eef2f8;
  --text-color: #20262f;
  --secondary-text-color: #545e69;
  --border-color: #bcc2ca;
  --border-color-rgb: 188, 194, 202;
  --card-background: #eef2f8;
  --card-background-rgb: 238, 242, 248;
  /* Decoupled from --surface-2 (which dark mode still uses). A .card sits on
     the raised surface at 97.9, so its hover presses one level down the stack
     to the container level — 2.5 L*. Falling to the deep well would be 6.9. */
  --card-hover: #eef2f8;
  --card-shadow: 0 2px 8px rgba(32, 38, 47, 0.1); /* Kept for modals */
  --button-primary-bg: #29618e;
  --button-primary-text: #ffffff;
  --button-primary-hover: #204e75;
  --button-light-bg: transparent;
  --button-light-text: #29618e;
  --button-light-border: var(--border-strong);
  --button-pill-hover-bg: #eef2f8;
  --input-border: var(--border-strong);
  --dropdown-bg: #f7f9fc;
  --dropdown-hover: #eef2f8;
  --pricing-tier-bg: #eef2f8;
  --link-color: #29618e;
  --visited-link-color: #29618e; /* Same as link-color */
  --footer-text-color: #545e69;
  /* The app header is part of the page, not a panel floating on it. */
  --header-bg: var(--page-bg);
  --warning-bg: #f7f0df;
  --warning-border: #e0d2b1;
  --warning-text: #855c1a;
  --warning-heading: #855c1a;
  --success-text: #1f6f54;
  --danger-text: #b3372f;
  --danger-border: #d8a09b;
  --danger-button-bg: #b3372f;
  --danger-button-text: #ffffff;
  --danger-button-hover: #922b25;
  --modal-bg-rgb: 250, 252, 254;
  --scrollbar-thumb: rgba(32, 38, 47, 0.28);
  --scrollbar-thumb-hover: rgba(32, 38, 47, 0.45);
  /* Text selection. Stated rather than left to the browser, whose default is
     derived from the operating system's light/dark setting and not from the
     theme in force here: a reader on a dark system reading a light page got a
     highlight the same colour as the text it covered, so a selection was
     invisible. Both pairs below clear 6:1. */
  --selection-bg: #b8d4ea;
  --selection-text: #16405f;

  /* Categorical chart series — read at runtime by
     ui/src/databases/charts/chart-theme.ts. Slots are assigned in the caller's
     stable series order and never re-derived from rank, so a series keeps its
     colour when the data changes. */
  --chart-series-1: #2a78d6;
  --chart-series-2: #1baf7a;
  --chart-series-3: #eda100;
  --chart-series-4: #008300;
  --chart-series-5: #4a3aa7;
  --chart-series-6: #e34948;
  --chart-series-7: #e87ba4;
  --chart-series-8: #eb6834;
  --chart-other: #898781;
}

[data-theme='dark'] {
  --surface-2: #242e39;
  --accent-tint: #1d2c3b;
  --accent-tint-text: #c3daf0;
  --border-strong: #3e4a57;

  --background-color: #14181d;
  /* Chat window surface: a touch lighter than the page in dark mode. */
  --chat-surface: #191e25;
  --text-color: #e4e7ea;
  --secondary-text-color: #939ea9;
  --border-color: #2c343d;
  --border-color-rgb: 44, 52, 61;
  --card-background: #1c232b;
  --card-background-rgb: 28, 35, 43;
  --card-hover: var(--surface-2);
  --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); /* Kept for modals */
  --button-primary-bg: #6ba3d4;
  --button-primary-text: #0f1d2a;
  --button-primary-hover: #8ab8de;
  --button-light-bg: transparent;
  --button-light-text: #6ba3d4;
  --button-light-border: var(--border-strong);
  --button-pill-hover-bg: #1c232b;
  --input-border: var(--border-strong);
  --dropdown-bg: #1c232b;
  --dropdown-hover: #242e39;
  --pricing-tier-bg: #1c232b;
  --link-color: #6ba3d4;
  --visited-link-color: #6ba3d4; /* Same as link-color */
  --footer-text-color: #939ea9;
  --header-bg: #14181d;
  --warning-bg: #292213;
  --warning-border: #4b3e20;
  --warning-text: #d9b36a;
  --warning-heading: #d9b36a;
  --success-text: #58b892;
  --danger-text: #e58079;
  --danger-border: #754541;
  --danger-button-bg: #e58079;
  --danger-button-text: #261311;
  --danger-button-hover: #f09b95;
  --modal-bg-rgb: 28, 35, 43;
  --scrollbar-thumb: rgba(228, 231, 234, 0.26);
  --scrollbar-thumb-hover: rgba(228, 231, 234, 0.45);
  --selection-bg: #2f4a66;
  --selection-text: #eaf2fa;
  /* Dark has no separate recessed ground: the page and the default surface are
     the same value, exactly as before the light theme gained the extra level.
     Dark therefore renders identically to how it always has. */
  --page-bg: var(--background-color);
  /* See the light block: raised means LIGHTER here, so this steps up off the
     page rather than down. Same value cards carried before the token existed. */
  --surface-raised: #1c232b;

  /* Categorical chart series — see the light block for how these are consumed. */
  --chart-series-1: #3987e5;
  --chart-series-2: #199e70;
  --chart-series-3: #c98500;
  --chart-series-4: #008300;
  --chart-series-5: #9085e9;
  --chart-series-6: #e66767;
  --chart-series-7: #d55181;
  --chart-series-8: #d95926;
  --chart-other: #898781;
}

/* Legacy token aliases — older page CSS and inline styles reference these
   names; they resolve to the Harbor tokens above (per-theme automatically,
   since var() resolution is dynamic). */
:root,
[data-theme='dark'] {
  --primary-color: var(--link-color);
  --primary-color-dark: var(--button-primary-hover);
  --primary-text-color: var(--text-color);
  --text-primary: var(--text-color);
  --text-secondary: var(--secondary-text-color);
  --text-muted: var(--secondary-text-color);
  --danger: var(--danger-text);
  --danger-color: var(--danger-text);
  --error-color: var(--danger-text);
  --warning-text-color: var(--warning-text);
  --hover-background: var(--card-hover);
  --hover-color: var(--card-hover);
  --surface-hover-bg: var(--card-hover);
  --surface-bg: var(--card-background);
  --surface-subtle: var(--card-background);
  --secondary-background-color: var(--card-background);
  --panel-background: var(--card-background);
  --tag-background: var(--card-background);
  --input-bg: var(--background-color);
  --email-line: var(--border-color);

  /* Chart chrome. Derived rather than literal so plot surfaces can never drift
     out of sync with the theme the way the hardcoded copies in the chart
     components did — those still carried pre-Harbor values. */
  --chart-surface: var(--card-background);
  --chart-grid: var(--border-color);
  --chart-axis-text: var(--secondary-text-color);
}

:root {
  --danger-bg: #f8eae8;
}

[data-theme='dark'] {
  --danger-bg: #33211f;
}

/* Selecting text has to show, whichever theme is on and whatever the reader's
   operating system is set to. Stating the colour also fixes the one place the
   browser's default could never get right: white-on-dark runs such as a code
   block keep their own text colour under the browser's highlight, so a light
   highlight landed white text on a near-white wash. */
::selection {
  background-color: var(--selection-bg);
  color: var(--selection-text);
}

/* Theme toggle container and button styles */
.theme-toggle-container {
  display: flex;
  align-items: center;
}

.theme-mode-text {
  font-size: 0.85rem;
  color: var(--secondary-text-color);
  margin-right: 0.5rem;
}

.theme-mode-text.clickable {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.3s;
}

.theme-mode-text.clickable:hover {
  color: var(--link-color);
  /* Stay link-like: override the global button:hover background */
  background: none;
}

.theme-toggle-button {
  background: none;
  border: none;
  color: var(--text-color);
  font-size: 1.2rem;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.3s;
}

.theme-toggle-button:hover {
  background-color: var(--dropdown-hover);
}

/* The toggle's own label and icon are STACKED, not swapped.

   Swapping the element (React) or the text/hidden attribute (marketing) is a
   replacement, and a replacement cannot transition — so the control snapped to
   its new state while the rest of the page eased, which read as a glitch. Both
   variants now live in the DOM permanently and [data-theme] decides which is
   opaque, which is a property change and therefore animatable.

   Grid stacking rather than absolute positioning keeps the button sized to the
   wider of the two labels, so nothing reflows mid-fade.

   aria-hidden on the container is deliberate: both buttons already carry an
   aria-label naming the destination, so exposing two stacked strings would
   have a screen reader announce the control twice. */
.theme-swap {
  display: inline-grid;
  align-items: center;
  justify-items: center;
}

/* The icon's stack holds an SVG and nothing else, but its spans still carry a
   text line-box strut, which made the icon BUTTON 3px taller than before. The
   container centres its two buttons, so that surplus pushed the label down by
   half of it — the 1.5px drop against the icon. Zeroing the line height sizes
   the stack to the glyph again. */
.theme-toggle-button .theme-swap,
.theme-toggle-button .theme-swap > * {
  line-height: 0;
}

.theme-swap > * {
  grid-area: 1 / 1;
  opacity: 0;
}

[data-theme='dark'] .theme-swap-dark,
html:not([data-theme='dark']) .theme-swap-light {
  opacity: 1;
}

/* Only while a user-initiated switch is running. On first paint the correct
   variant is simply already opaque, with no transition to play. */
html.theme-fading .theme-swap > * {
  transition: opacity 800ms ease !important;
}

/* Footer layout for theme toggle */
.footer-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  /* Constrain to the content column by default so the footer follows the
     active page's width (see PageShell's data-page-shell on <html>). */
  max-width: 900px;
  margin-inline: auto;
}

/* Wide (workspace) routes let the footer span the full content width. */
html[data-shell-variant='wide'] .footer-content {
  max-width: none;
}

/* Mobile view - move theme toggle to its own line */
@media (max-width: 768px) {
  .footer-content {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }

  .theme-toggle-container {
    align-self: flex-start;
  }
}

/* App-wide search field: a rounded pill with a leading icon and a borderless
   input. Use `.search-pill` on a container wrapping an icon + <input>. */
.search-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border: 1px solid var(--border-color);
  border-radius: 20px;
  /* A field, so it joins the raised stack rather than showing the page through
     itself. --surface-raised (not --input-bg) because that token means "one
     level above the page" in BOTH themes; --input-bg resolves to the page
     itself in dark, which left the pill invisible there. */
  background-color: var(--surface-raised);
  color: var(--secondary-text-color);
  min-width: 240px;
  position: relative;
}

.search-pill input {
  border: none;
  outline: none;
  background: none;
  width: 100%;
  padding: 0;
  color: var(--text-color);
  font-size: 16px;
}

/* Remove underlines from all links and make visited links the same color as non-visited links */
a,
a:link,
a:visited {
  text-decoration: none;
  color: var(--visited-link-color);
}

/* Fix Chrome autofill background color in dark mode */
@media (prefers-color-scheme: dark) {
  input:-webkit-autofill,
  input:-webkit-autofill:hover,
  input:-webkit-autofill:focus,
  input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 30px var(--card-background) inset !important;
    -webkit-text-fill-color: var(--text-color) !important;
    transition: background-color 5000s ease-in-out 0s;
  }
}

/* Apply the same fix when dark mode is explicitly set */
[data-theme='dark'] input:-webkit-autofill,
[data-theme='dark'] input:-webkit-autofill:hover,
[data-theme='dark'] input:-webkit-autofill:focus,
[data-theme='dark'] input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 30px var(--card-background) inset !important;
  -webkit-text-fill-color: var(--text-color) !important;
  transition: background-color 5000s ease-in-out 0s;
}

/* Disabled input styling */
input:disabled,
textarea:disabled,
select:disabled {
  background-color: var(--dropdown-hover);
  color: var(--secondary-text-color);
  border-color: var(--border-color);
  cursor: not-allowed;
  opacity: 0.7;
}

/* ── Theme crossfade ──────────────────────────────────────────────
   Added to <html> by ThemeProvider (ui/src/state/ThemeContext.tsx) and the
   marketing toggle (marketing/src/scripts/theme-toggle.ts) for the length of
   the fade, then removed.

   Applied ONLY when a person presses the toggle. Not on first paint, where a
   page that fades itself in reads as a rendering bug, and not when the OS
   preference flips, which the user did not ask to watch. Both of those call
   sites set the theme without this class.

   The !important is load-bearing: it has to beat each component's own
   transition for the duration, or half the page eases while the other half
   snaps. */
html.theme-fading,
html.theme-fading *,
html.theme-fading *::before,
html.theme-fading *::after {
  transition:
    background-color 800ms ease,
    border-color 800ms ease,
    color 800ms ease,
    fill 800ms ease,
    stroke 800ms ease,
    box-shadow 800ms ease,
    outline-color 800ms ease !important;
  transition-delay: 0s !important;
}

/* App.css already silences transitions under reduced motion, but its selector
   is bare `*` — this block is more specific and would otherwise win. Repeat the
   opt-out here so the fade cannot outrank it. */
@media (prefers-reduced-motion: reduce) {
  html.theme-fading,
  html.theme-fading *,
  html.theme-fading *::before,
  html.theme-fading *::after {
    transition: none !important;
  }
}
