/* ============================================================
 * components.css — neoturne component recipes (theme: Neoturne)
 * AUTO-GENERATED — DO NOT EDIT BY HAND.
 * A concatenation of src/components/*.css by scripts/generate.mjs.
 * To change a component: edit the matching src/components/<name>.css
 * partial and run `pnpm generate`.
 *
 * The `.nt-*` classes reference the design tokens (CSS custom properties)
 * but do NOT define them. Load the variables first — the pnpm `styles`
 * entry, or `neoturne.css` for a plain <link> — then this file.
 * ============================================================ */

/**
 * @file src/components/seam.css
 * @summary THE SEAM — the one construction every Arise recipe composes from.
 *          Defines the conic emissive edge once, as a custom property, plus the
 *          two-line application every component reuses. No component redeclares
 *          the gradient; they set VALUES and let this file draw.
 *
 * @traceability canon §6.1 (the frame), Law 5 (three stops per segment),
 *               Law 6 (figures dissolve, furniture does not), arise.seam tokens.
 *
 * ── THE CONTRACT ───────────────────────────────────────────────────────────
 * A seam-bearing element declares, ON ITSELF:
 *   --seam-rgb    the hue as an "r, g, b" triple (cyan at rest; the aura wakes)
 *   --seam-run    the flank alpha — "seam run" in every §7 table
 *   --seam-peak   the corner pool, a COMPLETE colour (often --emission-core)
 *   --seam-trough the mid-edge alpha. Defaults to --seam-run (furniture).
 *   --seam-cal    aura calibration. 1 for cyan; var(--aura-*-cal) when awake.
 *   --seam-fill   the interior colour (--void or --void-floor)
 * then applies `background: var(--nt-seam-bg); border: …` — see §APPLICATION.
 *
 * IMPLEMENTATION TRAP (canon §7.3, generalised): custom properties inherit
 * DOWNWARD ONLY, and one unresolved var() invalidates the whole gradient — the
 * seam then vanishes silently rather than erroring. WHICHEVER ELEMENT PAINTS
 * THE SEAM OWNS EVERY SEAM VARIABLE. Every recipe below therefore declares the
 * full set, even where a value equals the default.
 *
 * ── WHY NOT mask-composite ─────────────────────────────────────────────────
 * Canon §6.1 calls a 1px-padded pseudo-element with `mask-composite: exclude`
 * "the only way" to get a non-uniform stroke at true 1px with zero radius.
 * That is correct for a <div> — and it is what PresenceStates.module.css does,
 * where it is proven. It cannot work here, because <input> and <textarea> are
 * replaced elements with no reliable pseudo-elements; that is exactly why §7.3
 * routes the input's seam through a wrapper.
 *
 * But a wrapper is a MARKUP change, and §12.5 makes unchanged `.nt-*` markup
 * the entire basis on which this release is a visual migration rather than a
 * code one. Between "change every consumer's form markup" and "use a different
 * 1px technique", the technique loses nothing worth keeping.
 *
 * So: a double-layer background-clip. The interior paints to `padding-box`, the
 * seam gradient to `border-box`, behind a transparent 1px border. Identical
 * geometry (true 1px, zero radius, non-uniform along its length), works on form
 * controls, needs no pseudo-element — leaving ::before/::after free for
 * consumers, which the mask approach consumes. Its one requirement is an OPAQUE
 * interior, which every §7 recipe already has (void or void-floor).
 *
 * ── KNOWN LIMITATION, for the density test ─────────────────────────────────
 * A conic gradient's 45/135/225/315° rays land on the geometric corners only on
 * a SQUARE element. On a wide button (200x44) the corner sits ~12° off
 * horizontal, so the 45° pool lands on the top edge rather than the corner.
 * Faithful to §6.1's stop list, but the pooling reads differently at wide
 * aspect ratios than it does on the near-square card specimen §6.1 was written
 * from. Mitigated for furniture: with --seam-trough equal to --seam-run the
 * only variation IS the pool, so a mispositioned pool degrades to a steady run
 * rather than to visible banding. Flagged rather than solved — a robust fix
 * (four corner radials over a flat run) is a different construction than the
 * canon specifies, and that is a design call, not an implementation one.
 */

:root {
  /* DEFAULTS, so a partially-configured element degrades to a quiet cyan seam
   * on the void instead of vanishing. Individual recipes override all of them.
   * These are plain values: they inherit, and each element's own declarations
   * win the cascade normally. */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: var(--summoned-seam-mid);
  --seam-trough: var(--seam-run);
  --seam-peak: rgba(var(--seam-rgb), var(--summoned-peak));
  --seam-fill: var(--void);

  /* THE AURA — per-app tenancy (§4.5), read by the card, the page furniture
   * and the session button. Cyan so an app that sets nothing still renders.
   *
   * NOTE: declared HERE and nowhere else [US-3.2]. Until 0.3.6 each consumer
   * class redeclared this default on itself, and a custom property declared on
   * an element beats the inherited value — so an app's --aura-rgb on :root or
   * a wrapper never reached them, and every eyebrow, heading, rule and card
   * wake rendered cyan (measured in Chrome, 2026-09-23). Set it once on an
   * ancestor, e.g. `:root { --aura-rgb: var(--aura-yellow-rgb);
   * --aura-cal: var(--aura-yellow-cal); }`, or on one card for a per-item aura. */
  --aura-rgb: 34, 211, 238;
  --aura-cal: 1;
}

/* THE GRADIENT IS DECLARED ON THE ELEMENTS, NOT ON :root.
 *
 * A custom property's var() references are substituted on the element where
 * the property is DECLARED, not where it is used. Declared on :root, --nt-seam
 * resolved exactly once against :root's own --seam-run / --seam-peak /
 * --seam-fill, and every element inherited that finished string. Recipes could
 * set --seam-run all they liked; nothing read it again.
 *
 * Measured 2026-09-11: .nt-btn--primary carried --seam-run .9 and rendered a
 * gradient at 0.3 — the :root default. Every seam in the system was identical,
 * which is why no recipe resembled its specimen and why correcting values in
 * the recipes changed nothing.
 *
 * Declared here, on each component root, the same tokens resolve against that
 * element's own values. The list is every selector that applies
 * `background: var(--nt-seam-bg)` — keep the two in step.
 * [US-4.5, 0.3.11] + choice option, code block and inline chip, notice.
 */
.nt-btn,
.nt-card,
.nt-input,
.nt-choice__option,
.nt-code,
.nt-code--inline,
.nt-notice,
.nt-modal,
.nt-shell,
.nt-toast {
  /* THE FLOOR — the one fill every seamed recipe uses (`--seam-fill:
   * var(--nt-floor)`), so a decision about surfaces is a one-line change.
   *
   * NOTE [US-3.2, 0.3.8]: plain --void-floor, NO aura tint. §4.5 lists "a 2%
   * tint in --void-floor" as tenancy; 0.3.7 applied it page-wide as the
   * "Construct in Arise" specimen does (only .nt-card carried it before). On
   * the Toolbox hub, a thin yellow over the void beside cyan seams read as
   * GREEN, and Jan removed it — from the card as well. Canon amended. */
  --nt-floor: var(--void-floor);

  /* GEOMETRY — the shared stop list. canon §6.1:
   *   corner    45 / 135 / 225 / 315°   peak    light pools here
   *   flank     ±18° from each corner   run     the hue at strength
   *   mid-edge   0 /  90 / 180 / 270°   trough  falls back toward void
   * Seventeen stops: four quadrants of peak/flank/trough, plus the 360° close.
   *
   * LAW 6 IS A VARIABLE ASSIGNMENT, not a second gradient. A FIGURE sets
   * --seam-trough below --seam-run and its mid-edges dissolve into the void;
   * FURNITURE leaves --seam-trough at its default (= --seam-run) and holds a
   * steady run between the corner pools. One construction, two categories.
   */
  --nt-seam: conic-gradient(
    from 0deg at 50% 50%,
    rgba(var(--seam-rgb), calc(var(--seam-trough) * var(--seam-cal))) 0deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 27deg,
    var(--seam-peak) 45deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 63deg,
    rgba(var(--seam-rgb), calc(var(--seam-trough) * var(--seam-cal))) 90deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 117deg,
    var(--seam-peak) 135deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 153deg,
    rgba(var(--seam-rgb), calc(var(--seam-trough) * var(--seam-cal))) 180deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 207deg,
    var(--seam-peak) 225deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 243deg,
    rgba(var(--seam-rgb), calc(var(--seam-trough) * var(--seam-cal))) 270deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 297deg,
    var(--seam-peak) 315deg,
    rgba(var(--seam-rgb), calc(var(--seam-run) * var(--seam-cal))) 333deg,
    rgba(var(--seam-rgb), calc(var(--seam-trough) * var(--seam-cal))) 360deg
  );

  /* APPLICATION — the two layers. Interior first (it sits on top, clipped to
   * the padding box), seam second (clipped to the border box, so only the 1px
   * ring of it is ever visible). */
  --nt-seam-bg:
    linear-gradient(var(--seam-fill), var(--seam-fill)) padding-box,
    var(--nt-seam) border-box;
}

/**
 * @file src/components/button.css
 * @summary The neoturne BUTTON recipe, re-founded in Arise.
 *          <button class="nt-btn nt-btn--primary nt-btn--md">
 *
 * @traceability Implemented from the LOCKED SPECIMEN — the "Arise Button
 *               Hierarchy" artifact (2026-09-10), verified against canon §7.1.
 *
 * ── WHY THE SPECIMEN IS THE SOURCE, NOT §7.1 ───────────────────────────────
 * §7.1's table has columns for Seam run · Peak · Inner · Bloom · Label. It
 * records those perfectly — all eleven values matched on the first pass. But a
 * table with those columns structurally cannot hold a text-shadow radius, a
 * focus ring, a press offset, or a padding value, so the first implementation
 * was missing every one of them. Those were never dropped from the canon; the
 * canon was never shaped to carry them.
 *
 * So: VALUES COME FROM THE SPECIMEN, and §7.1 is the index to it. Where the two
 * differ, the difference is noted inline rather than silently resolved.
 *
 * ── THE THESIS (§7.1) ──────────────────────────────────────────────────────
 * Rank is EMISSION INTENSITY, not hue. Every variant is cyan, so the aura stays
 * reserved for identity (§4.5) — a button is a content control, i.e. product.
 * Destructive is the one exception, argued for below.
 *
 * THE INTERIOR IS LIT, NEVER FILLED. The centre stays void and an INSET glow
 * lights it from the seam inward, falling away toward the middle, so the button
 * obeys hard-edge / soft-fill (Law 1) instead of being an exception to it. A
 * filled button is a surface; a lit one is a summon. It also deepens rather
 * than flattens on hover.
 *
 * FURNITURE, SO THE RUN IS STEADY (Law 6). The specimen's note is the clearest
 * statement of why: the mid-edge dissolve "is atmosphere on a 240px card and a
 * broken outline at 44px." Mechanically this is leaving --seam-trough at its
 * default, which equals --seam-run.
 */

.nt-btn {
  /* SEAM — the full set, on the element that paints it (see seam.css). */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.38;
  --seam-trough: var(--seam-run);
  --seam-peak: rgba(var(--seam-rgb), 0.6);
  --seam-fill: var(--nt-floor); /* SPECIMEN: the tinted floor, not void (seam.css) */

  /* EMISSION — absolute per-size values, as the specimen sets them. */
  --btn-inner: 0px;
  --btn-inner-a: 0;
  --btn-glow: 0px;
  --btn-glow-a: 0;
  --btn-label: var(--text-primary);
  --btn-label-glow: 0;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-sm);
  min-height: var(--button-height-medium); /* 44px — --touch-target-min */
  padding: 10px 22px;
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--btn-label);
  border-radius: var(--border-radius); /* 0 */

  /* NO letter-spacing. The first pass added --letter-spacing-wide; the specimen
   * has none on buttons, and tracking a 600-weight label at this size loosens
   * it enough to read as a different typeface than the rest of the control. */

  /* The seam. Transparent border + two clipped background layers. */
  border: var(--seam-width) solid transparent;
  background: var(--nt-seam-bg);

  /* Outer halation, then the inset light. No cast shadow — there is no light
   * source in this world to cast one (§10). */
  box-shadow:
    0 0 var(--btn-glow) rgba(var(--seam-rgb), var(--btn-glow-a)),
    inset 0 0 var(--btn-inner) rgba(var(--seam-rgb), var(--btn-inner-a));

  /* LABEL GLOW — 10px, in the SEAM's colour, so destructive glows red rather
   * than cyan. The first pass cut this to 2.5px reasoning from Law 4 ("blur
   * scales with the element"), which was wrong twice over: Law 4 exists to stop
   * a blurred COPY of a glyph reading as out-of-focus, and this is a low-alpha
   * shadow behind a lit label — a different phenomenon. Overriding a value that
   * was locked by looking at it, with one derived from a law applied outside its
   * domain, is exactly backwards. The specimen wins. */
  text-shadow: 0 0 10px rgba(var(--seam-rgb), var(--btn-label-glow));

  cursor: pointer;
  isolation: isolate;
  transition:
    box-shadow 180ms var(--ease-out),
    color 160ms var(--ease-out),
    text-shadow 160ms var(--ease-out),
    transform 100ms var(--ease-out);
}

/* FOCUS — was missing entirely from the first pass, which is an accessibility
 * defect as much as a fidelity one. A hard 2px ring in --emission-core, offset
 * so it reads as a separate mark rather than thickening the seam. */
.nt-btn:focus-visible {
  outline: 2px solid var(--emission-core);
  outline-offset: 3px;
}

/* PRESS — the button goes DOWN. Also missing from the first pass, which only
 * shortened the transition. §8's asymmetry: dismissal is faster than arrival. */
.nt-btn:active {
  transform: translateY(1px);
}

/* --- Primary: the one lit interior at rest -------------------------------- */
.nt-btn--primary {
  --seam-run: 0.9;
  --seam-peak: rgba(224, 251, 255, 0.95);
  --btn-inner: 20px;
  --btn-inner-a: 0.34;
  --btn-glow: 14px;
  --btn-glow-a: 0.24;
  --btn-label: #fff;
}

.nt-btn--primary:hover {
  --seam-run: 1;
  --btn-inner: 28px;
  --btn-inner-a: 0.55;
  --btn-glow: 24px;
  --btn-glow-a: 0.42;
  --btn-label-glow: 0.4;
}

/* --- Secondary: the resting rank ------------------------------------------ */
.nt-btn--secondary {
  --seam-run: 0.38;
  --seam-peak: rgba(var(--seam-rgb), 0.6);
}

.nt-btn--secondary:hover {
  --seam-run: 0.85;
  --seam-peak: rgba(224, 251, 255, 0.8);
  --btn-inner: 16px;
  --btn-inner-a: 0.18;
  --btn-glow: 22px;
  --btn-glow-a: 0.3;
  --btn-label: #fff;
  --btn-label-glow: 0.35; /* SPECIMEN — §7.1's table has no column for it */
}

/* --- Ghost: the quietest visible furniture -------------------------------- */
/* §7.1's Label column says `--text-dim`, and that token did not exist. The
 * first pass substituted --text-muted (#64748b) rather than creating it, which
 * is how ghost came out two steps too dark. --text-dim (#94a3b8) is now a real
 * token; the canon was right and the token set was incomplete.
 *
 * CAVEAT (§3): a 0.12 run is the display-dependent quiet end. Ghost must differ
 * from disabled by more than seam alpha — it does, via label colour (#94a3b8 at
 * 7.9:1 vs dormant #33404f at 2.0:1) and the hover response.
 */
.nt-btn--ghost {
  --seam-run: 0.12;
  --seam-peak: rgba(var(--seam-rgb), 0.24);
  --btn-label: var(--text-dim);
}

.nt-btn--ghost:hover {
  --seam-run: 0.5;
  --seam-peak: rgba(var(--seam-rgb), 0.7);
  --btn-inner: 12px;
  --btn-inner-a: 0.1;
  --btn-glow: 18px;
  --btn-glow-a: 0.22;
  --btn-label: var(--text-primary);
  --btn-label-glow: 0.3; /* SPECIMEN */
}

/* --- Destructive ---------------------------------------------------------- */
/* §7.1 accepts the overlap with the red AURA deliberately: "Forty years of
 * muscle memory outweighs protecting a colour rule, and Delete is identified by
 * its label first." On a red-aura app the button and the frame share a hue but
 * not a FORM — a lit hover target against a 1px line.
 *
 * Because the label glow takes --seam-rgb, destructive's hover glow is RED.
 * That is the specimen's behaviour and it is what makes the hover "strong
 * enough to stop a hand".
 *
 * `--danger` is the shipped class name; `--destructive` is §7.1's vocabulary.
 * Both are honoured so the canon's name works without breaking consumers.
 */
.nt-btn--danger,
.nt-btn--destructive {
  --seam-rgb: 255, 48, 55; /* --aura-red, the locked red */
  --seam-run: 0.75;
  --seam-peak: color-mix(in srgb, #ff3037 48%, #fff);
  --btn-inner: 12px;
  --btn-inner-a: 0.16;
  --btn-glow: 14px;
  --btn-glow-a: 0.22;
  --btn-label: #ff9a9a;
}

.nt-btn--danger:hover,
.nt-btn--destructive:hover {
  --seam-run: 1;
  --seam-peak: #ffd9d9;
  --btn-inner: 30px;
  --btn-inner-a: 0.45;
  --btn-glow: 32px;
  --btn-glow-a: 0.6;
  --btn-label: #fff;
  --btn-label-glow: 0.6; /* SPECIMEN — the strongest label glow in the set */
}

/* --- Session controls ----------------------------------------------------- */
/* For log-out and account actions only, never for content actions (§7.1).
 * The one button that carries the AURA, because §4.5 assigns "the user's
 * session" to tenancy. It reads --aura-rgb, set once per app (seam.css :root).
 *
 * NOTE [US-3.2, 0.3.8] — THE SESSION BUTTON IS A SECONDARY IN THE AURA.
 * Jan's decision after seeing it on the hub: it should "feel and function like
 * a secondary button from the specimen, but with yellow as the seam colour".
 * This departs from both earlier sources, deliberately:
 *   - §7.1's prose ("smaller, mono, run .34 -> .85") — the pre-0.3.7 recipe;
 *   - "Construct in Arise"'s `.btn.ident` (compact mono, aura label) — 0.3.7.
 * So every value below is .nt-btn--secondary's, from "Arise Button Hierarchy";
 * only the seam hue changes. The label is secondary's too (neutral ink, white
 * on hover) — the seam is what says "session".
 *
 * NOTE — THE LIGHT MULTIPLIER. The package draws product cyan at cal 1 while
 * the canon gives cyan-as-aura 0.66 (see the G7 note, US-3.2). "A secondary,
 * but in the aura" must therefore scale the aura's light RELATIVE TO PRODUCT
 * CYAN, not by --aura-cal alone: k = --aura-cal / --aura-neutral-cal. Yellow
 * gets 0.46 / 0.66 = 0.70; a neutral aura gets exactly 1, so it renders
 * identical to a cyan secondary. Chosen by Jan from three rendered variants
 * (×0.46, ×0.70, ×1), 2026-09-23.
 *
 * HISTORY: until 0.3.6 this declared `--ident-rgb`/`--seam-rgb` as a cycle,
 * invalid on both sides — the button had no seam at all.
 */
.nt-btn--session {
  --session-k: calc(var(--aura-cal) / var(--aura-neutral-cal));
  --seam-rgb: var(--aura-rgb);
  --seam-cal: var(--session-k);
  --seam-run: 0.38; /* secondary */
  --seam-peak: rgba(var(--seam-rgb), calc(0.6 * var(--session-k)));
}

.nt-btn--session:hover {
  --seam-run: 0.85; /* secondary:hover */
  --seam-peak: rgba(224, 251, 255, 0.8);
  --btn-inner: 16px;
  --btn-inner-a: calc(0.18 * var(--session-k));
  --btn-glow: 22px;
  --btn-glow-a: calc(0.3 * var(--session-k));
  --btn-label: #fff;
  --btn-label-glow: 0.35;
}

/* --- Disabled: dormant ---------------------------------------------------- */
/* Not "greyed out" — DORMANT (§5): not called. The specimen's note: "derivable
 * from canon, not a design question". run = --dormant-seam-mid, peak alpha =
 * --dormant-peak. */
.nt-btn:disabled,
.nt-btn[disabled],
.nt-btn[aria-disabled='true'] {
  --seam-run: var(--dormant-seam-mid); /* 0.05 */
  --seam-peak: rgba(var(--seam-rgb), var(--dormant-peak)); /* 0.09 */
  --btn-inner: 0px;
  --btn-inner-a: 0;
  --btn-glow: 0px;
  --btn-glow-a: 0;
  --btn-label: #33404f; /* §5 dormant name colour, unlit */
  --btn-label-glow: 0;
  cursor: not-allowed;
}

.nt-btn:disabled:active,
.nt-btn[disabled]:active {
  transform: none;
}

/* --- Sizes ---------------------------------------------------------------- */
/* Absolute values, as the specimen sets them — NOT a scale multiplier. The
 * first pass used a 0.7 / 1.0 / 1.3 factor, which happened to reproduce the
 * hover numbers but shrank the REST bloom, which the specimen never scales.
 *
 * Law 4 applied: "a 36px button does not get a 52px button's light." Copying
 * one bloom across three sizes swamps the small one and flattens the large.
 */
.nt-btn--sm {
  --btn-inner: 14px;
  min-height: var(--button-height-small); /* 36px */
  padding: 6px 16px;
  font-size: var(--font-size-small);
}

.nt-btn--sm:hover {
  --btn-inner: 20px;
  --btn-glow: 16px;
}

.nt-btn--md {
  min-height: var(--button-height-medium); /* 44px */
}

.nt-btn--lg {
  --btn-inner: 26px;
  min-height: var(--button-height-large); /* 52px */
  padding: 14px 28px;
  font-size: var(--font-size-large);
}

.nt-btn--lg:hover {
  --btn-inner: 36px;
  --btn-glow: 30px;
}

@media (prefers-reduced-motion: reduce) {
  .nt-btn {
    transition-duration: 0.01ms;
  }
}

/**
 * @file src/components/card.css
 * @summary The neoturne CARD recipe, re-founded in Arise. The default is the
 *          summoned card from specimen 020.
 *          <div class="nt-card nt-card--interactive">
 *
 * @traceability canon §7.5 (locked 2026-09-10), §6.1, Law 6, §5, §4.5.
 *
 * ── THE ONE FIGURE ─────────────────────────────────────────────────────────
 * Cards are FIGURES — the summoned things — and this is the single construction
 * in the system that KEEPS ITS DISSOLVING MID-EDGES (Law 6). Their edges fading
 * into the void is what makes them read as summoned rather than as panels.
 * Everything else in §7 is furniture and holds a steady run.
 *
 * Law 6 was arrived at by falsification. The first theory was "the dissolve
 * scales with run length"; a 44px button losing it and a 640px shell losing it
 * while a 240px card keeps it killed that outright. The surviving rule is about
 * CATEGORY, and mechanically it is one line: --seam-trough below --seam-run.
 *
 * ── THE HUE SWITCHES ON WAKING, NOT AT REST (§6.1) ─────────────────────────
 * A resting grid is uniformly cyan; the card you touch changes colour. That
 * switch is the drama. Consumers set --aura-rgb / --aura-cal per app, or on a
 * single card; the default is cyan (seam.css :root), so an app that sets
 * neither still renders correctly. NOTE: this class must NOT redeclare the
 * aura itself — doing so shadowed every consumer's value until 0.3.6.
 *
 * ── ON THE PEAK VALUE ──────────────────────────────────────────────────────
 * §5's generic scale puts the summoned peak at cyan @ 0.50; §7.5 puts the
 * card's at 0.55. That is a sanctioned per-recipe deviation, not a drift — the
 * arise.presence token description says so outright: "Recipes deviate where
 * their scale demands it; the per-recipe values live in the hand-authored
 * partials." The trough and run DO come from the tokens verbatim.
 */

.nt-card {
  /* AURA — inherited, never declared here (see header). Read by the fill tint
   * below at rest, and by the seam once the card wakes (§6.1). */

  /* SEAM — cyan at rest. trough < run is what makes this a figure. */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-trough: var(--summoned-seam); /* 0.06 — dissolves at the mid-edge */
  --seam-run: var(--summoned-seam-mid); /* 0.30 */
  --seam-peak: rgba(var(--seam-rgb), 0.55); /* §7.5 per-recipe */

  /* FILL — the plain void floor, shared with every recipe (seam.css). The 2%
   * aura tint it carried until 0.3.8 is gone (US-3.2); at rest a card is
   * entirely cyan and void — only the SEAM takes the aura, on waking. */
  --seam-fill: var(--nt-floor);

  --card-bloom: 0px;
  --card-bloom-a: 0;
  --card-inner: 0px;
  --card-inner-a: 0;
  --card-lift: 0px;

  position: relative;
  padding: var(--card-padding-default);
  min-width: var(--card-min-width); /* was hard-coded 250px, ignoring the token */
  border-radius: var(--border-radius); /* 0 */
  border: var(--seam-width) solid transparent;
  background: var(--nt-seam-bg);
  color: var(--text-primary);
  transform: translateY(var(--card-lift));

  box-shadow:
    inset 0 0 var(--card-inner) rgba(var(--seam-rgb), var(--card-inner-a)),
    0 0 var(--card-bloom)
      rgba(var(--seam-rgb), calc(var(--card-bloom-a) * var(--seam-cal)));

  transition:
    background 240ms var(--ease-out),
    box-shadow 240ms var(--ease-out),
    transform 150ms var(--ease-out);
  /* NOTE [US-3.2, 0.3.8]: `var(--transition-base) var(--ease-out)` expanded to
   * `0.15s ease ease-out` — invalid, so the WHOLE transition was dropped and
   * hover snapped. Explicit durations, as .nt-btn writes them. */
}

/* --- Attention ------------------------------------------------------------ */
/* Renamed from `--elevated` (§7.5): elevation is no longer what it does, so the
 * name had to go with it. Higher presence AT REST — not hoverable, just more
 * awake. `--elevated` is kept as a deprecated alias: §12.5 makes unchanged
 * class names the basis of a visual-only migration, and the design book still
 * uses it. New code should say --attention.
 */
.nt-card--attention,
.nt-card--elevated {
  --seam-run: 0.62;
  --seam-peak: rgba(224, 251, 255, 0.85);
  --card-bloom: 26px;
  --card-bloom-a: 0.14;
}

/* --- Interactive: wakes on hover / focus ---------------------------------- */
.nt-card--interactive {
  cursor: pointer;
}

.nt-card--interactive:hover,
.nt-card--interactive:focus-visible {
  /* THE HUE SWITCH. Frame goes from cyan to the app's aura, and the alpha is
   * multiplied by that aura's calibration — because this is LIGHT. Ink is never
   * calibrated (§4.3). */
  --seam-rgb: var(--aura-rgb);
  --seam-cal: var(--aura-cal);
  --seam-trough: var(--awakened-seam); /* 0.10 */
  --seam-run: 0.85; /* §7.5 */
  --seam-peak: color-mix(in srgb, rgb(var(--aura-rgb)) 48%, #fff);
  --card-bloom: 26px;
  --card-bloom-a: 0.20;
  --card-inner: 40px;
  --card-inner-a: 0.10;
  --card-lift: -2px; /* §5 awakened lift */
  outline: none;
}

/* --- Padding modifiers: unchanged (§7.5) --------------------------------- */
.nt-card--compact {
  padding: var(--card-padding-compact);
}

.nt-card--spacious {
  padding: var(--card-padding-spacious);
}

/* --- Canvas: a DOCUMENT surface, deliberately not emissive ---------------- */
/* §7.7: "The light canvas survives, and it is expensive: every recipe in §7
 * assumes a void ground, so a light surface needs a second, inverted variant of
 * each. Treat --canvas as a document surface, not as a general-purpose panel."
 *
 * So this variant opts OUT of the seam entirely and wears an ordinary 1px
 * border. Emission cannot work on a light ground — there is nothing for light
 * to emit against — and faking it would produce the "fog" Law 1 forbids.
 */
.nt-card--canvas {
  border: 1px solid var(--canvas-border);
  background: var(--canvas-bg);
  color: var(--canvas-text);
  box-shadow: none;
}

/* --- Sections ------------------------------------------------------------- */
/* Dividers are emissive hairlines now, not --bg-color-hover lines. Quiet: they
 * are internal structure, and §9 rations emission to one awakened element. */
.nt-card__header {
  padding-bottom: var(--spacing-lg);
  margin-bottom: var(--spacing-lg);
  border-bottom: 1px solid rgba(var(--seam-rgb), 0.12);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-wide);
}

.nt-card__body {
  color: var(--text-secondary);
}

.nt-card__footer {
  padding-top: var(--spacing-lg);
  margin-top: var(--spacing-lg);
  border-top: 1px solid rgba(var(--seam-rgb), 0.12);
}

.nt-card--canvas .nt-card__header,
.nt-card--canvas .nt-card__footer {
  border-color: var(--canvas-border);
}

/* READABLE ON CANVAS [US-3.2, 0.3.8] — .nt-card__body sets --text-secondary
 * (#cbd5e1), which on #faf7f0 was about 1.4:1: practically invisible. */
.nt-card--canvas .nt-card__body {
  color: var(--canvas-text-secondary);
}

@media (prefers-reduced-motion: reduce) {
  .nt-card {
    transition: none;
  }
}

/**
 * @file src/components/input.css
 * @summary The neoturne INPUT / FIELD recipe, re-founded in Arise.
 *            <div class="nt-field">
 *              <label class="nt-label">…</label>
 *              <input class="nt-input">
 *              <p class="nt-helper-text">…</p>
 *            </div>
 *          `nt-input--error` for the error state; `nt-input nt-textarea` on a
 *          <textarea>. MARKUP IS UNCHANGED from the pre-Arise recipe.
 *
 * @traceability canon §7.3 (locked 2026-09-10), Law 6, §4.4, §3.
 *
 * ── THE PROBLEM §7.3 EXISTS TO SOLVE ───────────────────────────────────────
 * An empty field on the void reads as DISABLED. There is no fill to signal
 * "you may type here", and the old recipe's 1px --bg-color-hover border
 * disappears against #010204. So the resting seam is pitched at 0.34 —
 * deliberately well clear of dormant's 0.05, and above the card's 0.30, because
 * an input must advertise itself in a way a card need not.
 *
 * §3's rule is what makes that a requirement rather than a preference: never
 * encode meaning that exists only at the quiet end. "This field is editable" is
 * meaning, so it cannot live at 5% alpha on a display that lifts shadows.
 *
 * ── THE IMPLEMENTATION TRAP, AND WHY IT IS GONE ────────────────────────────
 * §7.3 warns: "The seam is drawn by the WRAPPER, so every seam variable must be
 * declared on the wrapper. Custom properties inherit downward only; putting
 * --seam-rgb on the inner <input> leaves var() unresolved, which invalidates
 * the whole conic-gradient and the seam silently vanishes."
 *
 * That trap is a consequence of needing a pseudo-element, which <input> cannot
 * carry. The double-layer background-clip in seam.css paints the seam on the
 * INPUT ITSELF, so there is no wrapper and no split ownership — the element
 * that declares the variables is the element that paints. The warning stands
 * for anything that does use a wrapper; it no longer applies here.
 *
 * FURNITURE (Law 6): steady run, no mid-edge dissolve.
 */

/* LABEL SKELETON [US-3.2, 0.3.8] — the "Arise Inputs" specimen's shared label
 * (used unmarked by all three input options): small muted mono, so the label
 * recedes and the field leads. Chosen by Jan over the pre-0.3.8 14px bright
 * semibold sans, comparing both on the Toolbox hub's real form. */
.nt-field {
  display: flex;
  flex-direction: column;
  gap: 6px; /* SPECIMEN */
}

.nt-label {
  font-family: var(--font-mono);
  font-size: 0.6rem; /* SPECIMEN — 9.6px */
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em; /* SPECIMEN */
  color: #55647a; /* SPECIMEN — darker than --text-muted (#64748b) */
}

.nt-input {
  /* SEAM — declared here, painted here. */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.34;
  --seam-trough: var(--seam-run); /* furniture: steady */
  --seam-peak: rgba(var(--seam-rgb), 0.62);
  --seam-fill: var(--nt-floor); /* SPECIMEN: the tinted floor, not void (seam.css) */

  --input-bloom: 0px;
  --input-bloom-a: 0;

  /* SIZE [US-3.2, 0.3.8] — a single-line field is EXACTLY --input-height, the
   * same 44px as a medium .nt-btn, so a field and a button in one row line up.
   * It was a min-height under 12px padding and a 1.5 line-height, which grew
   * it to 50px (52px for a date field) — and with no box-sizing it rendered
   * 70px tall and overflowed its container in any app without a reset. */
  box-sizing: border-box;
  width: 100%;
  height: var(--input-height);
  padding: 0 var(--spacing-md);
  border-radius: var(--border-radius); /* 0 */
  border: var(--seam-width) solid transparent;
  background: var(--nt-seam-bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  line-height: var(--line-height-input);

  /* Strip the platform look so the seam is the only boundary. */
  appearance: none;
  -webkit-appearance: none;

  /* DARK NATIVE CONTROLS [US-3.2, 0.3.8] — Arise is dark-only, but nothing
   * told the browser, so the date picker's calendar icon rendered black on the
   * void (invisible) and its pop-up calendar light. Covers date/time pickers
   * and number spinners in every consuming app. */
  color-scheme: dark;

  box-shadow: 0 0 var(--input-bloom) rgba(var(--seam-rgb), var(--input-bloom-a));

  /* NOTE [US-3.2, 0.3.8]: explicit durations. `var(--transition-base)
   * var(--ease-out)` expanded to `0.15s ease ease-out` — two timing functions,
   * invalid, so the browser dropped the whole declaration and focus snapped. */
  transition:
    background 150ms var(--ease-out),
    box-shadow 200ms var(--ease-out);
}

.nt-input::placeholder {
  color: var(--text-muted);
}

/* --- Focus ---------------------------------------------------------------- */
/* The seam IS the focus ring — no second outline. §4.5 assigns focus rings to
 * product, so this stays cyan in every app regardless of aura. */
.nt-input:focus,
.nt-input:focus-visible {
  --seam-run: 0.95;
  --seam-peak: rgba(224, 251, 255, 0.95);
  --input-bloom: 16px;
  --input-bloom-a: 0.30;
  outline: none;
}

/* --- Error ---------------------------------------------------------------- */
/* Uses the LOCKED red, consistent with destructive (§7.3).
 *
 * NOTE ON §4.4: "error as unstable emission" — irregular flicker, seam breaks,
 * widening chromatic fringe — is the canon's answer for the PRESENCE system,
 * and it is not implemented here. A field-level validation error is inline
 * messaging, which §4.4 explicitly keeps on status colour. Animating a form
 * field also fights §9's rationing: a form with four invalid inputs would have
 * four things flickering, and "if everything emits, nothing emits". The
 * unstable-emission treatment belongs to a whole summon failing, not a field.
 * [0.3.12] It now ships as `.nt-summon--unstable` (summon.css). A form with a
 * summon pairs the two — this red seam names WHICH field, the summon's fault
 * says THAT it failed (nt-blackwall US-6.7, variant B, chosen by Jan).
 */
.nt-input--error {
  --seam-rgb: 255, 48, 55; /* --aura-red, the locked red */
  --seam-run: 0.80;
  --seam-peak: color-mix(in srgb, #ff3037 48%, #fff);
}

.nt-input--error:focus,
.nt-input--error:focus-visible {
  --seam-run: 0.95;
  --seam-peak: #ffd9d9;
  --input-bloom: 16px;
  --input-bloom-a: 0.30;
}

/* --- Disabled: dormant ---------------------------------------------------- */
.nt-input:disabled {
  --seam-run: var(--dormant-seam-mid); /* 0.05 */
  --seam-peak: rgba(var(--seam-rgb), var(--dormant-peak)); /* 0.09 */
  --input-bloom: 0px;
  --input-bloom-a: 0;
  color: #33404f; /* §5 dormant name colour */
  cursor: not-allowed;
}

.nt-input:disabled::placeholder {
  color: #33404f;
}

/* --- Textarea ------------------------------------------------------------- */
.nt-textarea {
  /* A textarea grows — it opts back out of the single-line fixed height. */
  height: auto;
  padding: var(--spacing-md);
  min-height: var(--textarea-min-height);
  resize: vertical;
  line-height: var(--line-height-body);
}

/* --- Field text ----------------------------------------------------------- */
.nt-helper-text {
  margin: 0;
  font-size: var(--font-size-small);
  color: var(--text-muted);
}

.nt-error-text {
  margin: 0;
  font-size: var(--font-size-small);
  color: #ff9a9a; /* §7.1's destructive label — legible red INK, uncalibrated */
}

@media (prefers-reduced-motion: reduce) {
  .nt-input {
    transition: none;
  }
}

/**
 * @file src/components/choice.css
 * @summary The neoturne CHOICE recipe — one of a few, as a row of lit cells
 *          (segmented) or a stack of cards. CSS over NATIVE radio inputs, so
 *          selection, arrow-key movement, focus and announcement are the
 *          browser's, and the recipe stays framework-neutral.
 *
 *            <fieldset class="nt-choice">
 *              <legend class="nt-label">Gültig für</legend>
 *              <div class="nt-choice__track">
 *                <label class="nt-choice__option">
 *                  <input type="radio" name="ttl" value="3600"> 1 Std
 *                </label>
 *                …
 *              </div>
 *            </fieldset>
 *
 *          Stacked (title + description per option): add `nt-choice--stack`,
 *          and put `<span class="nt-choice__title">` and
 *          `<span class="nt-choice__desc">` inside each label.
 *
 * @traceability canon §7.9 (US-4.5, 2026-09-24), §7.1 (the ranks it borrows),
 *               §4.5 (a control is the product: cyan), Law 6 (furniture).
 *
 * ── C1, LIT CELL — chosen by Jan, 2026-09-24 ──────────────────────────────
 * From the US-4.5 specimen's three options (C1 lit cell, C2 one field with
 * interior light, C3 one field with a stripe). Every option is a SECONDARY
 * button's seam (§7.1: run .38); the chosen one takes the PRIMARY's (run .90,
 * inner light, glow, white label). No new values — a choice is a set of
 * buttons of which one is pressed, and it reads that way.
 *
 * ── WHY A TRACK ────────────────────────────────────────────────────────────
 * The options sit in `__track`, not directly in the fieldset: a <legend>
 * renders INSIDE any border or background its fieldset paints, so a group
 * surface on the fieldset swallows its own label (seen in the specimen). The
 * track also keeps the markup identical across variants — a future group
 * surface is a class change, never a markup change (§12.5).
 *
 * ── THE INPUT IS HIDDEN, NOT REMOVED ───────────────────────────────────────
 * opacity 0 over the whole cell, never `display: none` — a display-none radio
 * is unreachable by keyboard (h-construct's style-preferences page does this
 * today). The input covers the cell, so a click anywhere on it selects.
 */

.nt-choice {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.nt-choice > legend {
  padding: 0;
  margin-bottom: 6px; /* the label skeleton's 6px above the field (§7.3) */
}

.nt-choice__track {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.nt-choice__option {
  /* THE SECONDARY'S SEAM (§7.1). Declared in full on the painting element —
   * seam.css's contract; this selector is in its gradient list. */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.38;
  --seam-trough: var(--seam-run); /* furniture: steady */
  --seam-peak: rgba(var(--seam-rgb), 0.6);
  --seam-fill: var(--nt-floor);

  --choice-inner: 0px;
  --choice-inner-a: 0;
  --choice-glow: 0px;
  --choice-glow-a: 0;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--button-height-medium); /* 44px — a field and a cell share a centre */
  padding: 0 18px;
  font-family: var(--font-body);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-semibold);
  color: var(--text-secondary);
  border: var(--seam-width) solid transparent;
  border-radius: var(--border-radius); /* 0 */
  background: var(--nt-seam-bg);
  box-shadow:
    0 0 var(--choice-glow) rgba(var(--seam-rgb), var(--choice-glow-a)),
    inset 0 0 var(--choice-inner) rgba(var(--seam-rgb), var(--choice-inner-a));
  cursor: pointer;
  user-select: none;
  transition:
    box-shadow 180ms var(--ease-out),
    color 160ms var(--ease-out);
}

.nt-choice__option input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: inherit;
}

.nt-choice__option:hover:not(:has(input:disabled)) {
  color: var(--text-primary);
}

/* THE CHOSEN CELL — the primary's values (§7.1). */
.nt-choice__option:has(input:checked) {
  --seam-run: 0.9;
  --seam-peak: rgba(224, 251, 255, 0.95);
  --choice-inner: 20px;
  --choice-inner-a: 0.34;
  --choice-glow: 14px;
  --choice-glow-a: 0.24;
  color: #fff;
}

/* FOCUS — the button's ring, drawn around the cell the hidden input sits in. */
.nt-choice__option:has(input:focus-visible) {
  outline: 2px solid var(--emission-core);
  outline-offset: 3px;
}

/* DISABLED — dormant, as a disabled input (§7.3: run .05). */
.nt-choice__option:has(input:disabled) {
  --seam-run: 0.05;
  --seam-peak: rgba(var(--seam-rgb), 0.09);
  --choice-inner: 0px;
  --choice-glow: 0px;
  color: var(--text-muted);
  cursor: not-allowed;
}

/* --- Stacked: cards with a title and a description ------------------------ */
.nt-choice--stack .nt-choice__track {
  flex-direction: column;
}

.nt-choice--stack .nt-choice__option {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 12px 18px;
  text-align: start;
}

.nt-choice__title {
  font-weight: var(--font-weight-semibold);
  color: inherit;
}

.nt-choice__desc {
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-small);
  color: var(--text-muted);
}

/**
 * @file src/components/code.css
 * @summary The neoturne CODE recipe — monospace text that is read, copied or
 *          pasted exactly: a link, a command, a prompt, a secret, a document
 *          preview. A block with an optional header and footer, and an inline
 *          chip for code inside prose.
 *
 *            <div class="nt-code">
 *              <div class="nt-code__header">
 *                <span class="nt-code__label">Einmal-Link</span>
 *                <button class="nt-btn nt-btn--ghost nt-btn--sm">Kopieren</button>
 *              </div>
 *              <pre class="nt-code__body">https://…</pre>
 *              <div class="nt-code__footer">Läuft in 30 Tagen ab.</div>
 *            </div>
 *
 *            <p>Setze <code class="nt-code--inline">REDIS_URL</code> vor dem Start.</p>
 *
 *          Header and footer are optional; the copy BEHAVIOUR is the app's.
 *
 * @traceability canon §7.10 (US-4.5, 2026-09-24), §6.1 (the frame), Law 6
 *               (figures dissolve), §7.0 (the floor is the surface), §10.
 *
 * ── K3, DISSOLVING — chosen by Jan, 2026-09-24 ────────────────────────────
 * From the US-4.5 specimen's three options (K1 steady seam, K2 floor only,
 * K3 dissolving). A FIGURE's seam, as a card: the corners pool at the input's
 * rest values (run .34, peak .62, §7.3) and the mid-edges fall to the void —
 * --seam-trough below --seam-run is Law 6 as a variable assignment (seam.css).
 * A code block is content set on the page, not furniture around it.
 *
 * ── WRAPPING IS A CONTENT DECISION, NOT A STYLE ────────────────────────────
 * Default: `white-space: pre` and a horizontal scroll. A link or a shell
 * command must never wrap — a break becomes part of what gets pasted.
 * `--wrap`: prose, prompts, secrets and documents, whitespace kept exactly.
 * Both are needed (h-construct uses both; Cipher's link and its revealed
 * secret are one of each).
 *
 * ── --code-max-height IS READ WITH A FALLBACK, NEVER DECLARED HERE ──────────
 * `--scroll` caps the body at `var(--code-max-height, 400px)`. Declaring a
 * default on .nt-code itself would shadow any value an app sets on an
 * ancestor (a custom property declared on an element beats the inherited one
 * — the trap US-3.2 hit four times, seam.css §THE AURA). Set it on the block
 * or on any ancestor.
 */

.nt-code {
  /* A FIGURE (Law 6): the trough falls below the run, so the mid-edges
   * dissolve. Run and peak are the input's rest (§7.3). This selector is in
   * seam.css's gradient list. */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.34;
  --seam-trough: 0.04;
  --seam-peak: rgba(var(--seam-rgb), 0.62);
  --seam-fill: var(--nt-floor);

  display: flex;
  flex-direction: column;
  min-width: 0; /* lets the body scroll instead of widening a grid or flex parent */
  margin: 0;
  border: var(--seam-width) solid transparent;
  border-radius: var(--border-radius); /* 0 */
  background: var(--nt-seam-bg);
}

.nt-code__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  min-height: 40px;
  padding: 4px 6px 4px 16px;
}

/* The label skeleton (§7.3 amendment): mono 0.6rem, 0.2em, #55647a. */
.nt-code__label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #55647a;
}

/* THE INSET IS A MARGIN, NOT PADDING (Jan, look round 1): a scroll container
 * clips at its PADDING box, so with padding-inline a long unwrapped line ran
 * through the padding and its last characters sat on the seam. As a margin,
 * the scroller itself ends 16px short of the seam and the text clips there —
 * a steady inset, at rest and scrolled. */
.nt-code__body {
  margin: 0 16px;
  padding: 14px 0;
  font-family: var(--font-mono);
  font-size: var(--font-size-small);
  line-height: 1.7;
  color: var(--text-primary);
  white-space: pre;
  overflow-x: auto;
  tab-size: 2;
}

/* SEPARATORS DISSOLVE (§7.6): full run at the middle, trough at the ends —
 * never a ruled line, which is a containing border lying down (§10). */
.nt-code__header + .nt-code__body,
.nt-code__footer {
  background:
    linear-gradient(90deg, transparent, rgba(var(--seam-rgb), 0.22) 50%, transparent)
    top / 100% 1px no-repeat;
}

.nt-code__footer {
  padding: 10px 16px;
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--text-secondary);
}

.nt-code--wrap .nt-code__body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.nt-code--scroll .nt-code__body {
  max-height: var(--code-max-height, 400px);
  overflow-y: auto;
}

/* --- Inline: code inside prose -------------------------------------------- */
/* A steady, quieter seam (furniture at 0.22) — too small to dissolve; a
 * figure's trough would leave only four corner dots. Sized in em, so it sits
 * right in any text size; `clone` gives each wrapped line its own frame. */
.nt-code--inline {
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.22;
  --seam-trough: var(--seam-run);
  --seam-peak: rgba(var(--seam-rgb), 0.4);
  --seam-fill: var(--nt-floor);

  display: inline;
  padding: 1px 6px;
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--text-primary);
  border: var(--seam-width) solid transparent;
  border-radius: var(--border-radius); /* 0 */
  background: var(--nt-seam-bg);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/**
 * @file src/components/modal.css
 * @summary The neoturne MODAL recipe (M3), re-founded in Arise. Presentation
 *          only — focus-trap, ESC-to-close and scroll-lock remain the
 *          consumer's behaviour to wire up.
 *
 *            <div class="nt-modal-overlay">
 *              <div class="nt-modal nt-modal--md" role="dialog" aria-modal="true">
 *                <div class="nt-modal__header"><h3 class="nt-modal__title">…</h3></div>
 *                <div class="nt-modal__body">…</div>
 *                <div class="nt-modal__footer">
 *                  <button class="nt-btn nt-btn--ghost">Cancel</button>
 *                  <button class="nt-btn nt-btn--primary">Confirm</button>
 *                </div>
 *              </div>
 *            </div>
 *
 * @traceability canon §7.2 (locked 2026-09-10), §8, §10, Law 6.
 *
 * ── ABOVE BY TWO MECHANISMS AT ONCE ────────────────────────────────────────
 * Elevation-as-lighter-surface is retired (§10), so "on top" has to be said
 * some other way. §7.2 says it twice:
 *
 *   THE WORLD DIMS.  The page behind drops toward dormant — seams 2%, icons
 *                    10%, text 22% — behind a 78% scrim. This is the ensemble
 *                    dim from the card grid (§9), applied at page scale. No new
 *                    vocabulary.
 *   THE MODAL BURNS. Seam 95%, a 60px inner light, a 46px bloom.
 *
 * Either alone was tested and rejected: dimming only left the modal flat with
 * nothing to be brighter than; burning only put two lit layers in competition.
 * That is why both are here and neither is tunable down without the other.
 *
 * FURNITURE (Law 6): a steady run. A modal is a container, not a summon.
 */

.nt-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal-backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-lg);

  /* THE 78% SCRIM (§7.2). Not the --scrim token, which is 60% — §7.2 names a
   * specific figure, and the modal's brightness was chosen against it. */
  background-color: rgba(0, 0, 0, 0.78);
}

/* ── THE ENSEMBLE DIM ──────────────────────────────────────────────────────
 * §7.2's "the world dims" needs a hook on an ancestor of the page content,
 * which a modal cannot reach from inside itself. Consumers apply this to the
 * root or app shell while a modal is open — the same category of wiring as the
 * focus-trap and scroll-lock this file already delegates.
 *
 * The scrim alone already reads as dimmed; this makes the page behind go
 * genuinely DORMANT rather than merely darkened, which is what stops residual
 * emission competing with the modal through the scrim.
 */
/* NOTE [US-3.2, 0.3.8] — THE DIM IS FOR THE PAGE, NEVER THE MODAL. The header
 * tells consumers to put .nt-modal-open on the root; every selector below
 * therefore excludes `.nt-modal-overlay *`. Without that, the modal's own Save
 * and Cancel lost their seams — the opposite of "the modal burns" (§7.2). */
.nt-modal-open :is(.nt-card, .nt-btn, .nt-input, .nt-shell):not(.nt-modal-overlay *) {
  --seam-run: 0.02;
  --seam-trough: 0.02;
  --seam-peak: rgba(var(--seam-rgb), 0.02);
  --card-bloom-a: 0;
  --btn-glow-a: 0; /* was --btn-bloom-a, which no button reads — glow survived */
  --btn-inner-a: 0;
  --input-bloom-a: 0;
}

/* `[data-nt-summon]` rather than a `.nt-icon` class: the system has no icon
 * recipe, and inventing a class name here would imply an API that does not
 * exist. A data attribute reads unambiguously as an opt-in hook — mark whatever
 * plays the summon's part in your markup. */
.nt-modal-open [data-nt-summon]:not(.nt-modal-overlay *) {
  opacity: 0.10;
}

/* TEXT 22% (§7.2). Colour on the containers, plus opacity on the text-bearing
 * furniture that sets its own colour — .nt-card__body and the aura page
 * furniture stayed bright before 0.3.8. Loose app text is the app's to dim. */
.nt-modal-open :is(.nt-card, .nt-shell):not(.nt-modal-overlay *) {
  color: rgba(241, 245, 249, 0.22);
}

.nt-modal-open :is(.nt-card__header, .nt-card__body, .nt-shell__title, .nt-page__eyebrow, .nt-page__heading, .nt-page__rule):not(.nt-modal-overlay *) {
  opacity: 0.22;
}

/* ── THE MODAL ─────────────────────────────────────────────────────────────*/
.nt-modal {
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.95;
  --seam-trough: var(--seam-run); /* furniture: steady */
  --seam-peak: rgba(224, 251, 255, 0.98);
  --seam-fill: var(--nt-floor); /* SPECIMEN: the tinted floor, not void (seam.css) */

  position: relative;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  max-width: 100%;
  max-height: 85vh;
  border-radius: var(--border-radius); /* 0 */
  border: var(--seam-width) solid transparent;
  background: var(--nt-seam-bg);
  color: var(--text-primary);

  /* IT BURNS: a 60px inner light and a 46px bloom (§7.2). No cast shadow —
   * there is no light source in this world to cast one (§10). */
  box-shadow:
    inset 0 0 60px rgba(var(--seam-rgb), 0.18),
    0 0 46px rgba(var(--seam-rgb), 0.34);

  /* ARISE ON OPEN (§7.2): 340ms, overshoot to 1.015, a 10px rise. */
  animation: nt-modal-arise 340ms cubic-bezier(0.22, 1.2, 0.36, 1) both;
}

/* The overshoot IS the arrival; without it the animation is merely polite
 * (§8). Scale runs 0.94 -> 1.015 -> 1.0 rather than easing flat into place. */
@keyframes nt-modal-arise {
  0% {
    opacity: 0;
    transform: translateY(10px) scale(0.94);
  }
  72% {
    opacity: 1;
    transform: translateY(0) scale(1.015);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* --- Sizes ---------------------------------------------------------------- */
.nt-modal--sm {
  width: var(--modal-width-small);
}

.nt-modal--md {
  width: var(--modal-width-medium);
}

.nt-modal--lg {
  width: var(--modal-width-large);
}

/* --- Sections ------------------------------------------------------------- */
/* Dividers are emissive hairlines, not --bg-color-hover lines (§10 retires the
 * containing border). Held quiet: the modal's own seam is already at 95%, and
 * §9 rations emission to one awakened element per view. */
.nt-modal__header {
  padding: var(--spacing-lg);
  border-bottom: 1px solid rgba(var(--seam-rgb), 0.14);
}

.nt-modal__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--font-size-large);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  color: var(--text-primary);
}

.nt-modal__body {
  flex: 1;
  padding: var(--spacing-lg);
  overflow-y: auto;
  color: var(--text-secondary);
}

.nt-modal__body p {
  margin: 0 0 var(--spacing-base) 0;
}

.nt-modal__body ul {
  margin: var(--spacing-base) 0;
  padding-left: var(--spacing-lg);
}

.nt-modal__body li {
  margin-bottom: var(--spacing-sm);
}

.nt-modal__footer {
  display: flex;
  gap: var(--spacing-base);
  justify-content: flex-end;
  padding: var(--spacing-lg);
  border-top: 1px solid rgba(var(--seam-rgb), 0.14);
}

@media (prefers-reduced-motion: reduce) {
  .nt-modal {
    animation: none;
  }
}

/**
 * @file src/components/toast.css
 * @summary The neoturne TOAST recipe (T1+T2), re-founded in Arise. Presentation
 *          only — dismissal timing, stacking and the positioning container
 *          remain the consumer's behaviour.
 *
 *            <div class="nt-toast nt-toast--success">
 *              <div class="nt-toast__icon">✓</div>
 *              <div class="nt-toast__content">
 *                <p class="nt-toast__title">Saved</p>
 *                <p class="nt-toast__message">Your changes were saved.</p>
 *              </div>
 *            </div>
 *
 * @traceability canon §7.4 (locked 2026-09-10), §4.2, §4.4, Law 6.
 *
 * ── STATUS TAKES THE SEAM AND THE STRIPE ───────────────────────────────────
 * Decided 2026-09-11, from the remainder specimen: the T1 and T2 options were
 * combined rather than chosen between. The status owns the WHOLE SEAM as in T1
 * — run .55, a peak mixed 45% toward white, an 18px halation — AND keeps T2's
 * 2px stripe on the leading edge with its own 12px glow.
 *
 * This deliberately overrides the argument §7.4 was written with, which held
 * that four more full-strength hues would undo the reasoning that closed the
 * aura set at four (§4.2). The counter-argument that won: a toast is
 * transient and has to be read at a glance, and unlike a card it never sits in
 * a grid of siblings competing for identity. §7.4 and the arise.status token
 * description both still say otherwise and want updating.
 *
 * The status values are the EMISSIVE set (arise.status), lightened to read
 * against the void. The pre-Arise --success-color family was chosen for a
 * #0f172a ground and goes muddy on #010204. Error is the locked red, the same
 * value destructive and input-error use.
 *
 * FURNITURE (Law 6): steady run. Fills are retired (§10) — the old recipe
 * tinted the whole toast with --{status}-bg and put a filled circle behind the
 * icon; both are gone. The surface is void, and the stripe is the signal.
 */

.nt-toast {
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  /* T1 + T2 COMBINED (decided 2026-09-11). The status owns the whole seam, as
   * in specimen T1, AND keeps T2's stripe on the leading edge. Each variant
   * therefore sets --seam-rgb as well as --toast-status. */
  --seam-run: 0.55; /* T1 */
  --seam-trough: var(--seam-run); /* furniture: steady */
  --seam-peak: color-mix(in srgb, rgb(var(--seam-rgb)) 45%, #fff); /* T1 */
  --seam-fill: var(--nt-floor); /* the tinted floor (seam.css) */

  /* Set per status variant. Cyan default so a bare .nt-toast still renders. */
  --toast-status: var(--emission);

  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-base);
  min-width: var(--toast-min-width);
  max-width: var(--toast-max-width);
  padding: var(--spacing-base) var(--spacing-lg);
  padding-inline-start: calc(var(--spacing-lg) + 2px);
  border-radius: var(--border-radius); /* 0 */
  border: var(--seam-width) solid transparent;
  background: var(--nt-seam-bg);
  box-shadow: 0 0 18px rgba(var(--seam-rgb), 0.14); /* T1 */
}

/* THE 2px STATUS STRIPE on the leading edge (§7.4).
 *
 * Drawn as a pseudo-element rather than a thick border-inline-start: the seam
 * occupies the element's border box via background-clip, so widening one border
 * side would distort the conic gradient's geometry and break the 1px ring.
 * ::before is free here precisely because the seam does not use it.
 *
 * `inset-inline-start` so the stripe follows writing direction — "leading edge"
 * is what §7.4 says, not "left".
 */
.nt-toast::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 2px;
  background: var(--toast-status);
  box-shadow: 0 0 12px rgba(var(--seam-rgb), 0.7); /* T2 */
  pointer-events: none;
}

/* --- Status variants ------------------------------------------------------ */
/* Two properties each. --toast-status is the hex the stripe and icon paint
 * with; --seam-rgb is the same colour as a triple, because the seam composes
 * alpha (rgba(var(--seam-rgb), …)) and cannot take a hex. Keep the pair in
 * step — they are the same colour said twice, and the only way a status can
 * end up mismatched is by editing one and not the other.
 *
 * The triples are declared here rather than tokenised, matching how
 * --aura-rgb is handled in card.css and shell.css. */
.nt-toast--success {
  --toast-status: var(--status-success);
  --seam-rgb: 52, 211, 153; /* #34d399 */
}

.nt-toast--error {
  --toast-status: var(--status-error);
  --seam-rgb: 255, 48, 55; /* #ff3037 — the locked red */
}

.nt-toast--warning {
  --toast-status: var(--status-warning);
  --seam-rgb: 251, 191, 36; /* #fbbf24 */
}

.nt-toast--info {
  --toast-status: var(--status-info);
  --seam-rgb: 96, 165, 250; /* #60a5fa */
}

/* --- Icon ----------------------------------------------------------------- */
/* No filled circle. The glyph IS the mark — hard-edged, in the status colour,
 * with a halation sized to the element (Law 4: 2.5px on ~18px type, the same
 * proportion as 4px on a 96px glyph).
 *
 * The glow is drop-shadow rather than box-shadow so it follows the GLYPH's
 * alpha instead of a box behind it (§6.2).
 */
.nt-toast__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  margin-top: 2px;
  font-size: var(--font-size-large);
  font-weight: var(--font-weight-bold);
  color: var(--toast-status);
  filter: drop-shadow(0 0 2.5px var(--toast-status));
}

/* --- Content -------------------------------------------------------------- */
.nt-toast__content {
  flex: 1;
}

.nt-toast__title {
  margin: 0 0 var(--spacing-xs) 0;
  font-family: var(--font-heading);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-wide);
  color: var(--text-primary);
}

.nt-toast__message {
  margin: 0;
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--text-secondary);
}

/**
 * @file src/components/notice.css
 * @summary The neoturne NOTICE recipe — an inline message that stays on the
 *          page: an explained state, a warning, an error with a way out. The
 *          toast's construction standing still.
 *
 *            <div class="nt-notice nt-notice--warning" role="status">
 *              <div class="nt-notice__content">
 *                <p class="nt-notice__title">Bereits geöffnet</p>
 *                <p class="nt-notice__body">Am <strong>24.09. um 14:02</strong>.</p>
 *              </div>
 *              <div class="nt-notice__actions">
 *                <button class="nt-btn nt-btn--secondary nt-btn--sm">…</button>
 *              </div>
 *            </div>
 *
 *          Title and actions are optional. Use role="alert" only for an error
 *          that appears in response to what the user just did; role="status"
 *          otherwise. Not dismissible — a notice explains a state and goes
 *          when the state does.
 *
 * @traceability canon §7.11 (US-4.5, 2026-09-24), §7.4 (the construction it
 *               reuses), §4.4 (status colours, never the aura), Law 6.
 *
 * ── N1, STRIPE + SEAM — chosen by Jan, 2026-09-24 ─────────────────────────
 * From the US-4.5 specimen's three options (N1 stripe + seam, N2 stripe only,
 * N3 seam only). The toast's two signals — the status owns the seam AND the
 * 2px leading stripe with its 12px glow (§7.4) — at the §7.4 TABLE's run .30
 * rather than the toast's .55: a toast is read at a glance and leaves; a
 * notice stays on screen for minutes, beside the content it explains.
 *
 * ── STATUS, NOT AURA (§4.4) ────────────────────────────────────────────────
 * "Already opened" is a state, not the app's identity. The status hues are the
 * toast's emissive set; each variant sets the pair (--notice-status as the
 * stripe's colour, --seam-rgb as the same colour for alpha) — keep them in
 * step, as toast.css says of its own pair. --quiet has no status: the empty
 * and "nothing here" lines, in the muted slate.
 */

.nt-notice {
  /* FURNITURE (Law 6): steady run. This selector is in seam.css's list. */
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.3; /* §7.4's table — the toast itself runs .55 */
  --seam-trough: var(--seam-run);
  --seam-peak: color-mix(in srgb, rgb(var(--seam-rgb)) 55%, transparent);
  --seam-fill: var(--nt-floor);

  /* Cyan default so a bare .nt-notice still renders. */
  --notice-status: var(--emission);

  position: relative;
  display: flex;
  align-items: center;
  gap: var(--spacing-base);
  padding: 12px 16px;
  padding-inline-start: calc(16px + 2px); /* the stripe's width, as the toast */
  border: var(--seam-width) solid transparent;
  border-radius: var(--border-radius); /* 0 */
  background: var(--nt-seam-bg);
}

/* THE 2px STATUS STRIPE on the leading edge — the toast's, and drawn the same
 * way for the same reason: a thicker border-inline-start would distort the
 * seam's conic geometry (toast.css). */
.nt-notice::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 2px;
  background: var(--notice-status);
  box-shadow: 0 0 12px rgba(var(--seam-rgb), 0.7);
  pointer-events: none;
}

/* --- Status variants — the toast's emissive set (§7.4) ---------------------- */
.nt-notice--success {
  --notice-status: var(--status-success);
  --seam-rgb: 52, 211, 153; /* #34d399 */
}

.nt-notice--error {
  --notice-status: var(--status-error);
  --seam-rgb: 255, 48, 55; /* #ff3037 — the locked red */
}

.nt-notice--warning {
  --notice-status: var(--status-warning);
  --seam-rgb: 251, 191, 36; /* #fbbf24 */
}

.nt-notice--info {
  --notice-status: var(--status-info);
  --seam-rgb: 96, 165, 250; /* #60a5fa */
}

.nt-notice--quiet {
  --notice-status: var(--text-muted);
  --seam-rgb: 100, 116, 139; /* #64748b — --text-muted as a triple */
}

/* --- Content and actions --------------------------------------------------- */
.nt-notice__content {
  flex: 1;
  min-width: 0;
}

.nt-notice__title {
  margin: 0 0 2px;
  font-family: var(--font-heading);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-wide);
  color: var(--text-primary);
}

.nt-notice__body {
  margin: 0;
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--text-secondary);
}

.nt-notice__body strong {
  color: var(--text-primary);
}

.nt-notice__actions {
  display: flex;
  flex-shrink: 0;
  gap: var(--spacing-sm);
}

/**
 * @file src/components/shell.css
 * @summary The neoturne SHELL recipe (S2) — NEW in Arise. The containers that
 *          hold everything else: nt-construct's headerCard and mainCard, an
 *          app's page frame, a sidebar.
 *
 *            <header class="nt-shell">…</header>
 *            <main class="nt-shell">…</main>
 *
 * @traceability canon §7.6 (locked 2026-09-10), Law 6, §9, §4.5.
 *
 * ── PRESENCE IS NOT IMPORTANCE ─────────────────────────────────────────────
 * §7.6 states this outright, and it is the only place in the canon where the
 * presence axis inverts:
 *
 *   "A shell must be PRESENT — you have to see where the app's boundary is —
 *    while ranking BELOW its own contents. The presence axis was built for
 *    figures, where more presence means more important. Containers invert that,
 *    and the rule has to be stated or someone will reasonably pitch a shell at
 *    Attention and wonder why the page feels wrong."
 *
 * So a shell is pitched at 0.42 — brighter than a resting card's 0.30, and far
 * below Attention's 0.62. It out-brightens its contents WITHOUT competing with
 * them, and the reason that works is Law 6, not intensity:
 *
 *   "A shell can be brighter than the cards it contains without competing with
 *    them, because the two read as different KINDS — a continuous outline
 *    holding corner-pooled, fading figures. Intensity alone would have forced
 *    every container to stay dim."
 *
 * That is the load-bearing idea. The shell holds a STEADY run all the way
 * round; the cards inside DISSOLVE at their mid-edges. Same hue, same rough
 * intensity, unmistakably different objects. Take the steady run away and the
 * shell becomes just a brighter card, and the hierarchy collapses.
 *
 * ── WHY IT IS CYAN, ALWAYS ─────────────────────────────────────────────────
 * §4.5 puts "shell seam" under PRODUCT. The shell is neoturne's own furniture,
 * identical in every app in the suite; the aura belongs to the page and the
 * session inside it. Twelve apps sharing one shell colour is what makes them
 * read as one system rather than twelve themes.
 *
 * It also never wakes. There is no --shell--awakened: §9 rations emission to
 * one awakened element per view, and a container is never the thing that
 * matters most on a page.
 */

.nt-shell {
  --seam-rgb: 34, 211, 238;
  --seam-cal: 1;
  --seam-run: 0.42;
  --seam-trough: var(--seam-run); /* FURNITURE — the steady run IS the point */
  --seam-peak: rgba(var(--seam-rgb), 0.75);
  --seam-fill: var(--nt-floor); /* SPECIMEN: the tinted floor, not void (seam.css) */

  position: relative;
  border-radius: var(--border-radius); /* 0 */
  border: var(--seam-width) solid transparent;
  background: var(--nt-seam-bg);
  color: var(--text-primary);

  /* A wide, very faint bloom: 40px @ .06. Enough to place the boundary in
   * space, far too weak to read as a focal element. */
  box-shadow: 0 0 40px rgba(var(--seam-rgb), 0.06);
}

/* --- Padding -------------------------------------------------------------- */
/* Reuses the card padding tokens rather than inventing shell-specific ones —
 * the 8px grid is explicitly kept (§10), and a container's density has no
 * reason to differ from a figure's. */
.nt-shell {
  padding: var(--card-padding-default);
}

.nt-shell--compact {
  padding: var(--card-padding-compact);
}

.nt-shell--spacious {
  padding: var(--card-padding-spacious);
}

/* --- Flush ---------------------------------------------------------------- */
/* For shells whose children manage their own edges (a table, a nav strip). */
.nt-shell--flush {
  padding: 0;
}

/* --- Sections ------------------------------------------------------------- */
/* §4.5 assigns "table and column headers" to product, so these stay cyan. */
.nt-shell__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-base);
  padding-bottom: var(--spacing-lg);
  margin-bottom: var(--spacing-lg);
  border-bottom: 1px solid rgba(var(--seam-rgb), 0.14);
}

/* THE APP NAME — product, so cyan (§4.5), in the specimen's type [US-3.2]:
 * "Construct in Arise" sets it Rajdhani 600 at 1rem with 0.24em tracking. The
 * pre-0.3.7 recipe was 18px bold at 0.05em, which read as a heading rather
 * than a name. Jan, 2026-09-23: canon's colour, specimen's type. */
.nt-shell__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.24em; /* SPECIMEN — no token this wide */
  color: var(--emission);
}

.nt-shell__body {
  color: var(--text-secondary);
}

.nt-shell__footer {
  padding-top: var(--spacing-lg);
  margin-top: var(--spacing-lg);
  border-top: 1px solid rgba(var(--seam-rgb), 0.14);
}

/* --- The page eyebrow and heading: TENANCY, so the aura ------------------- */
/* §4.5's allocation table puts "page eyebrow and heading" and "section rules"
 * under AURA — the page you are looking at belongs to this app, where the
 * shell and the mark belong to neoturne. Consumers set --aura-rgb per app;
 * cyan is the default (seam.css :root) so an app that sets nothing still
 * renders. NOTE: these classes must NOT redeclare the aura themselves — until
 * 0.3.6 each did, which shadowed every app's value and left them cyan.
 *
 * NOTE: `cal` is NOT applied here. These are INK, and §4.3 is explicit —
 * "Calibration applies to light, never to ink… legibility outranks luminance
 * matching there." The section rule below IS light, so it does get cal.
 */
/* NOTE [US-3.2, 0.3.7]: metrics now follow the "Construct in Arise" specimen,
 * resolving the two divergences canon §7.8 recorded as "unverified against the
 * specimen" — the heading was 32px where the specimen is 24px, and the rule
 * was a block under the heading where the specimen runs it inline beside it.
 * Measured side by side in Chrome before changing. */
.nt-page__eyebrow {
  margin: 0 0 6px 0;
  font-family: var(--font-mono);
  font-size: 0.62rem; /* SPECIMEN */
  text-transform: uppercase;
  letter-spacing: 0.24em; /* SPECIMEN */
  color: rgba(var(--aura-rgb), 0.85); /* SPECIMEN — ink, so no cal */
}

/* THE HEADING ROW — heading, then the rule running inline to the right.
 * Also the pattern for a section heading inside a shell. */
.nt-page__head {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-base);
  margin-bottom: 12px; /* SPECIMEN — 1.5 steps */
}

.nt-page__heading {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.5rem; /* SPECIMEN — 24px */
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.16em; /* SPECIMEN */
  line-height: var(--line-height-heading);
  white-space: nowrap;
  color: rgb(var(--aura-rgb));
}

/* THE RULE — light, so calibrated (§4.3). The specimen's 45% → transparent was
 * approved on violet (cal 1), so multiplying by --aura-cal reproduces it there
 * exactly and dims only the hues that need it. Works standalone as a block
 * <hr>, or inline as the last child of .nt-page__head. */
.nt-page__rule {
  height: 1px;
  border: 0;
  margin: var(--spacing-lg) 0;
  background: linear-gradient(
    90deg,
    rgba(var(--aura-rgb), calc(0.45 * var(--aura-cal))),
    transparent
  );
}

.nt-page__head > .nt-page__rule {
  flex: 1;
  min-width: var(--spacing-lg);
  margin: 0;
}

/**
 * @file src/components/mark.css
 * @summary THE MARK — the neoturne wordmark as a recipe, not a file.
 *
 * @traceability canon §6.6 (the mark), §6.6.3 (the halation), §6.6.5 (sizing),
 *               §4.5 (the mark is product, never tenancy), Law 4 (blur is a
 *               fraction of the em box), Law 2's amendment (the pool is legal
 *               because it is additive). FR-008.
 *
 * ── WHY A RECIPE AND NOT AN ASSET ──────────────────────────────────────────
 * A rendered asset is a picture, and a picture cannot be shaded. The pool is a
 * gradient across the WORD and the halation is four blurred copies of it, so the
 * mark has to be drawn rather than fetched. Shipping it as CSS also means it
 * emits to Dart and Python with everything else (§13) and nt-deck cannot drift
 * from the web.
 *
 * ── THE CONTRACT ───────────────────────────────────────────────────────────
 * Markup, once per placement. The path data comes from the package's own
 * `mark` export (dist/glyphs.js), so the consumer never hand-copies a `d`:
 *
 *   <span class="nt-mark nt-mark--fused nt-mark--full">
 *     <svg class="nt-mark__ghost" viewBox="…" aria-hidden="true">…</svg>
 *     <svg class="nt-mark__halo nt-mark__halo--base"  viewBox="…" aria-hidden="true">…</svg>
 *     <svg class="nt-mark__halo nt-mark__halo--lead"  viewBox="…" aria-hidden="true">…</svg>
 *     <svg class="nt-mark__halo nt-mark__halo--trail" viewBox="…" aria-hidden="true">…</svg>
 *     <svg class="nt-mark__halo nt-mark__halo--core"  viewBox="…" aria-hidden="true">…</svg>
 *     <svg class="nt-mark__ink" viewBox="…" role="img" aria-label="neoturne">…</svg>
 *   </span>
 *
 * The GHOST is the only layer in flow; it is invisible and exists to give the
 * box its size, because every other layer is absolutely positioned over it.
 *
 * Two cut modifiers — `--display` (`+0.055em`) and `--fused` (`−0.030em`) — and
 * two construction modifiers, `--full` and `--flat`. Pick one of each.
 *
 * ── SIZE THE MARK BY WIDTH ─────────────────────────────────────────────────
 * Every construction value in §6.6.3 is a fraction of the EM BOX, which is the
 * viewBox's 660 units in CSS pixels. Call sites size by `width` instead, and
 * keeping `width: 10rem` untouched is the whole reason two cuts exist.
 *
 * So the recipe recovers the em box from the width it is actually given:
 * `container-type: inline-size` makes the mark its own query container, and
 * `100cqw` inside it is the rendered width in pixels. Dividing by the cut's
 * width factor yields `h`. ONE source of truth — the size cannot be declared
 * twice and disagree with itself, which is what a `--nt-mark-h` custom property
 * would have allowed.
 *
 * ITS ONE COST, stated plainly: `container-type: inline-size` applies
 * `contain: inline-size`, so an element whose width comes from its CONTENTS
 * collapses. Size the mark by `width` (or give it a definite inline size). A
 * consumer sizing by `height` with `width: auto` — nt-main is the only one —
 * must set a width instead.
 *
 * ── THE FLOOR IS ENFORCED HERE, NOT DOCUMENTED ─────────────────────────────
 * §6.6.5 puts the floor at a 30px em box and it is a judgement, not a
 * measurement. Below it the recipe renders NOTHING rather than something
 * illegible: a mark too small to read is worse than an absent one, because it
 * still claims the space and still says this is what the brand looks like.
 *
 * The thresholds below are the em-box floor times each cut's width factor, and
 * they are DERIVED in wordmark.json (`cuts.<id>.minWidthPx`). A container query
 * cannot read a custom property, so they are transcribed here — and a parity
 * test asserts these numbers still equal the generated ones, because a
 * transcribed constant with no check is a constant that drifts.
 *
 * ── THE MARK IS CYAN IN EVERY APP ──────────────────────────────────────────
 * §4.5 places the mark under PRODUCT. It never carries the tenancy aura, not
 * even in neoturne's own surfaces — the retired wordmark ran a gradient from the
 * tenancy hue to the product hue, across the one boundary that makes twelve apps
 * read as one suite. Nothing in this file may reference `--aura-*`, and a test
 * enforces that.
 */

.nt-mark {
  /* The cut modifier sets this. Ink width over canvas height — see the cut
     table in wordmark.json, where it is derived rather than typed. */
  --nt-mark-factor: 5.162;

  /* THE EM BOX, recovered from the width actually rendered. Every value below
     is a fraction of it, so this is the one number the recipe needs. */
  --nt-mark-h: calc(100cqw / var(--nt-mark-factor));

  container-type: inline-size;
  position: relative;
  display: inline-block;

  /* The layers are stacked boxes, not text; any leading would offset them. */
  line-height: 0;
}

.nt-mark--display {
  --nt-mark-factor: 6.064;
}

.nt-mark--fused {
  --nt-mark-factor: 5.162;
}

/* Every layer draws the same outlines at the same size. `overflow: visible`
   because the halation blurs OUTSIDE the ink's extents, and the viewBox is
   bound to the ink — a clipped glow is a rectangle with a soft edge. */
.nt-mark > svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* IN FLOW, INVISIBLE. It gives the box its height via the intrinsic ratio so
   the absolutely positioned layers have something to sit on. `visibility`
   rather than `display: none`, which would remove it from flow and collapse
   the box it exists to create. */
.nt-mark__ghost {
  visibility: hidden;
}

.nt-mark__halo,
.nt-mark__ink {
  position: absolute;
  inset: 0;
}

/* THE POOL. An additive gradient across the whole word, peaking on the `t` —
   the only letter that rises above the x-height, so the brightest point and the
   tallest point are the same point. Consumers paint it with the gradient the
   `mark` export describes; the recipe only positions the layer. */
.nt-mark__ink {
  fill: var(--emission);
}

/* ── THE HALATION — canon §6.6.3, four layers ──────────────────────────────
   Blur is a fraction of the em box; displacement is in FONT UNITS converted
   against the same em box, so both scale with the mark exactly. A fixed-pixel
   offset would be a fraction of the letterform at 100px and a multiple of it
   at 16px.

   `screen` because the halation is LIGHT: it may only ever add. Nothing here
   is darker than the ink (rule 1), there are two radii rather than one (rule
   2), and a concentric base sits under the displaced layers (rule 3) — light
   on one side and nowhere else is a second copy, not a source. */
.nt-mark__halo {
  mix-blend-mode: screen;
  /* Font units to pixels: the canvas is 660 units tall. */
  --nt-mark-unit: calc(var(--nt-mark-h) / 660);
}

.nt-mark__halo--base {
  fill: var(--emission);
  opacity: 0.55;
  filter: blur(calc(var(--nt-mark-h) * 0.085));
}

.nt-mark__halo--lead {
  fill: var(--halation-lead);
  opacity: 0.55;
  filter: blur(calc(var(--nt-mark-h) * 0.038));
  translate: calc(var(--nt-mark-unit) * 40);
}

.nt-mark__halo--trail {
  fill: var(--halation-trail);
  opacity: 0.55;
  filter: blur(calc(var(--nt-mark-h) * 0.013));
  translate: calc(var(--nt-mark-unit) * -22);
}

.nt-mark__halo--core {
  fill: var(--emission-core);
  opacity: 0.95;
  filter: blur(calc(var(--nt-mark-h) * 0.013));
  translate: calc(var(--nt-mark-unit) * 24);
}

/* ── FLAT — the print and one-colour variant ───────────────────────────────
   NOT a degraded Full. The pool alone still reads as this mark, which is what
   makes it usable where light cannot be: paper, a single ink, a dark value on
   a pale ground. */
.nt-mark--flat .nt-mark__halo {
  display: none;
}

/* ── THE FLOOR ─────────────────────────────────────────────────────────────
   30px em box × the cut's width factor. Derived in wordmark.json as
   `minWidthPx`; transcribed here because a container query cannot read a
   custom property, and checked by the parity test so it cannot drift. */
/* THE DOUBLE CLASS IS LOAD-BEARING, not a style. A container query adds NO
   specificity — the rule inside it is an ordinary rule — so `.nt-mark--display
   > *` at (0,1,0) loses to `.nt-mark > svg { display: block }` at (0,1,1) and
   the mark goes on drawing below the floor with nothing failing. Matching both
   classes takes it to (0,2,0), which outranks the base rule wherever this file
   lands in the cascade. */
@container (width < 182px) {
  .nt-mark.nt-mark--display > * {
    display: none;
  }
}

@container (width < 155px) {
  .nt-mark.nt-mark--fused > * {
    display: none;
  }
}

/**
 * @file src/components/summon.css
 * @summary The neoturne SUMMON recipe — a glyph as a live element on the
 *          presence axis, and the error that happens TO it.
 *
 *            <div class="nt-summon" data-presence="summoned" aria-hidden="true">
 *              <svg class="nt-summon__halo" viewBox="0 0 48 48">
 *                <path class="nt-summon__line" d="…outline…"/>
 *              </svg>
 *              <svg class="nt-summon__core" viewBox="0 0 48 48">
 *                <path class="nt-summon__line" d="…outline…"/>
 *                <path class="nt-summon__plate" d="…plate…"/>
 *                <circle class="nt-summon__joint" cx="…" cy="…" r="1"/>
 *              </svg>
 *            </div>
 *
 *          The path data is glyphs.json's (`outline`, `plate`, `joints`) —
 *          take it from the package, never redraw it. `outlineHeavy` paths add
 *          `nt-summon__line--heavy`. `data-presence`: dormant · summoned
 *          (default) · charging · awakened. `nt-summon--unstable` plays the
 *          error once; re-add it (after a reflow) to play it again.
 *
 *          THE PAGE MUST CARRY THE TWO GRADIENTS, once, inline — an SVG paint
 *          server is resolved within the document, never across files:
 *
 *            <svg width="0" height="0" aria-hidden="true" style="position:absolute">
 *              <defs>
 *                <linearGradient id="ntSummonSeam" gradientUnits="userSpaceOnUse"
 *                                x1="0" y1="0" x2="48" y2="48">
 *                  <stop offset="0%" stop-color="#0b7f94"/>
 *                  <stop offset="28%" stop-color="#22d3ee"/>
 *                  <stop offset="52%" stop-color="#e0fbff"/>
 *                  <stop offset="74%" stop-color="#22d3ee"/>
 *                  <stop offset="100%" stop-color="#0b7f94"/>
 *                </linearGradient>
 *                <linearGradient id="ntSummonPlate" x1="0" y1="0" x2="0.4" y2="1">
 *                  <stop offset="0%" stop-color="#9df1ff" stop-opacity=".85"/>
 *                  <stop offset="45%" stop-color="#22d3ee" stop-opacity=".42"/>
 *                  <stop offset="100%" stop-color="#06b6d4" stop-opacity=".12"/>
 *                </linearGradient>
 *              </defs>
 *            </svg>
 *
 *          Without them the awakened stroke and the plate paint nothing, with
 *          no error. Bundled consumers in React can render the same two
 *          gradients as the design book's GlyphSet does.
 *
 * @traceability canon §6.2 (construction), §5 (presence values), §4.4 (error
 *               as behaviour), §9 (rationing), §4.5 (the summon is product).
 * @story nt-blackwall US-6.7 — specimen claude.ai/artifact/KRwghthzotCmKkznuinVYs,
 *        approved by Jan 2026-10-13.
 *
 * ── WHY THIS IS A RECIPE NOW ───────────────────────────────────────────────
 * Until 0.3.12 every summon was built by its consumer: the design book in JSX
 * (GlyphSet, PresenceStates), nt-deck in Dart. Blackwall — a plain page with
 * no build step — needed one too, and the rule is to fix the package, not the
 * app. This is the design book's construction with §5's values, unchanged.
 *
 * ── ALWAYS CYAN ───────────────────────────────────────────────────────────
 * §4.5 lists the summon under PRODUCT. Nothing here reads --aura-rgb: the
 * aura belongs to the frame around a summon, never to the summon.
 *
 * ── UNSTABLE: §4.4, ON THE SUMMON, NOT THE FIELD ──────────────────────────
 * §4.4 makes error "a behaviour, not a hue: the emission goes unstable —
 * irregular flicker, seam breaks, the chromatic fringe widening". input.css
 * keeps a field's error on the red seam and reserves this for "a whole summon
 * failing" — §9 forbids four flickering fields on one form. Blackwall's
 * specimen (variant B, chosen by Jan) uses BOTH: the field's red seam says
 * WHICH input, the summon's instability says THAT it failed.
 */

.nt-summon {
  /* PRESENCE — summoned by default (§5: flat, even stroke at 0.75). */
  --summon-glyph: var(--summoned-glyph);
  --summon-plate: 0;
  --summon-joint: 0;
  --summon-bloom: 3px; /* §5 icon bloom, summoned */
  --summon-bloom-a: 0.35;
  --summon-edge: 3px; /* §5 icon hot edge, summoned */
  --summon-edge-a: 0.25;
  --summon-stroke: var(--emission);
  --summon-stroke-w: 1.7; /* glyphs.json meta.strokeWidth.summoned */
  --summon-stroke-heavy: 3.4; /* glyphs.json meta.strokeWidthHeavy.summoned */

  position: relative;
  display: block;
  width: 64px; /* consumers size it; the glyph scales with the box */
  height: 64px;
  flex: none;
}

.nt-summon > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* THE HALO — a blurred copy of the OUTLINE ONLY (§6.2). Its own element,
 * because filter chains are sequential: a later stage cannot be sourced from
 * the original alpha. Plate and joints are never in it — blurring a filled
 * path inside a hollow glyph fogs the interior. */
.nt-summon__halo {
  opacity: var(--summon-bloom-a);
  filter: blur(var(--summon-bloom));
  mix-blend-mode: screen;
  transition:
    opacity 300ms var(--ease-out),
    filter 300ms var(--ease-out);
}

.nt-summon__halo .nt-summon__plate,
.nt-summon__halo .nt-summon__joint {
  display: none;
}

/* THE CORE — drop-shadow, NOT box-shadow: the hot edge follows the stroke's
 * alpha, so the glow is the shape of the summon. */
.nt-summon__core {
  opacity: var(--summon-glyph);
  filter: drop-shadow(0 0 var(--summon-edge) rgba(34, 211, 238, var(--summon-edge-a)));
  transition:
    opacity 300ms var(--ease-out),
    filter 300ms var(--ease-out);
}

.nt-summon__line {
  fill: none;
  stroke: var(--summon-stroke);
  stroke-width: var(--summon-stroke-w);
  stroke-linecap: square;
  stroke-linejoin: miter;
  transition: stroke-width 150ms var(--ease-out);
}

.nt-summon__line--heavy {
  stroke-width: var(--summon-stroke-heavy);
}

/* PLATE and JOINTS — awakened only, hard-edged (§6.2). */
.nt-summon__plate {
  fill: url(#ntSummonPlate);
  opacity: var(--summon-plate);
  transition: opacity 300ms var(--ease-out);
}

.nt-summon__joint {
  fill: var(--emission-core);
  opacity: var(--summon-joint);
  transition: opacity 200ms var(--ease-out);
}

/* --- Dormant: not called (§5: dark, no bloom) ------------------------------ */
.nt-summon[data-presence='dormant'] {
  --summon-glyph: var(--dormant-glyph);
  --summon-bloom-a: 0;
  --summon-edge-a: 0;
}

/* --- Charging: work in progress toward waking ------------------------------ */
/* A move ALONG the axis, never past it: the bloom gathers, the stroke stays
 * flat. For the moment between asking and being answered — Blackwall shows it
 * while a grant is made. Waking is the answer, so it must not be borrowed. */
.nt-summon[data-presence='charging'] {
  --summon-bloom-a: 0.6;
  --summon-edge-a: 0.45;
}

/* --- Awakened: the one that matters (§5, §9 — one per view) ---------------- */
/* Flat → alive: the stroke takes the pooling gradient and thickens
 * 1.7 → 1.95, the joints light, the plate fills. */
.nt-summon[data-presence='awakened'] {
  --summon-glyph: var(--awakened-glyph);
  --summon-plate: 0.92;
  --summon-joint: 1;
  --summon-bloom: 4px;
  --summon-bloom-a: 0.85;
  --summon-edge: 4px;
  --summon-edge-a: 0.72;
  --summon-stroke: url(#ntSummonSeam);
  --summon-stroke-w: 1.95; /* glyphs.json meta.strokeWidth.awakened */
  --summon-stroke-heavy: 3.9; /* glyphs.json meta.strokeWidthHeavy.awakened */
}

/* --- Unstable: the error, once (§4.4) -------------------------------------- */
/* 900ms, STEPPED so it reads as a fault rather than a fade: irregular flicker,
 * seam breaks (dash gaps), and a chromatic fringe that widens 0.5 → 3px before
 * it settles back to whatever presence the summon holds. */
.nt-summon--unstable .nt-summon__core {
  animation: nt-summon-unstable-core 900ms steps(1, end) both;
}

.nt-summon--unstable .nt-summon__halo {
  animation: nt-summon-unstable-halo 900ms steps(1, end) both;
}

.nt-summon--unstable .nt-summon__line {
  animation: nt-summon-seam-break 900ms steps(1, end) both;
}

@keyframes nt-summon-unstable-core {
  0%   { opacity: 1;    filter: drop-shadow(-0.5px 0 0 rgba(255, 48, 55, 0.55)) drop-shadow(0.5px 0 0 rgba(34, 211, 238, 0.8)); }
  9%   { opacity: 0.25; }
  14%  { opacity: 0.95; filter: drop-shadow(-1.5px 0 0 rgba(255, 48, 55, 0.6)) drop-shadow(1.5px 0 0 rgba(34, 211, 238, 0.85)); }
  27%  { opacity: 0.4; }
  31%  { opacity: 1;    filter: drop-shadow(-2.5px 0 0 rgba(255, 48, 55, 0.6)) drop-shadow(2.5px 0 0 rgba(34, 211, 238, 0.85)); }
  48%  { opacity: 0.15; }
  55%  { opacity: 0.9;  filter: drop-shadow(-3px 0 0 rgba(255, 48, 55, 0.55)) drop-shadow(3px 0 0 rgba(34, 211, 238, 0.8)); }
  70%  { opacity: 0.55; }
  76%  { opacity: 1;    filter: drop-shadow(-1.5px 0 0 rgba(255, 48, 55, 0.4)) drop-shadow(1.5px 0 0 rgba(34, 211, 238, 0.7)); }
  100% { opacity: var(--summon-glyph); filter: drop-shadow(0 0 var(--summon-edge) rgba(34, 211, 238, var(--summon-edge-a))); }
}

@keyframes nt-summon-unstable-halo {
  0%   { opacity: 0.7; }
  9%   { opacity: 0.1; }
  14%  { opacity: 0.6; }
  31%  { opacity: 0.8; }
  48%  { opacity: 0.05; }
  55%  { opacity: 0.7; }
  76%  { opacity: 0.5; }
  100% { opacity: var(--summon-bloom-a); }
}

@keyframes nt-summon-seam-break {
  0%   { stroke-dasharray: none; }
  14%  { stroke-dasharray: 10 3; }
  31%  { stroke-dasharray: 4 2 14 3; }
  55%  { stroke-dasharray: 2 5 8 2; }
  76%  { stroke-dasharray: 18 2; }
  100% { stroke-dasharray: none; }
}

/* REDUCED MOTION — no flicker; the fault holds as one still seam break for
 * as long as the class is set, so it is still visible. */
@media (prefers-reduced-motion: reduce) {
  .nt-summon__halo,
  .nt-summon__core,
  .nt-summon__line,
  .nt-summon__plate,
  .nt-summon__joint {
    transition: none;
  }

  .nt-summon--unstable .nt-summon__core,
  .nt-summon--unstable .nt-summon__halo,
  .nt-summon--unstable .nt-summon__line {
    animation: none;
  }

  .nt-summon--unstable .nt-summon__line {
    stroke-dasharray: 10 3;
  }
}
