/* ==========================================================================
   QVO.TECH / Visual System
   The system is defined here and nowhere else. styles.css consumes tokens
   only; no raw hex, no magic numbers outside this file.

   LOCKS (enforced across the whole page):
   - Theme lock ........ one theme per page, driven by prefers-color-scheme
   - Colour lock ....... exactly one accent, --signal. Nothing else is coloured.
   - Shape lock ........ radius 0 everywhere. Architectural, not consumer-glass.
   - Motion lock ....... one easing curve, three durations.
   ========================================================================== */

:root {
  /* ---------------------------------------------------------------- colour
     Mineral grey-white base, cool ink, one oxide signal.
     Deliberately NOT: AI violet, neon, cream/beige + brass, gradient mesh.
     The base carries a faint green-grey cast so it reads as mineral stock
     rather than warm paper. */
  --paper:        #e9ebe8;
  --paper-recess: #dedfdb;
  --paper-raised: #f2f3f1;
  /* Channels only, so the veil gradient can build translucent stops from the
     same token the solid surfaces use. */
  --paper-rgb: 233 235 232;

  --ink:    #16181a;
  --ink-70: rgba(22, 24, 26, 0.70);
  --ink-50: rgba(22, 24, 26, 0.50);
  --ink-35: rgba(22, 24, 26, 0.35);
  --ink-18: rgba(22, 24, 26, 0.18);
  --ink-10: rgba(22, 24, 26, 0.10);

  /* The only chromatic value in the system. Oxide orange, saturation ~76%.
     Permitted uses, and no others: active capability marker, a sparse
     fraction of Core nodes, focus ring. */
  --signal:    #bf4722;
  --signal-40: rgba(191, 71, 34, 0.40);

  /* Hairline. The single structural device inherited from the reference. */
  --rule:        var(--ink-10);
  --rule-strong: var(--ink-18);
  --hairline:    1px;

  /* ------------------------------------------------------------------ type
     Geist for text, Geist Mono for labels and numerals. Sans display by
     default; no serif, and specifically not the two LLM-default serifs. */
  --font-sans: 'Geist', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;

  /* Fluid display scale. Top end is capped so every chapter headline holds to
     two lines at its measure: a three-line display headline is a font-size
     error, not a copy-length problem. Hierarchy comes from weight and colour,
     not from raw scale. */
  --t-display: clamp(1.75rem, 1.15rem + 2.6vw, 3.25rem);
  --t-display-open: clamp(2rem, 1.35rem + 2.8vw, 3.5rem);
  --t-lead:    clamp(0.9375rem, 0.86rem + 0.35vw, 1.125rem);
  --t-body:    0.8125rem;
  --t-label:   0.6875rem;

  --lh-display: 1.04;
  --lh-lead:    1.5;
  --lh-body:    1.62;

  --tr-display: -0.03em;   /* tight display tracking */
  --tr-label:   0.14em;    /* wide mono tracking */

  --w-light:  300;
  --w-book:   400;
  --w-medium: 500;

  /* ----------------------------------------------------------------- space
     Sparse scale. Density dial is 3, so the gaps are large and few. */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-6: 1.5rem;
  --s-8: 2rem;
  --s-12: 3rem;
  --s-16: 4rem;

  /* Page frame inset. The hairline frame is the composition's armature. */
  --frame: 1.25rem;

  /* ----------------------------------------------------------------- shape
     Shape lock: sharp. One value, used everywhere. */
  --radius: 0px;

  /* ---------------------------------------------------------------- motion
     One curve. Everything on the page uses it, so the whole surface shares
     a single sense of physics. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --d-fast: 220ms;
  --d-base: 420ms;
  --d-slow: 900ms;

  /* ---------------------------------------------------------------- layers
     Documented scale. No arbitrary z-index anywhere else. */
  --z-core:  10;
  --z-veil:  15;
  --z-frame: 20;
  --z-type:  30;
  --z-rail:  40;
  --z-nav:   50;
  --z-grain: 60;
}

/* Contrast compensation for the video renderer.
 *
 * The hairline armature and the low-contrast secondary type were tuned against
 * flat paper, where a 10% rule and 35% label read cleanly. Over a lit
 * photographic scene they disappear: the surrounding contrast is far higher, so
 * the same values that looked precise now look broken. Correcting at the token
 * layer rather than per component means the frame, the rail, the terms panel and
 * every label recover together, and the flat-paper values stay untouched for the
 * point-cloud renderer. */
:root.has-core-video {
  --ink-70: rgba(22, 24, 26, 0.80);
  --ink-50: rgba(22, 24, 26, 0.66);
  --ink-35: rgba(22, 24, 26, 0.55);
  --ink-18: rgba(22, 24, 26, 0.34);
  --ink-10: rgba(22, 24, 26, 0.22);
  --rule:        rgba(22, 24, 26, 0.22);
  --rule-strong: rgba(22, 24, 26, 0.34);
}

/* Dark counterpart. Same system, inverted stock. The page locks to one of these;
   sections never flip mid-scroll.
 *
 * Suppressed when the video renderer mounts (.has-core-video, set by scene.js).
 * The master is a daylight architectural render, and no amount of token swapping
 * makes a dark theme sit correctly on top of it. Filtering the render dark
 * enough to carry light text turns it to grey mud and throws away the reason to
 * use it. So: video path means light palette. The dark set below still serves
 * the point-cloud renderer, which draws its own colour from these tokens. */
@media (prefers-color-scheme: dark) {
  :root:not(.has-core-video) {
    --paper:        #101214;
    --paper-recess: #0a0c0d;
    --paper-raised: #191c1f;
    --paper-rgb:    16 18 20;

    --ink:    #e6e8e5;
    --ink-70: rgba(230, 232, 229, 0.70);
    --ink-50: rgba(230, 232, 229, 0.50);
    --ink-35: rgba(230, 232, 229, 0.35);
    --ink-18: rgba(230, 232, 229, 0.18);
    --ink-10: rgba(230, 232, 229, 0.10);

    --signal:    #e2632f;
    --signal-40: rgba(226, 99, 47, 0.40);
  }
}

/* ============================================================ base reset */

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

html {
  /* Scroll drives the entire scene; keep it smooth but never hijacked. */
  scroll-behavior: auto;
  background: var(--paper);
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--w-light);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1,
h2,
h3,
p {
  margin: 0;
}

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

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

:focus-visible {
  outline: var(--hairline) solid var(--signal);
  outline-offset: 3px;
}

/* Utility: the mono label. Used for functional labels only, never as
   decorative section eyebrows. */
.label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: var(--w-book);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
}

/* ==================================================== reduced motion ==== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
