/* =============================================================================
   ΠΡΟΤΑΣΕΙΣ / SUGGESTIONS
   Stylesheet. One file, no imports, no external requests of any kind.

   Aesthetic system: ~/.claude/skills/concept-viz/references/aesthetic-system.md
   Variant: festival-dark (its own deviation-trigger table routes sound
   installations here). --radius stays 2px. Nothing is rounded.

   -----------------------------------------------------------------------------
   FONT EXCEPTION, MANDATORY (CONTRACT.md §8, invariant I2)
   -----------------------------------------------------------------------------
   The aesthetic system ships a Google Fonts <link> for Space Grotesk / Inter /
   JetBrains Mono. It is FORBIDDEN in this piece. Loading it would be a
   third-party request, which breaks I2, and it would break the page's central
   claim at the exact moment a visitor opens the network tab to test it. The
   whole work rests on that claim surviving inspection.

   So the type is a system stack. If self-hosted files are wanted later, drop
   the .woff2 files into _build/fonts/ and uncomment the block below. They are
   same-origin, so I2 holds. Nothing else in this file needs to change: the
   family names are already first in each stack.

   @font-face {
     font-family: "Space Grotesk";
     src: url("fonts/SpaceGrotesk-Medium.woff2") format("woff2");
     font-weight: 500; font-style: normal; font-display: swap;
   }
   @font-face {
     font-family: "Inter";
     src: url("fonts/Inter-Regular.woff2") format("woff2");
     font-weight: 400; font-style: normal; font-display: swap;
   }
   @font-face {
     font-family: "Inter";
     src: url("fonts/Inter-Medium.woff2") format("woff2");
     font-weight: 500; font-style: normal; font-display: swap;
   }
   @font-face {
     font-family: "JetBrains Mono";
     src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2");
     font-weight: 400; font-style: normal; font-display: swap;
   }

   Greek is needed (ΠΡΟΤΑΣΕΙΣ, κ, the Greek lexicon). Any self-hosted file must
   carry the Greek subset, or the system fallback silently takes over for those
   glyphs and the two faces sit next to each other.
   ============================================================================= */


/* ── 1. Tokens ────────────────────────────────────────────────────────────
   The canonical warm-light root is declared first so that the categorical
   palette exists, then festival-dark overrides what the variant overrides.
   This mirrors the aesthetic file exactly rather than flattening it. */

:root {
  --base:        #F3EEE8;
  --base-white:  #FFFFFF;
  --base-warm:   #FFE699;

  --ink:         #0A0907;
  --ink-soft:    rgba(10, 9, 7, .55);
  --ink-mute:    rgba(10, 9, 7, .35);
  --line:        rgba(10, 9, 7, .12);

  --accent:      #D73234;

  --cat-1:       #D73234;
  --cat-2:       #E8734A;
  --cat-3:       #FFCB05;
  --cat-4:       #5B8FA8;
  --cat-5:       #8BAF7E;
  --cat-6:       #A67FB5;

  --s-0:  0.25rem;
  --s-1:  0.5rem;
  --s-2:  1rem;
  --s-3:  1.5rem;
  --s-4:  2.5rem;
  --s-5:  4rem;
  --s-6:  6rem;
  --s-7:  8rem;

  --radius:       2px;
  --stroke-thin:  0.8px;
  --stroke:       1.5px;
  --stroke-bold:  3px;
}

:root[data-theme="festival-dark"] {
  --base:        #0A0A0C;
  --base-alt:    #111115;
  --base-card:   #16161C;
  --base-white:  #16161C;

  --ink:         rgba(255, 255, 255, .88);
  --ink-soft:    rgba(255, 255, 255, .42);
  --ink-mute:    rgba(255, 255, 255, .28);
  --line:        rgba(255, 255, 255, .08);

  --accent:      #E83A2D;
  --cat-3:       #FFB800;

  /* DOCUMENTED DEVIATION 1 of 2 (contrast).
     --ink-soft at .42 alpha computes to about 4.1:1 on --base, which fails
     WCAG AA for body-size text. The colophon, the glosses and the log
     measurements have to be readable, so secondary prose uses --ink-dim
     (about 7.7:1) and --ink-soft is kept for decoration only. Proposed as a
     guide addition rather than an inline one-off. */
  --ink-dim:     rgba(255, 255, 255, .62);

  /* DOCUMENTED DEVIATION 2 of 2 (fonts).
     Space Grotesk / Inter / JetBrains Mono are the variant's families. They
     are declared first in each stack so a later self-hosted drop-in needs no
     edit here, but with no network the system faces do the work. */
  --font-display: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto,
                  "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Inter", system-ui, -apple-system, "Segoe UI", Roboto,
                  "Helvetica Neue", Arial, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono",
                  Menlo, Consolas, "Liberation Mono", monospace;

  color-scheme: dark;
}


/* ── 2. Reset and base ───────────────────────────────────────────────────── */

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

html {
  background: var(--base);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--base);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: clamp(0.94rem, 1.05vw, 1.05rem);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  position: relative;
  isolation: isolate;      /* lets the graticule below sit under the content */
}

/* A 48px graticule, fixed to the viewport and faded out from the top right,
   where Fig. 1 sits. Lines only: the page's one background texture. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, .035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .035) 1px, transparent 1px);
  background-size: 48px 48px;
  -webkit-mask-image: radial-gradient(ellipse 70% 55% at 78% 8%, black, transparent 78%);
          mask-image: radial-gradient(ellipse 70% 55% at 78% 8%, black, transparent 78%);
}

p { margin: 0 0 var(--s-2); }
p:last-child { margin-bottom: 0; }

code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.05em 0.35em;
}

/* 820px until 2026-10-16. Widened so the figures can sit beside the text
   on a desktop screen; prose blocks keep their own ch-based measure. */
.wrap {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 clamp(18px, 4vw, 40px);
}

.section-label {
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 0 0 var(--s-2);
}

/* Roman numerals on the sections, from the aesthetic system. Generated, so
   the headings' text and ids stay as the DOM contract has them. The second
   `content` line hides the numeral from screen readers where the browser
   supports alt text; older browsers drop it and keep the first. */
main { counter-reset: section; }
.panel .section-label::before {
  counter-increment: section;
  content: counter(section, upper-roman) "\00a0\00b7\00a0";
  content: counter(section, upper-roman) "\00a0\00b7\00a0" / "";
  font-family: var(--font-mono);
  letter-spacing: 0.12em;
  color: var(--accent);
}
/* A hairline runs from the label to the edge of its column. */
.panel > .section-label {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}
.panel > .section-label::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--line);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}


/* ── 3. Focus, skip link ─────────────────────────────────────────────────
   Every interactive element gets a visible focus ring. --accent on --base is
   about 4.8:1, well over the 3:1 needed for a non-text indicator. */

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--base-card);
  color: var(--ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: var(--s-1) var(--s-2);
  font-size: 0.85rem;
}
.skip-link:focus {
  left: clamp(18px, 4vw, 40px);
  top: var(--s-1);
}


/* ── 4. Masthead ─────────────────────────────────────────────────────────
   2026-10-16, Phivos: "make a better visual presentation ... add graphics,
   lines". That request replaces the rule this section opened with until
   then: "Quiet on purpose. No hero, no full-viewport section, no gradient,
   no entrance animation. The page has to be credible before it starts
   refusing things, so it opens looking like the interface everyone already
   knows." Kept from it: no full-viewport section, no image, no colour wash.
   Added: Fig. 1 beside the title and a short entrance. Fig. 1 is drawn from
   the organism's own state, so the first thing on the page is still the
   work. */

.masthead {
  padding: clamp(var(--s-4), 7vw, var(--s-6)) 0 var(--s-4);
  border-bottom: 1px solid var(--line);
}

.masthead .wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
}

@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
.masthead-text > *,
.fig--trajectory {
  animation: rise 0.9s cubic-bezier(.2, .7, .2, 1) both;
}
.masthead-text > :nth-child(2) { animation-delay: 0.08s; }
.masthead-text > :nth-child(3) { animation-delay: 0.16s; }
.masthead-text > :nth-child(4) { animation-delay: 0.24s; }
.fig--trajectory              { animation-delay: 0.30s; }

/* The swoosh. Path and stroke from the aesthetic system; pathLength="1" in
   the markup lets one dash draw it from left to right. */
.swoosh {
  display: block;
  width: min(300px, 72%);
  height: 14px;
  margin: calc(-1 * var(--s-1)) 0 var(--s-3);
  overflow: visible;
}
.swoosh path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
  animation: draw 1.4s 0.45s cubic-bezier(.2, .7, .2, 1) both;
}
@keyframes draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 0 0 var(--s-2);
}

.engine-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2px 8px;
  letter-spacing: 0.16em;
  color: var(--ink-dim);
}
/* A square lamp. visual.js sets data-state from the badge's own text. */
.engine-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 1px;
  background: var(--ink-soft);
}
.engine-status[data-state="sounding"] {
  color: var(--ink);
  border-color: rgba(232, 58, 45, .45);
}
.engine-status[data-state="sounding"]::before {
  background: var(--accent);
  animation: lamp 1.6s ease-in-out infinite;
}
@keyframes lamp {
  0%, 100% { opacity: 1;   box-shadow: 0 0 0 0 rgba(232, 58, 45, .55); }
  50%      { opacity: .55; box-shadow: 0 0 0 4px rgba(232, 58, 45, 0); }
}

h1.display {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 6.4vw, 3.9rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin: 0 0 var(--s-2);
}
h1.display .slash { color: var(--accent); }

.standfirst {
  max-width: 60ch;
  color: var(--ink-dim);
  margin: 0;
}

/* The birth line. Written once, on the first proposition, and never again.
   Mono and accent-ruled, because it is the one timestamped fact on the page:
   this organism started here, at this moment, and nowhere else. */
.birth-line {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink);
  max-width: 60ch;
  margin: var(--s-2) 0 0;
  padding-left: var(--s-2);
  border-left: 2px solid var(--accent);
}
/* [hidden] must beat the block display above it before the first gesture. */
.birth-line[hidden] { display: none; }


/* ── 4b. The figures (js/visual.js, 2026-10-16) ──────────────────────────
   Two canvases drawn from the organism's state. Colour carries one meaning
   in them, as everywhere on the page: the verdict. Accent red marks κ and
   the present moment. Everything else is ink at some opacity. */

.fig {
  margin: 0;
  min-width: 0;
}

.fig-frame {
  position: relative;
  background: var(--base-alt);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* Registration marks at two corners: the figure is a plate, measured. */
.fig-frame::before,
.fig-frame::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  border: 0 solid var(--accent);
  pointer-events: none;
}
.fig-frame::before { top: -1px;    left: -1px;  border-top-width: 2px;    border-left-width: 2px; }
.fig-frame::after  { bottom: -1px; right: -1px; border-bottom-width: 2px; border-right-width: 2px; }

.fig-canvas {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
}
.fig-frame--square .fig-canvas { aspect-ratio: 1 / 1; }

.fig-strip {
  display: block;
  width: 100%;
  height: 28px;
  margin-top: var(--s-1);
}

.fig-readout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-0) var(--s-3);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--ink-dim);
  margin: var(--s-0) 0 0;
}
.fig-read [data-read] { color: var(--ink); }

.fig-key {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-0) var(--s-2);
  margin: var(--s-1) 0 0;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  color: var(--ink-dim);
}
.fig-key li {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.fig-key li::before {
  content: "";
  width: 12px;
  height: 2px;
  background: var(--v, var(--ink-dim));
}
.fig-key .is-pending { --v: var(--cat-3); }

.fig-caption {
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-dim);
  max-width: 62ch;
  margin-top: var(--s-2);
}
.fig-num {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  margin-right: 0.3em;
}


/* ── 5. Panels ───────────────────────────────────────────────────────────── */

.panel {
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--line);
}
.panel:last-of-type { border-bottom: none; }

/* Reveal on scroll, from the aesthetic system. Only when visual.js has
   added .has-motion, so a page without script, or with reduced motion,
   shows every panel at once. */
.has-motion main .panel,
.has-motion .page-footer {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity 0.9s cubic-bezier(.2, .7, .2, 1),
              transform 0.9s cubic-bezier(.2, .7, .2, 1);
}
.has-motion main .panel.is-in,
.has-motion .page-footer.is-in {
  opacity: 1;
  transform: none;
}
.has-motion .panel--reply { transition-delay: 0.12s; }

/* A quiet line under a panel's controls, in the piece's own voice.
   Same weight as .kappa-note so the page has one register for asides. */
.panel-note {
  font-size: 0.82rem;
  color: var(--ink-dim);
  max-width: 62ch;
  margin: var(--s-2) 0 0;
}


/* ── 6. The prompt field ─────────────────────────────────────────────────
   The familiar object: a line, a caret, a button. */

.field-label {
  display: block;
  font-size: 0.85rem;
  color: var(--ink-dim);
  margin-bottom: var(--s-1);
}

.field-row {
  display: flex;
  align-items: stretch;
  gap: var(--s-1);
  background: var(--base-card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-1);
}
/* The row warms up whenever anything inside it holds focus. The 2px ring
   itself stays on the individual control (see #prompt-input:focus-visible
   below), so focusing the button never draws two rings at once. */
.field-row:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(232, 58, 45, .25), 0 0 28px rgba(232, 58, 45, .12);
}
.field-row {
  position: relative;
  transition: border-color 0.2s, box-shadow 0.3s;
}

/* The 1.8 s the reply measures, drawn under the field while it runs.
   main.js sets aria-busy on the form for exactly that window. */
form[aria-busy="true"] .field-row::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--cat-3);
  transform-origin: left center;
  animation: measure 1.8s linear both;
}
@keyframes measure {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.field-caret {
  font-family: var(--font-mono);
  color: var(--accent);
  padding: 0 var(--s-0) 0 var(--s-1);
  display: flex;
  align-items: center;
  user-select: none;
}
/* The prompt sign blinks while nobody is typing, and holds still once
   someone is. */
.field-row:not(:focus-within) .field-caret {
  animation: blink 1.2s steps(1, end) infinite;
}
@keyframes blink {
  50% { opacity: 0; }
}

#prompt-input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 1rem;
  line-height: 1.5;
  padding: var(--s-1) 0;
  caret-color: var(--accent);
}
/* Pointer focus stays clean (the row border is the cue). Keyboard focus gets a
   real 2px ring, drawn inside the row so it cannot be clipped. Order matters:
   the :focus-visible rule follows :focus at equal specificity and wins. */
#prompt-input:focus { outline: none; }
#prompt-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}
/* There is no placeholder on #prompt-input any more, deliberately: in the same
   mono face as real input it read as text already typed. The rule stays, dimmer
   and italic, so that if anyone adds one back it cannot be mistaken for input. */
#prompt-input::placeholder {
  color: var(--ink-faint, var(--ink-dim));
  opacity: 0.55;
  font-style: italic;
}

/* The examples, in the label, in the body face so they read as prose. */
.field-examples {
  display: block;
  margin-top: 2px;
  color: var(--ink-dim);
  font-size: 0.82rem;
}
.field-examples em {
  font-style: normal;
  font-family: var(--font-mono);
  color: var(--ink);
}

.btn {
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-1) var(--s-2);
  cursor: pointer;
  white-space: nowrap;
}
.btn:hover { border-color: var(--ink-dim); }
.btn--primary { border-color: var(--accent); color: var(--accent); }
.btn--primary:hover { background: rgba(232, 58, 45, .1); border-color: var(--accent); }
.btn[disabled], #prompt-input[disabled] {
  opacity: .5;
  cursor: not-allowed;
}

/* The sound control in the masthead. Deliberately quiet: it is a courtesy to
   the listener's ears, not a control over the organism, and it should not
   read as one of the instrument's own affordances. */
.btn--ghost {
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  padding: 2px 8px;
  color: var(--ink-dim);
}
.btn--ghost:hover { color: var(--ink); border-color: var(--ink-dim); }
.btn--ghost[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }
.audio-toggle { margin-left: var(--s-1); vertical-align: baseline; }
.audio-toggle[hidden] { display: none; }

.status {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ink-dim);
  margin: var(--s-1) 0 0;
  min-height: 1.4em;      /* reserved, so a status change never shifts layout */
}

form[aria-busy="true"] .status { color: var(--cat-3); }


/* ── 7. Verdict colour map ───────────────────────────────────────────────
   data-verdict on any ancestor sets --v, which the badge and the log entry
   border read. Set it once on the container and it inherits. */

[data-verdict]            { --v: var(--ink-dim); }
[data-verdict="ENACTED"]  { --v: var(--cat-5); }
[data-verdict="PARTIAL"]  { --v: var(--cat-3); }
[data-verdict="ABSORBED"] { --v: var(--ink-dim); }
[data-verdict="REFUSED"]  { --v: var(--accent); }
[data-verdict="OVERSHOT"] { --v: var(--cat-2); }
[data-verdict="DISPLACED"]{ --v: var(--cat-4); }

.verdict {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--v, var(--ink-dim));
  border: 1px solid var(--v, var(--line));
  border-radius: var(--radius);
  padding: 2px 8px;
}


/* ── 8. The reply ────────────────────────────────────────────────────────── */

.reply {
  position: relative;
  overflow: hidden;
  background: var(--base-card);
  border: 1px solid var(--line);
  border-left: 2px solid var(--v, var(--line));
  border-radius: var(--radius);
  padding: var(--s-2) var(--s-3);
}

/* Each new verdict draws a line across the top of the reply in its own
   colour. visual.js restarts it by toggling .is-new. */
.reply::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 2px;
  background: var(--v, var(--accent));
  transform-origin: left center;
  transform: scaleX(0);
  opacity: 0;
}
.reply.is-new::after { animation: sweep 1.4s cubic-bezier(.2, .7, .2, 1) both; }
.reply.is-new .reply-line { animation: rise 0.6s cubic-bezier(.2, .7, .2, 1) both; }
@keyframes sweep {
  0%   { transform: scaleX(0); opacity: 1; }
  65%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

.reply-head { margin: 0 0 var(--s-1); }

.reply-line {
  font-size: clamp(1.05rem, 2.2vw, 1.3rem);
  line-height: 1.45;
  margin: 0;
  overflow-wrap: anywhere;
}

.reply-detail {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
  color: var(--ink-dim);
  margin: var(--s-1) 0 0;
  overflow-wrap: anywhere;
}
.reply-detail:empty { display: none; }


/* ── 9. Parameter readout ────────────────────────────────────────────────
   Six rows, mono, tabular figures, fixed-width value column. Numbers change
   every frame: nothing here may reflow when a digit changes. */

.params {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-1) var(--s-3);
  margin-bottom: var(--s-3);
}

.param {
  display: grid;
  grid-template-columns: minmax(5.5rem, 9.5rem) 1fr auto;
  align-items: center;
  gap: var(--s-1);
  padding: 3px 0;
}

.param-name {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  line-height: 1.25;
}

.param-gloss {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0;
  color: var(--ink-dim);
}

/* Since 2026-10-16 a hairline with a point at its end, to match the figures.
   overflow is visible so the point and the ghost tick can stand off it. */
.param-track {
  position: relative;
  display: block;
  height: 2px;
  min-width: 40px;
  background: rgba(255, 255, 255, .08);
  border-radius: var(--radius);
  overflow: visible;
}

.param-bar {
  position: relative;
  display: block;
  height: 100%;
  width: 0%;
  background: var(--ink-soft);
  border-radius: var(--radius);
  transition: width 90ms linear;
}
.param-bar::after {
  content: "";
  position: absolute;
  right: -3px;
  top: 50%;
  width: 6px;
  height: 6px;
  margin-top: -3px;
  background: var(--ink);
  border-radius: 1px;
}

/* Appended by visual.js: where the unheard history has this parameter,
   during the 1.8 s window. Colour and position are set from script. */
.param-ghost {
  position: absolute;
  left: 0;
  top: -5px;
  width: 2px;
  height: 12px;
  margin-left: -1px;
  background: var(--cat-3);
  opacity: 0;
  pointer-events: none;
}

.param-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 0.82rem;
  color: var(--ink);
  text-align: right;
  min-width: 5ch;          /* "0.000" never resizes its column */
  font-stretch: normal;
}


/* ── 10. Kappa meter ─────────────────────────────────────────────────────
   The one place accent red is used structurally: coupling is the argument. */

.kappa {
  background: var(--base-card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-2);
}

.kappa-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
  margin-bottom: var(--s-1);
}

.kappa-label {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  /* No text-transform: uppercase here. It would fold the lowercase κ into a
     capital Κ, and κ is the coupling coefficient's name in the contract. The
     word beside it is written uppercase in the markup instead. */
  text-transform: none;
  color: var(--ink-dim);
}

.kappa-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 1rem;
  color: var(--ink);
  min-width: 5ch;
  text-align: right;
}

.kappa-track {
  position: relative;
  height: 6px;
  background-color: rgba(255, 255, 255, .07);
  /* a scale: one tick every tenth */
  background-image: repeating-linear-gradient(90deg,
    rgba(255, 255, 255, .22) 0 1px, transparent 1px 10%);
  border-radius: var(--radius);
  overflow: visible;
}

.kappa-bar {
  display: block;
  height: 100%;
  width: 12%;                 /* matches the contract's starting κ = 0.12 */
  background: var(--accent);
  border-radius: var(--radius);
  box-shadow: 0 0 12px rgba(232, 58, 45, .45);
  transition: width 140ms linear;
}

.kappa-note {
  font-size: 0.82rem;
  color: var(--ink-dim);
  margin: var(--s-1) 0 0;
  max-width: 62ch;
}

.kappa-state {
  font-family: var(--font-mono);
  color: var(--ink);
}


/* ── 11. The log ─────────────────────────────────────────────────────────── */

.log-bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
  flex-wrap: wrap;
}

.log-count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  color: var(--ink-dim);
  text-transform: none;
  margin-left: var(--s-1);
}

.log-actions {
  display: flex;
  gap: var(--s-1);
  margin-bottom: var(--s-2);
}

.log-empty {
  color: var(--ink-dim);
  font-size: 0.88rem;
  max-width: 62ch;
}

/* Entries that survived an earlier Ephemeron. Dimmer than the empty state
   and set in mono, so it reads as a note about the list rather than as part
   of it. Disappears the moment this organism writes its own first line. */
.log-carried {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-dim);
  max-width: 62ch;
  margin: var(--s-1) 0 0;
}
.log-carried[hidden] { display: none; }

/* The list is capped, the log is not, and this says so. Same voice as
   .log-carried: a note about the list, not an entry in it. */
.log-truncated {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-dim);
  max-width: 62ch;
  margin: var(--s-2) 0 0;
  padding-top: var(--s-2);
  border-top: 1px solid var(--line);
}
.log-truncated[hidden] { display: none; }

.log-list {
  list-style: none;
  margin: var(--s-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

.log-entry {
  background: var(--base-alt);
  border: 1px solid var(--line);
  border-left: 2px solid var(--v, var(--line));
  border-radius: var(--radius);
  padding: var(--s-2);
}
/* The newest entry slides in from the side its colour rule is on. */
.has-motion .log-entry:first-child {
  animation: entry-in 0.7s cubic-bezier(.2, .7, .2, 1) both;
}
@keyframes entry-in {
  from { opacity: 0; transform: translateX(-12px); }
  to   { opacity: 1; transform: none; }
}
/* The log reads as a column of text; the wider page does not widen it. */
.log-list,
.log-empty,
.log-truncated { max-width: 880px; }

.log-head {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  flex-wrap: wrap;
  margin: 0 0 var(--s-1);
}

.log-time,
.log-kappa {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
}
.log-time { min-width: 6ch; }
.log-kappa { margin-left: auto; }

.log-text {
  font-family: var(--font-mono);
  font-size: 0.88rem;
  color: var(--ink);
  margin: 0;
  overflow-wrap: anywhere;   /* pasted injection payloads can be long */
}
.log-text::before {
  content: "> ";
  color: var(--accent);
}

.log-measure {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 0.74rem;
  color: var(--ink-dim);
  margin: var(--s-1) 0 0;
  overflow-wrap: anywhere;
}
.log-measure span:empty { display: none; }


/* ── 12. Colophon and footer ─────────────────────────────────────────────── */

.panel--colophon p {
  max-width: 68ch;
  color: var(--ink-dim);
  font-size: 0.92rem;
}

.page-footer {
  padding: var(--s-3) 0 var(--s-5);
  border-top: 1px solid var(--line);
  color: var(--ink-dim);   /* --ink-soft would fail AA at this size */
  font-size: 0.8rem;
}
.page-footer p { max-width: 62ch; }

.noscript-note {
  padding-top: var(--s-3);
  padding-bottom: var(--s-3);
  color: var(--cat-3);
}


/* ── 13. Responsive ──────────────────────────────────────────────────────
   Mobile-first. Tested against a 360px viewport: the param row keeps its
   three columns because the value column is what has to stay put. */

@media (max-width: 460px) {
  .param { grid-template-columns: minmax(4.5rem, 7rem) 1fr auto; }
  .param-gloss { display: none; }     /* the seven names carry themselves */
  /* Caret and field stay on the first line, the button takes its own full
     width below. Familiar mobile shape, no cramped 60px button. */
  .field-row { flex-wrap: wrap; }
  .field-caret { order: 1; }
  #prompt-input { flex: 1 1 8rem; order: 2; }
  .btn--primary { flex: 1 1 100%; order: 3; }
  .log-kappa { margin-left: 0; }
}

@media (min-width: 680px) {
  .params { grid-template-columns: 1fr 1fr; }
}

/* Panel III: Fig. 2 above the rows on a narrow screen, beside them on a
   wide one. */
.readout-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
}
.fig--dial {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
}

/* Desktop: the title beside Fig. 1, the instruction beside its reply,
   Fig. 2 beside the eleven rows. Everything else spans both columns. */
@media (min-width: 960px) {
  .masthead .wrap {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    align-items: center;
    column-gap: var(--s-5);
  }
  .fig--trajectory .fig-canvas { aspect-ratio: 5 / 4; }

  main.wrap {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--s-5);
  }
  main.wrap > * { grid-column: 1 / -1; }
  main.wrap > .panel--prompt { grid-column: 1; }
  main.wrap > .panel--reply  { grid-column: 2; }

  .readout-grid {
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    align-items: start;
    column-gap: var(--s-5);
  }
  .readout-grid .params { grid-template-columns: 1fr; }
  .fig--dial { max-width: none; margin: 0; }
}


/* ── 14. Reduced motion ──────────────────────────────────────────────────
   Under a reduced motion preference every animation and transition ends at
   once, in its final state: the bars jump to their value, the swoosh is
   drawn, nothing blinks or rises. visual.js reads the same preference,
   stops Fig. 1 turning, redraws both figures once a second, and does not
   add .has-motion, so no panel waits to be revealed. The numbers still
   update, so nothing is lost. */

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


/* ── 15. Print ───────────────────────────────────────────────────────────
   A visitor may want the log on paper. Ink on white, no meters. */

@media print {
  html, body { background: #fff; color: #000; }
  body::before, .fig, .swoosh { display: none; }
  .has-motion main .panel, .has-motion .page-footer { opacity: 1; transform: none; }
  .skip-link, .log-actions, .param-track, .kappa-track { display: none; }
  .panel, .log-entry, .reply, .kappa { border-color: #999; background: #fff; }
  .log-text, .log-measure, .param-value, .reply-line, .panel--colophon p { color: #000; }
}
