/* =========================================================================
 * deepkernels — design tokens
 * Aesthetic: PANDORA. The void, ONE ink — now white — and a live particle
 * field behind every page. Everything that used to be carried by hue is
 * carried by density instead; the two hues this theme owns (cyan, amber)
 * are spent on STATE and nothing else.
 *
 * The values below ARE pandora/pandora.css, which is the marketing site at
 * pandora-world.com. That is the point: a visitor crosses from the site to
 * the app in one click, and the ground must not shift under them. The app
 * and the site are one system rather than two that drift.
 *
 * These tokens are the source of truth. tailwind.config.js mirrors them;
 * if you change a value here, change it there too and rebuild
 * (./build-css.sh).
 *
 * ── Granite → pandora, and why it was a values-only change ─────────────
 * Every template addresses colour through semantic names (bg-bg-surface,
 * text-text-primary, border-border) rather than literal values, so
 * inverting the whole app — marketing, dashboard, auth, billing — was a
 * change to these two files and the backdrop in base.html. No template
 * needed its classes edited. Keep it that way: if you find yourself
 * writing a hex literal in a template, add a token instead.
 *
 * Two things do NOT come from tokens, and both are deliberate:
 *   • the critical shell in base.html, which repeats the ground and the ink
 *     as literals so a stale copy of this file cannot cost legibility; and
 *   • the two override rules at the FOOT of this file, which need to beat
 *     Tailwind utilities and say why there.
 * ========================================================================= */

:root {
  /* -----------------------------------------------------------------------
   * Color — Surface palette
   *
   * PANDORA. `base` is the void the particle field is drawn on, and it is
   * the same #07090D the marketing site and the renderer both use. Surfaces
   * step up by getting LIGHTER AND MORE TRANSPARENT rather than more
   * opaque: the field is live behind every page, and a card that blocks it
   * out reads as a hole punched in the scene.
   *
   * That is why `surface` and `elevated` are rgba, not hex. They are the
   * marketing site's `--glass` and its button fill, and they only work with
   * the blur that the rule further down this file applies — see the note
   * there. `sunken` is the one surface that goes DOWN, for inset wells
   * (code blocks, empty panels) that should read as cut into the page.
   * ----------------------------------------------------------------------- */
  --color-bg-base:           #07090D;
  --color-bg-sunken:         #04060A;
  --color-bg-surface:        rgba(7, 10, 16, 0.58);
  /* A lift rather than a tint: on a dark theme "elevated" cannot be darker
     than the plate it sits on, so it adds light instead of subtracting it.
     Same value as the marketing site's button fill. */
  --color-bg-elevated:       rgba(255, 255, 255, 0.06);
  --color-bg-overlay:        rgba(7, 9, 13, 0.92);

  /* The page wash, and the FALLBACK for the whole backdrop.
   *
   * These are the two accents — cyan for containment, amber for release —
   * at the density the marketing site uses when the scene cannot run
   * (`.no-scene` in pandora.css). On a page where the canvas boots, this
   * sits underneath it and is never seen. On one where it does not — no
   * WebGL, no canvas2d, JavaScript off — this IS the design, which is why
   * it is a composed gradient rather than a flat colour.
   * Apply with: background: var(--gradient-page); */
  --gradient-page:
    radial-gradient(60rem 40rem at 68% 18%, rgba(79, 240, 220, 0.10), transparent 70%),
    radial-gradient(48rem 34rem at 22% 78%, rgba(255, 138, 77, 0.08), transparent 70%),
    var(--color-bg-base);

  /* -----------------------------------------------------------------------
   * Color — Brand / accent
   *
   * The accent is still the ink — the rule survives the inversion intact,
   * only the ink is now white. A filled button is the type colour with the
   * page reversed out of it, so emphasis is carried by density and area
   * rather than by introducing a second hue. The two hues this theme does
   * own (cyan, amber) are reserved for STATE, further down.
   *
   * `accent-strong` is reserved for emphasis and focus rings.
   * `accent-muted` is for hover backgrounds and subtle fills.
   *
   * NOTE `accent-contrast` is the VOID: it is the text colour that sits ON
   * a filled accent button, and the ink is now white. It inverted with
   * everything else, and getting it backwards is the single most visible
   * way to break the theme — white on white, on the primary button.
   * ----------------------------------------------------------------------- */
  --color-accent:            #FFFFFF;
  --color-accent-hover:      rgba(255, 255, 255, 0.86);
  --color-accent-strong:     #FFFFFF;
  --color-accent-muted:      rgba(255, 255, 255, 0.08);
  --color-accent-ring:       rgba(255, 255, 255, 0.45);
  --color-accent-contrast:   #07090D;   /* text on accent fills */

  /* Secondary — the second ink, not a second hue. The marketing site's
   * `--soft`: what body copy is set in, and what anything that needs to sit
   * back from full white without reading as disabled takes. */
  --color-secondary:         rgba(255, 255, 255, 0.72);
  --color-secondary-hover:   rgba(255, 255, 255, 0.86);
  --color-secondary-muted:   rgba(255, 255, 255, 0.08);

  /* -----------------------------------------------------------------------
   * Color — Text
   *
   * The ink, and the three densities below it. White and two alphas of it,
   * so type sits in the same family as the field behind it.
   *
   * `tertiary` DIVERGES from the marketing site's `--faint` (0.46), and for
   * the same reason it diverged from the dashboard's muted under granite:
   * this value is spent on 11px uppercase captions, the smallest type in
   * the app, and it is read over a LIVE field rather than a flat plate. The
   * site can afford 0.46 because its captions sit on a scrim with almost
   * nothing else on it; a table of eleven-pixel column headers cannot.
   *
   * 0.58 is the same relation, one step denser. Anything below ~0.55 starts
   * failing where a caption crosses a bright knot of particles — and that
   * is the failure you will not see while testing on a calm frame, because
   * the frame moves.
   * ----------------------------------------------------------------------- */
  --color-text-primary:      #FFFFFF;
  --color-text-secondary:    rgba(255, 255, 255, 0.72);
  --color-text-tertiary:     rgba(255, 255, 255, 0.58);
  --color-text-disabled:     rgba(255, 255, 255, 0.32);
  --color-text-on-accent:    #07090D;

  /* -----------------------------------------------------------------------
   * Color — Borders & dividers
   *
   * Light-on-dark, so these are alpha-white. `default` is the marketing
   * site's `--hair` exactly — the one value every border on that site uses,
   * and the reason its plates read as edges rather than as boxes.
   * ----------------------------------------------------------------------- */
  --color-border-subtle:     rgba(255, 255, 255, 0.08);
  --color-border-default:    rgba(255, 255, 255, 0.16);
  --color-border-strong:     rgba(255, 255, 255, 0.28);
  --color-border-accent:     rgba(255, 255, 255, 0.45);

  /* -----------------------------------------------------------------------
   * Color — Semantic / status
   *
   * The ONE place hue survives, and it survives on purpose: these encode
   * meaning, and a state that differs from its neighbour only in density
   * cannot be read at a glance.
   *
   * These are now the SCENE'S OWN two accents rather than a generic status
   * ramp, and the mapping is not arbitrary — it is the vocabulary the field
   * already speaks. Cyan is containment holding; amber is release, the
   * state the vessel turns when the cloud escapes it (see `.vessel.breach`
   * and the containment chip on the marketing site). So a healthy row and a
   * calm simulation are the same colour, and a failed job and a breached
   * boundary are the same colour, across the whole product.
   *
   * The old green/red pair is also simply wrong here: #0CA30C and #D03B3B
   * are chosen against white and go muddy on the void, where a saturated
   * mid-tone has nothing to contrast with.
   *
   * `info` is NOT chromatic — informational is the default state, so it
   * takes the ink like everything else, which is now white.
   * ----------------------------------------------------------------------- */
  --color-success:           #4FF0DC;
  --color-success-muted:     rgba(79, 240, 220, 0.14);
  --color-warning:           #FFD166;
  --color-warning-muted:     rgba(255, 209, 102, 0.14);
  --color-danger:            #FF8A4D;
  --color-danger-muted:      rgba(255, 138, 77, 0.14);
  --color-info:              #FFFFFF;
  --color-info-muted:        rgba(255, 255, 255, 0.08);

  /* -----------------------------------------------------------------------
   * Typography — Families
   *
   * Display: Bodoni Moda, ITALIC — the marketing site's face, and the one
   *   the wordmark is set in. An italic variable cut, 400–900, so weight
   *   utilities still work; there is no roman, which is why the rule at
   *   the foot of this file pins the posture. Used for h1/h2 and all-caps
   *   eyebrow labels.
   * Body:    Inter — neutral, excellent at 14–18px over the field.
   * Mono:    JetBrains Mono — for tabular numbers, code, log streams.
   * ----------------------------------------------------------------------- */
  --font-display: "Bodoni Moda", "Libre Bodoni", Georgia, serif;
  --font-body:    "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:    "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;

  /* -----------------------------------------------------------------------
   * Typography — Type scale (1.250 ratio, "major third")
   *
   * Sizes are tuned for a dashboard where dense text is common.
   * `step-0` is the body baseline (16px).
   * ----------------------------------------------------------------------- */
  --text-2xs:   0.6875rem;   /* 11px — micro labels, table footnotes */
  --text-xs:    0.75rem;     /* 12px — eyebrow caps, badges */
  --text-sm:    0.875rem;    /* 14px — nav, secondary UI */
  --text-base:  1rem;        /* 16px — body */
  --text-lg:    1.125rem;    /* 18px — lead paragraphs */
  --text-xl:    1.25rem;     /* 20px — card titles */
  --text-2xl:   1.5rem;      /* 24px — section headings */
  --text-3xl:   1.875rem;    /* 30px — page headings */
  --text-4xl:   2.5rem;      /* 40px — hero subheads */
  --text-5xl:   3.25rem;     /* 52px — hero displays */
  --text-6xl:   4.25rem;     /* 68px — splash */

  /* -----------------------------------------------------------------------
   * Typography — Weights & line-heights
   * ----------------------------------------------------------------------- */
  --font-weight-regular:  400;
  --font-weight-medium:   500;
  --font-weight-semibold: 600;
  --font-weight-bold:     700;

  --leading-tight:   1.15;
  --leading-snug:    1.30;
  --leading-normal:  1.50;
  --leading-relaxed: 1.65;

  /* -----------------------------------------------------------------------
   * Typography — Letter-spacing
   *
   * The Epoch nav and "CLICK THE LOGO" eyebrow use heavy tracking.
   * Reach for `widest` on all-caps labels; never on body copy.
   * ----------------------------------------------------------------------- */
  --tracking-tighter: -0.02em;
  --tracking-tight:   -0.01em;
  --tracking-normal:  0em;
  --tracking-wide:    0.025em;
  --tracking-wider:   0.10em;
  --tracking-widest:  0.20em;

  /* -----------------------------------------------------------------------
   * Spacing scale (4px base)
   *
   * Tailwind's default scale + a few extras for the wide gutters this
   * design uses. Use multiples of `--space-2` (8px) wherever practical.
   * ----------------------------------------------------------------------- */
  --space-0:    0;
  --space-px:   1px;
  --space-0_5:  0.125rem;   /*  2px */
  --space-1:    0.25rem;    /*  4px */
  --space-1_5:  0.375rem;   /*  6px */
  --space-2:    0.5rem;     /*  8px */
  --space-3:    0.75rem;    /* 12px */
  --space-4:    1rem;       /* 16px */
  --space-5:    1.25rem;    /* 20px */
  --space-6:    1.5rem;     /* 24px */
  --space-8:    2rem;       /* 32px */
  --space-10:   2.5rem;     /* 40px */
  --space-12:   3rem;       /* 48px */
  --space-16:   4rem;       /* 64px */
  --space-20:   5rem;       /* 80px */
  --space-24:   6rem;       /* 96px */
  --space-32:   8rem;       /* 128px */
  --space-40:   10rem;      /* 160px */
  --space-48:   12rem;      /* 192px */
  --space-64:   16rem;      /* 256px */

  /* -----------------------------------------------------------------------
   * Border radii
   *
   * The reference design uses small radii — never pillowy. Buttons,
   * inputs, and cards sit between 4–10px. `--radius-pill` is the only
   * exception, reserved for tags / status chips.
   * ----------------------------------------------------------------------- */
  --radius-none:  0;
  --radius-xs:    2px;
  --radius-sm:    4px;    /* buttons, inputs */
  --radius-md:    6px;
  --radius-lg:    10px;   /* cards, panels */
  --radius-xl:    14px;   /* modals */
  --radius-2xl:   20px;
  --radius-pill:  9999px;

  /* -----------------------------------------------------------------------
   * Elevation — shadows tuned for the stone canvas
   *
   * Soft ink drops, no inset highlight: the white top-edge line the dark
   * theme used is invisible against a near-white surface, and the heavy
   * black drops it paired with read as grime rather than as lift.
   *
   * `accent-glow` is no longer a glow. Under granite there is no second
   * hue to halo with, so the primary CTA gets a hairline of its own ink
   * plus a drop — weight, not colour. Keep this in step with `boxShadow`
   * in tailwind.config.js, which is the copy that actually compiles.
   * ----------------------------------------------------------------------- */
  /* Deeper and darker than the light theme's, because a shadow works by
     darkening what is behind it and there is much less headroom above
     #07090D than there was above #FCFCFB. A 6%-black drop that read as
     lift on stone is invisible on the void. */
  --shadow-sm:  0 1px 2px rgba(0, 0, 0, 0.40);
  --shadow-md:  0 2px 10px rgba(0, 0, 0, 0.45);
  --shadow-lg:  0 12px 36px rgba(0, 0, 0, 0.55);
  /* The accent is white now, so its "glow" is a real glow rather than a
     dark halo — a hairline of the ink plus a soft spread of it. */
  --shadow-accent-glow:
    0 0 0 1px rgba(255, 255, 255, 0.22),
    0 8px 24px rgba(0, 0, 0, 0.45);

  /* -----------------------------------------------------------------------
   * Motion
   * ----------------------------------------------------------------------- */
  --duration-fast:   120ms;
  --duration-base:   200ms;
  --duration-slow:   320ms;
  --ease-standard:   cubic-bezier(0.2, 0, 0, 1);
  --ease-emphasized: cubic-bezier(0.3, 0, 0, 1);

  /* -----------------------------------------------------------------------
   * Layout
   * ----------------------------------------------------------------------- */
  --container-max:   1440px;
  --container-pad:   var(--space-8);
  --sidebar-width:   240px;
  --topnav-height:   64px;
  --z-base:          0;
  --z-sticky:        100;
  --z-overlay:       200;
  --z-modal:         300;
  --z-toast:         400;
}

/* ---------------------------------------------------------------------------
 * Base resets that lean on the tokens. Optional — drop if you prefer to
 * stay close to Tailwind's preflight.
 * --------------------------------------------------------------------------- */
/* NOTE: `background` and `color` here are intentionally ALSO set inline in
   templates/base.html, with literal values, and that inline block wins
   (same specificity, declared later).

   That duplication is on purpose. A cached copy of this file can lag a
   freshly-compiled template — and when it did, the old navy canvas sat
   under the new near-black text and the headline vanished. Legibility
   must not depend on a file that can go stale. Referencing the variables
   from base.html would not help: a stale file still defines them, just
   with the wrong values.

   If you change either value, change it in BOTH places. There is no
   cache-buster to bump — crate::assets hashes the bytes on disk. */
html {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: var(--leading-normal);
  color: var(--color-text-primary);
  background: var(--gradient-page);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  margin: 0;
  min-height: 100vh;
  background: transparent;
}

/* ---------------------------------------------------------------------------
 * The backdrop — layer 2 of 2 (layer 1 is --gradient-page above).
 *
 * A fixed, non-interactive contour field behind everything. Static: drawn
 * paths, not a running simulation, and not decoration either — it is the
 * Expected-Free-Energy decision surface, generated from the shipped cost
 * table by dk-graphics. See the header of partials/_manifold.html.
 *
 * ITS LAYOUT RULES ARE NOT HERE. `.manifold`, `.manifold svg` and
 * `.manifold-content` live in an inline <style> in templates/base.html,
 * next to the markup they apply to.
 *
 * That is deliberate. Those rules are structural: without them the SVG
 * lands in normal flow, pushes the whole page below the fold and blankets
 * every click target. This file is served from /static with no
 * fingerprinting, so it can be cached stale against a freshly-compiled
 * template — and a stale copy must only ever cost you colour, never
 * layout. Don't move them back here.
 * --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  /* Nothing animates today, but if the contours are ever animated (sweeping
     a level, or morphing between traffic states) it must respect this — the
     hook is here so that's not forgotten. */
  .manifold svg * { animation: none !important; }

  /* The arrival mark, and note it is `display`, NOT `animation` like its
     neighbour above. Turning the animation off would be actively worse than
     leaving it on: the mark is a full-viewport opaque layer that the
     animation is what CLEARS, so stopping it strands the layer over the page
     forever. Anything that suppresses this mark has to remove it.

     Belt and braces. The mark's own stylesheet already refuses to show
     itself outside `prefers-reduced-motion: no-preference`, and that copy is
     the load-bearing one because it ships inline with the markup and cannot
     go stale against it. This file can, which is exactly why the rule here
     is the fail-safe direction. */
  .entrance { display: none !important; }
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: var(--font-weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--color-text-primary);
  margin: 0;
}

/* Utility class for the all-caps eyebrow labels seen in the reference design. */
.eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--tracking-widest);
  color: var(--color-text-secondary);
}

/* Focus state — accent ring, never the browser default on a dark UI. */
:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--color-bg-base), 0 0 0 4px var(--color-accent-ring);
  border-radius: var(--radius-sm);
}

/* Off-screen wrapper for the contact form's honeypot field.
 *
 * Was an inline style="…" attribute until the CSP work. A nonce covers
 * <style> ELEMENTS only, never style ATTRIBUTES — those need
 * 'unsafe-hashes' — so this had to become a class for style-src to drop
 * 'unsafe-inline' entirely. See src/security/headers.rs.
 *
 * Deliberately not `display:none`, and deliberately not Tailwind's
 * `.sr-only`: the point of a honeypot is that a bot fills it in, so the
 * field must look real, and a well-known utility class name is precisely
 * what a more careful bot skips. Declarations are unchanged from the
 * attribute this replaced. */
.offscreen-field {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------------------------------------------------------------------------
 * PANDORA: the two rules that make the token swap actually look like the
 * marketing site rather than like the old theme with the lights turned off.
 *
 * Both are written `:root [class~="…"]` — (0,2,0) — rather than as a plain
 * class. That is not stylistic. This file is linked BEFORE tailwind.css, so
 * at equal specificity Tailwind wins on source order, and both of these
 * exist precisely to override a Tailwind utility. Drop the `:root` and they
 * silently stop applying, which looks like the tokens being wrong.
 * --------------------------------------------------------------------------- */

/* 1. Surfaces are SCRIMS, not plates.
 *
 * `--color-bg-surface` and `--color-bg-elevated` are rgba over a live
 * particle field. Transparency alone is not enough to keep type readable on
 * it: the field is additively blended, so it has bright knots, and the blur
 * is what destroys the high-frequency detail that makes type shimmer. Tint
 * holds the average luminance down; blur handles the worst moment. The
 * marketing site's `.scrim` is this same pair, and the long note in
 * pandora/pandora.css explains which one you can spend and which you cannot.
 *
 * `@supports` fallback: a browser with no backdrop-filter gets an OPAQUE
 * plate instead of a transparent one, because the point is legibility, not
 * the blur. Same reasoning, same values as the site. */
:root [class~="bg-bg-surface"],
:root [class~="bg-bg-elevated"] {
  backdrop-filter: blur(22px) saturate(120%);
  -webkit-backdrop-filter: blur(22px) saturate(120%);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root [class~="bg-bg-surface"] { background-color: var(--color-bg-surface); }
  :root [class~="bg-bg-elevated"] { background-color: var(--color-bg-elevated); }
}

/* 2. Display type is ITALIC, always.
 *
 * Bodoni Moda is self-hosted here as an italic VARIABLE face covering
 * 400–900 (see fonts.css). So the weight is genuinely there and
 * `font-bold` on a heading still means something — this rule deliberately
 * does NOT pin a weight, and adding one would flatten every heading in the
 * app to regular.
 *
 * What is missing is the ROMAN: there is no upright cut, because this face
 * arrived to set eleven letters in the wordmark and nothing else. Ask for
 * `font-style: normal` and the browser either falls through to Georgia or
 * synthesises an upright from the italic by shearing it back, and which of
 * those you get is a per-browser accident.
 *
 * Pinning italic makes that unreachable, and it is also simply the design:
 * the marketing site sets every h1 and h2 in italic 400 Bodoni, and the
 * point of this theme merge was that the app stops looking like somewhere
 * else. Fetch the roman before removing this. */
:root [class~="font-display"] {
  font-style: italic;
}

/* Pandora workspace theme. Semantic tokens cover auth, billing, notices,
   HTMX fragments and gateway failures as well as ordinary app pages. */
:root {
  color-scheme:light;
  --color-bg-base:#fafbf7;--color-bg-sunken:#edf1e8;--color-bg-surface:#fffffc;--color-bg-elevated:#f0f3eb;--color-bg-overlay:#fafbf7;
  --gradient-page:#fafbf7;
  --color-accent:#315e42;--color-accent-hover:#22472f;--color-accent-strong:#244b36;--color-accent-muted:#e6eddc;--color-accent-ring:#527c4f;--color-accent-contrast:#fff;
  --color-secondary:#53644f;--color-secondary-hover:#244b36;--color-secondary-muted:#edf1e8;
  --color-text-primary:#203a2d;--color-text-secondary:#596654;--color-text-tertiary:#65725f;--color-text-disabled:#778471;--color-text-on-accent:#fff;
  --color-border-subtle:#e3e8dd;--color-border-default:#dce2d7;--color-border-strong:#a5b49b;--color-border-accent:#63865b;
  --color-success:#315e42;--color-success-muted:#e7efdf;--color-warning:#365f3b;--color-warning-muted:#e5eee0;--color-danger:#24472e;--color-danger-muted:#e5eee0;--color-info:#315e42;--color-info-muted:#edf1e8;
  --font-display:Inter,Arial,sans-serif;
  --shadow-sm:0 1px 2px #294b2510;--shadow-md:0 2px 10px #294b2514;--shadow-lg:0 12px 36px #294b251a;--shadow-accent-glow:none;
}
html,body{background:var(--color-bg-base);color:var(--color-text-primary)}
:root [class~="font-display"]{font-family:Inter,Arial,sans-serif;font-style:normal;font-weight:400}
h1,h2,h3{font-style:normal;letter-spacing:-.04em}
.manifold{display:none}.nav-panel{background:var(--color-bg-base)}
.pandora-lockup{display:inline-flex;align-items:center;gap:9px;font:700 25px Inter,Arial,sans-serif;letter-spacing:-1px;color:var(--color-text-primary)}
.pandora-lockup img{width:36px;height:36px}
input,textarea,select{color-scheme:light;font-size:16px;max-width:100%}
input:focus-visible,textarea:focus-visible,select:focus-visible,a:focus-visible,button:focus-visible,summary:focus-visible{outline:2px solid #527c4f;outline-offset:3px}
main{min-width:0}pre{overflow-x:auto}img,svg{max-width:100%}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{animation:none!important;transition:none!important}}
