/* Kalibr Design Tokens — Grafted union / pomological plate
 *
 * The world is a scientific plate documenting a graft: a scion and a rootstock joined at
 * a union that either takes or fails. Neither party is subordinate. The union is what we
 * measure; the forest is the world it belongs to, never a claim that we score the stand.
 *
 * The ground is heartwood, not paper. A botanical subject rendered on cream with a serif
 * display is the exact default this palette exists to avoid, so the plate's saturated
 * hand-coloured materials carry the surface instead: quince, plum, cambium, necrosis.
 *
 * Every text pairing below was measured against WCAG AA before it was written here.
 * Ratios are against the ground and the panel respectively, and are not decorative.
 */
:root {
  /* Night — the ground the whole surface sits in. Black-ish: a near-black with a whisper
     of cool indigo, not green. The heartwood green is gone; the four dyes stay. */
  --k-bg: #0A0B0F;
  --k-bg-2: #12131A;            /* panel */
  --k-bg-3: #1A1C26;            /* raised panel */
  --k-surface: #12131A;
  --k-surface-sunk: #050508;    /* wells, inputs, specimen fields */
  --k-scrim: rgba(5,5,8,0.78);  /* night drawn over the page behind an overlay */

  /* Rules — structure only, never text */
  --k-grid: #22242E;
  --k-grid-strong: #2C2F3C;
  --k-grid-lift: #3A3E4C;       /* the scrollbar thumb under the pointer */
  --k-line: #22242E;
  --k-line-strong: #2C2F3C;

  /* Ink — the living layer, carries all reading text. Neutral-cool whites on black. */
  --k-ink: #EEEEF4;
  --k-ink-2: #E8EAEF;
  --k-ink-3: #DCDEE5;
  --k-ink-4: #676970;

  /* Four named roles, each earned by the plate it annotates */
  --k-scion: #E6BC4D;           /* 9.2:1 — quince. The scion: the first party. */
  --k-scion-field: #3A2E12;
  --k-rootstock: #BE92DC;       /* 6.6:1 — plum. The rootstock: the second party. */
  --k-rootstock-field: #2C2138;
  --k-union: #6FC08D;           /* 7.6:1 — cambium. The union takes; it holds under load. */
  --k-union-field: #14301F;
  --k-union-lift: #85CE9F;      /* 9.0:1 — cambium under the pointer, never at rest */
  --k-necrosis: #DC7359;        /* 5.3:1 — the union fails. Delayed incompatibility. */
  --k-necrosis-field: #3A1F19;

  /* ── The torch-lit cave ──────────────────────────────────────────────────────
   * A deliberate second visual world, used only by the sign-in-gated "simplify the
   * science" branch in screens/Landing.jsx and recorded in DESIGN.md Known Open. It is
   * NOT part of the grafted-union palette and none of these may be borrowed by a plate
   * surface. They live here rather than as literals in the screen for the reason The No
   * Literal Rule gives: if a screen needs a colour the token layer does not have, the
   * token layer is what changes. They were nine value-scoped detector waivers, which
   * suppressed those same values across the whole repo.
   *
   * The ochres are bare triples so one torch colour can carry many alphas:
   *   rgb(var(--k-cave-ember) / 0.4)      — and it works in an SVG stroke=/fill= too. */
  --k-cave-ground: #120e08;      /* the deepest floor */
  --k-cave-ground-2: #1a1208;    /* the second stop of the floor gradient */
  --k-cave-ember: 200 140 60;    /* torchlight; the working ochre */
  --k-cave-ember-lift: 210 150 60;  /* brighter ochre: figures and highlights */
  --k-cave-ember-deep: 180 120 40;  /* deeper ochre: washes and fills */
  --k-cave-soot: 120 80 30;      /* the faintest wash */
  --k-cave-ochre-red: 201 108 71;   /* the russet the cave labels take */


  /* Type. Petrona is a text serif with real character and a full weight range, chosen
   * because a plate's voice is engraved and scholarly, not a trend serif. Barlow
   * Condensed does the specimen and herbarium labels a plate actually carries. Spline
   * Sans Mono carries measured values only, never decoration. */
  --k-serif: "Petrona", Georgia, "Times New Roman", serif;
  --k-sans: "Petrona", Georgia, serif;          /* reading text is the serif here */
  --k-narrow: "Barlow Condensed", "Helvetica Neue", Arial, sans-serif;
  --k-mono: "Spline Sans Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Structure. A plate is trimmed and squared. */
  --r-1: 1px;
  --r-2: 2px;
  --r-3: 3px;
  --r-4: 4px;
  --r-5: 6px;
  --r-round: 999px;

  /* Depth: offset plus blur, always. A zero-offset halo is decoration, not depth. */
  --sh-1: 0 1px 2px rgba(4,10,7,0.42), 0 2px 6px rgba(4,10,7,0.30);
  --sh-2: 0 2px 6px rgba(4,10,7,0.46), 0 8px 22px rgba(4,10,7,0.36);
  --sh-3: 0 6px 18px rgba(4,10,7,0.52), 0 20px 52px rgba(4,10,7,0.44);
  --sh-inset: inset 0 0 0 1px var(--k-line);

  /* Spacing scale. Named so no screen has to invent a magic number again. */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px; --s-10: 128px;
}

/* ── Surfaces, and what is allowed on each one ─────────────────────────────────
 * Measured, not asserted. WCAG 2.1 contrast ratios against each surface. The hero
 * column is the brightest 16px block anywhere in the copy region of
 * assets/hero-orchard-2480.webp, which is the worst ground a glyph can land on
 * there; the flat-token columns are the plain surfaces.
 *
 *                    hero column   --k-bg   --k-bg-2   --k-bg-3
 *     --k-ink             10.02     17.02      16.03      14.68
 *     --k-ink-2            9.62     16.34      15.39      14.09
 *     --k-ink-3            8.62     14.63      13.78      12.62
 *     --k-ink-4            2.11      3.59       3.38       3.09
 *     --k-scion            6.00      9.23       8.14       7.07
 *     --k-rootstock        4.29      6.59       5.82       5.05
 *     --k-union            4.94      7.59       6.70       5.82
 *     --k-necrosis         3.41      5.25       4.64       4.02
 *
 * The rules that follow from the table, and they are rules:
 *   - Over the hero image, normal-size text may only be --k-ink, --k-ink-2,
 *     --k-scion or --k-union. Rootstock, necrosis and ink-3 all fail AA there, so
 *     anything using them is sited on a plate instead. This is why the hero's
 *     measured data sits on a mount rather than floating on the artwork.
 *   - --k-ink-4 never carries text on any surface. Rules, borders and dividers only.
 *   - --k-bg-3 is not a home for --k-ink-3 or --k-necrosis at normal size.
 *   - Reversed out on a dye (--k-bg on --k-union and the rest), the same ratios
 *     hold, since contrast is symmetric.
 */

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

html, body {
  margin: 0; padding: 0;
  background: var(--k-bg); color: var(--k-ink);
  font-family: var(--k-serif);
  -webkit-font-smoothing: antialiased;
  font-synthesis-weight: none;
}
button, input, select, textarea { font-family: inherit; }

/* ── Browser surfaces. These ship with defaults belonging to no design system. ── */
::selection { background: var(--k-union); color: var(--k-bg); }
::-moz-selection { background: var(--k-union); color: var(--k-bg); }
:root { accent-color: var(--k-union); caret-color: var(--k-union); color-scheme: dark; }
:focus-visible { outline: 2px solid var(--k-union); outline-offset: 2px; border-radius: var(--r-2); }
* { scrollbar-color: var(--k-grid-strong) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--k-grid-strong); border: 2px solid var(--k-bg); }
::-webkit-scrollbar-thumb:hover { background: var(--k-grid-lift); }
::-webkit-scrollbar-track { background: var(--k-bg); }
a { color: var(--k-union); text-decoration-thickness: 1px; text-underline-offset: 3px; }
:where(td, th) { font-variant-numeric: tabular-nums; }

/* ── Type helpers ── */
.k-serif { font-family: var(--k-serif); font-weight: 400; letter-spacing: -0.015em; }
.k-mono {
  font-family: var(--k-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "zero" 1;
}
.k-narrow { font-family: var(--k-narrow); }

/* A plate's specimen label: condensed, spaced, small.
 *
 * Amended, because the original rule here said "never stacked above a heading as an
 * eyebrow" and then the marketing screens did exactly that. The rule as written was too
 * broad: on a plate, naming the section above its heading is how a plate is labelled, and
 * that is legitimate. What is not legitimate is a label that only restates the heading
 * underneath it, which is decoration wearing a label's clothes. So: a label must add
 * information the heading does not carry. If you can delete it without losing anything,
 * delete it. */
.k-annot,
.k-label {
  font-family: var(--k-narrow);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-3);
}

/* Latin binomial convention: the plate names its specimen in italic. */
.k-binomial { font-style: italic; letter-spacing: 0; }

.k-measure { max-width: 68ch; }   /* body measure, inside the 65-75ch floor */

/* Present to assistive technology, absent to the eye. Used for live regions that must
 * already exist in the DOM before their content changes: a region mounted at the same
 * moment as its text is announced unreliably. Not display:none, which removes it from the
 * accessibility tree entirely, and not width:0, which some engines collapse away. */
.k-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Components, in the plate's vocabulary ── */
.k-card {
  background: var(--k-bg-2);
  border: 1px solid var(--k-line);
  border-radius: var(--r-3);
  box-shadow: var(--sh-1);
}

/* A plate is mounted, not floated: a hairline mount inside the trim. */
.k-plate {
  background: var(--k-bg-2);
  border: 1px solid var(--k-grid-strong);
  box-shadow: var(--sh-2), inset 0 0 0 5px var(--k-bg-2), inset 0 0 0 6px var(--k-grid);
  border-radius: var(--r-2);
}

/* A ranked row in a report spine. It is a real <button> because it opens its own detail, so it
   has to read as pressable; it takes the same hover ground as a nav item. */
.k-spine-row { border-radius: var(--r-2); transition: background 180ms cubic-bezier(.16,1,.3,1); }
.k-spine-row:hover { background: var(--k-bg-3); }

.k-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--k-narrow); font-weight: 600; font-size: 15px;
  letter-spacing: 0.07em; text-transform: uppercase;
  height: 42px; padding: 0 20px;
  border-radius: var(--r-2); border: 1px solid transparent;
  cursor: pointer; text-decoration: none;
  transition: background-color .18s cubic-bezier(.16,1,.3,1),
              color .18s cubic-bezier(.16,1,.3,1),
              border-color .18s cubic-bezier(.16,1,.3,1),
              box-shadow .18s cubic-bezier(.16,1,.3,1);
}
.k-btn--primary,
.k-btn--accent { background: var(--k-union); color: var(--k-bg); box-shadow: var(--sh-1); }
.k-btn--primary:hover,
.k-btn--accent:hover { background: var(--k-union-lift); box-shadow: var(--sh-2); }
.k-btn--ghost { background: transparent; color: var(--k-ink); border-color: var(--k-grid-strong); }
.k-btn--ghost:hover { background: var(--k-bg-3); border-color: var(--k-ink-4); }
.k-btn--lg { height: 50px; padding: 0 26px; font-size: 16px; }
.k-btn--sm { height: 32px; padding: 0 12px; font-size: 13px; }
/* opacity:.45 faded the whole control, label included: the primary variant measured
   1.58:1 and the ghost 3.93:1. WCAG 1.4.3 exempts inactive components, but these buttons
   carry the status the user is waiting on -- "Waiting for scores...", "Regenerating...",
   "Send" -- so the label has to stay readable while the control reads as unavailable.
   Dropping the fill and keeping the dye as a hairline does both, and reuses the
   field/ink pairing .k-chip--union already defines. Measured: 6.52:1 and 5.50:1. */
.k-btn:disabled { cursor: not-allowed; box-shadow: none; }
.k-btn--primary:disabled,
.k-btn--accent:disabled {
  background: var(--k-union-field);
  color: var(--k-union);
  border-color: var(--k-union);
}
.k-btn--ghost:disabled {
  background: transparent;
  color: var(--k-ink-3);
  border-color: var(--k-grid);
}

.k-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px;
  border-radius: var(--r-2);
  font-family: var(--k-mono); font-size: 12.5px; font-weight: 500;
  background: var(--k-bg-3); color: var(--k-ink-2);
  border: 1px solid var(--k-line);
}
/* Named for the dye, not for the legacy alias. --sage and --accent were the same
   declaration under two names; there is now one. */
/* The contrast table forbids --k-ink-3 on --k-bg-3: it measures 4.21:1, under AA at
   normal size. But ink-3 is the correct ink for a label everywhere else, so .k-label
   cannot simply be raised. A panel that grounds itself in bg-3 marks itself with this
   class instead, and every tertiary ink inside it steps up to ink-2 (7.12:1). */
.k-on-sunk,
.k-on-sunk .k-label { color: var(--k-ink-2); }

.k-chip--union    { background: var(--k-union-field);     color: var(--k-union);     border-color: transparent; }
.k-chip--scion    { background: var(--k-scion-field);     color: var(--k-scion);     border-color: transparent; }
.k-chip--rootstock{ background: var(--k-rootstock-field); color: var(--k-rootstock); border-color: transparent; }
.k-chip--necrosis { background: var(--k-necrosis-field);  color: var(--k-necrosis);  border-color: transparent; }

/* ── The hero ground ───────────────────────────────────────────────────────────
 * The stand itself. The artwork's own outer pixels were composited onto --k-bg when
 * it was cut, so the image has no edge against the ground and the section below it
 * has no seam. Anchored right because the copy occupies the left and the ground
 * there is uniform, so cropping it costs nothing at any viewport width.
 */
.k-hero {
  position: relative;
  background-color: var(--k-bg);
  background-image: url("/assets/hero-orchard-1440.webp");
  background-image: image-set(url("/assets/hero-orchard-1440.webp") 1x,
                              url("/assets/hero-orchard-2480.webp") 2x);
  background-position: right center;
  background-size: cover;
  background-repeat: no-repeat;
}

/* Below the width where the copy still clears the canopy, the copy has to overlay
 * the artwork. Push the stand further right and lay the ground back over it, so the
 * measured ratios above still hold on what is now a much darker column. */
@media (max-width: 900px) {
  .k-hero { background-position: 78% center; }
  .k-hero::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(100deg,
      var(--k-bg) 0%, var(--k-bg) 40%, rgba(20,33,28,0.78) 64%, rgba(20,33,28,0.34) 100%);
  }
  .k-hero > * { position: relative; }
}

/* Desktop-only chrome is removed below the phone breakpoint by class, not by the JS
   `isMobile` flag alone. The flag and this rule agree in normal operation; the class is
   the deterministic backstop, so a missed resize or a stale bundle cannot leave a desktop
   overlay floating over a phone layout. */
@media (max-width: 767px) {
  .k-hide-mobile { display: none; }
}

/* ── Motion ────────────────────────────────────────────────────────────────────
 * Every animated state is opt-in behind .js-motion, which scripting adds to <html>.
 * Nothing here can leave content invisible if the observer never fires or JS fails:
 * the resting state in the stylesheet is the visible one.
 *
 * Three authored moments, each carrying meaning rather than decorating:
 *   .k-reveal   a section arriving, so a long page reads as a sequence
 *   .k-grow     a bar drawn to its value, because the value is the point
 *   .k-graft-in the union closing, on the hero plate only
 */
.js-motion .k-reveal {
  opacity: 0;
  transform: translateY(14px);
}
.js-motion .k-reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 620ms cubic-bezier(.16,1,.3,1),
              transform 620ms cubic-bezier(.16,1,.3,1);
}

/* A measured bar draws from its own origin to its width. scaleX rather than width so it
 * runs on the compositor and cannot reflow the table around it. */
.js-motion .k-grow {
  transform: scaleX(0);
  transform-origin: left center;
}
.js-motion .k-grow.is-in {
  transform: scaleX(1);
  transition: transform 900ms cubic-bezier(.16,1,.3,1);
}

/* Staggering is capped: past a handful of steps a stagger stops reading as sequence and
 * starts reading as lag. */
.js-motion .k-reveal.is-in[data-step="1"] { transition-delay:  70ms; }
.js-motion .k-reveal.is-in[data-step="2"] { transition-delay: 140ms; }
.js-motion .k-reveal.is-in[data-step="3"] { transition-delay: 210ms; }
.js-motion .k-grow.is-in[data-step="1"] { transition-delay:  60ms; }
.js-motion .k-grow.is-in[data-step="2"] { transition-delay: 120ms; }
.js-motion .k-grow.is-in[data-step="3"] { transition-delay: 180ms; }
.js-motion .k-grow.is-in[data-step="4"] { transition-delay: 240ms; }
.js-motion .k-grow.is-in[data-step="5"] { transition-delay: 300ms; }
.js-motion .k-grow.is-in[data-step="6"] { transition-delay: 360ms; }
.js-motion .k-grow.is-in[data-step="7"] { transition-delay: 420ms; }
.js-motion .k-grow.is-in[data-step="8"] { transition-delay: 480ms; }
.js-motion .k-grow.is-in[data-step="9"] { transition-delay: 540ms; }

/* Someone who asked the operating system for less motion gets the finished state at once,
 * not a slower version of the same movement. */
@media (prefers-reduced-motion: reduce) {
  .js-motion .k-reveal,
  .js-motion .k-reveal.is-in,
  .js-motion .k-grow,
  .js-motion .k-grow.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Motion: one authored moment. Exponential ease-out from an already-visible default,
 * so nothing depends on JS to become readable. The union closes; it does not fade in. */
@media (prefers-reduced-motion: no-preference) {
  .k-graft-in { animation: k-graft 1000ms cubic-bezier(.16,1,.3,1) both; }
}
@keyframes k-graft {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}
