/*
 * Objexiv — the field's own styles (canon 91 §2b, file 83 "lights, not cells").
 *
 * Loaded after tokens.css. The lights-not-cells vocabulary (file 83 §1): crisp
 * geometry at rest, grouping by attachment and shared glow not by fence,
 * emission only for what is live now, and no hand/finger/cursor/body ever drawn.
 * The class vocabulary extends the design-system's semantic names
 * (design-system/project/components/bundle.css) so the two do not drift.
 *
 * K-91e ships the feed + challenge read surfaces and dresses them so they read
 * as a living field (not an unstyled page): layered rim emission on live
 * challenges, a soft bloom behind an opened ring, pulsing points for takes that
 * are playing now, the attachment grammar (member lines + a shared glow) between
 * a challenge's points, and a landscape-aware layout so a horizontally-held
 * phone is not cramped. Split/full overlays and the drawn-ink strokes are
 * K-91g/K-91f/K-91k.
 *
 * K-94c dresses the SIGNATURE interaction to the CORRECTED canon-94 §2 metaphor:
 * THE ELECTRIC COMPANY "Silhouettes" — a connection is two people, from both
 * sides, EMITTING their words, which travel inward and MEET IN THE MIDDLE to form
 * the tie. The old wire/current-in-a-wire reading is RETIRED. The island
 * (web/island/electric.ts) paints the live emission over the structure the
 * render layer emits: a stylised voice cue at each speaking end (crisp geometry,
 * never a drawn figure — file 83 §1.4), the words travelling inward, and the lit
 * meeting mark that settles where they converge.
 *
 * TWO REGISTERS (canon 94 §2a): the signature carries two registers by WHO is
 * speaking. HUMAN — when the two sides are PEOPLE (a person answering a
 * challenge, a take speaking to another take): warm and characterful, the
 * person's PORTRAIT in their hand is the emitter. MECHANICAL — object-to-object
 * with no person speaking (a take settling into structure, a projection
 * recomputing, a system tie, activity propagating): cooler, geometric, crisp
 * lights + a quiet pulse, NO portrait, NO mouth. The register SHIFTS WITH THE
 * PARTICIPANTS; same charged ground + six hands throughout. The classes the
 * island adds — .connection-live, .register-human/.register-mechanical,
 * .voice-cue, .portrait-cue/.portrait-node/.portrait-halo, .emission,
 * .meeting/.meeting-human/.meeting-mechanical, .ring-live — get their resting
 * (and reduced-motion) styling here so the signal reads even before the island's
 * Web-Animations motion runs and after it settles.
 */

html, body { margin: 0; background: var(--ground); color: var(--ink); min-height: 100%; }
body {
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* The one charged ground reads as depth, never a card (canon 94 §1): the radial
     ground-wash (lifted at centre, deep at the edges) with a breath of the live
     charge warming the top. */
  background:
    radial-gradient(130% 90% at 50% -10%, color-mix(in oklab, var(--live) 7%, transparent), transparent 55%),
    var(--ground-wash);
  background-attachment: fixed;
}

/* The field. Everything is drawn on the one ground; nothing sits in a card. */
#field {
  display: block;
  min-height: 100vh;
  padding: clamp(var(--step), 3vw, calc(var(--step) * 4));
  box-sizing: border-box;
}

/* ================================================================== */
/* The feed (file 75 §1 Feed): viewer-independent challenges, each a   */
/* crisp ring with the opener's words. Never a boxed card.            */

.feed {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(200px, 24vw, 260px), 1fr));
  gap: clamp(calc(var(--step) * 2), 4vw, calc(var(--step) * 5));
  align-items: start;
  justify-items: center;
  margin: 0 auto;
  padding: calc(var(--step) * 2) 0;
  max-width: 1600px;
  list-style: none;
}

.challenge {
  display: grid;
  grid-template-rows: auto auto;
  gap: calc(var(--step) * 1.5);
  justify-items: center;
  text-align: center;
  color: var(--ink);
  text-decoration: none;
  background: none;
  border: none;
  font: inherit;
  padding: var(--step);
  border-radius: calc(var(--step) * 2);
  /* The lift uses the field's --ease-lift (Open Props --ease-out-3): presence
     decelerates in softly rather than the browser `ease-out`'s flatter stop. */
  transition: transform var(--lift) var(--ease-lift);
}
/* The whole challenge lifts toward the viewer on hover/focus — presence, not a
   button press. The ring itself does the speaking. */
.challenge:hover,
.challenge:focus-visible { transform: translateY(-3px); outline: none; }
.challenge:focus-visible .circle { stroke: var(--live); }

.challenge svg { display: block; width: clamp(140px, 16vw, 190px); height: clamp(140px, 16vw, 190px); overflow: visible; }

/* A challenge is a crisp circle in ink (file 83 §1.1), now drawn with WEIGHT
   and LAYERING, not a lone hairline (K-94d / canon 94 §7). The ring is a crisp
   CORE stroke (.circle) riding a wider, far fainter concentric AURA
   (.circle-aura, same geometry) — the charge the ring sits in, a breath of
   presence rather than a second drawn band. It stays one circle to the eye;
   the aura only gives it depth so it is not a single 2px rule. */
/* The ring's crisp core. At rest it is the quieter near-paper ink (never the
   loud charge — emission is live-only, file 83 §1.3), so a quiet challenge reads
   as a crisp charged ring with depth, not a hard bright flat loop. A live-rim
   lifts it to the charge below. */
.circle { fill: none; stroke: var(--ink-quiet); stroke-width: var(--ring-core); stroke-linecap: round; transition: stroke var(--lift) var(--ease-lift); }
/* The ring's aura — the wider, fainter concentric body the core rides. Softly
   blurred so it bleeds into the ground as light, giving even a quiet ring depth
   (K-94d), never a second drawn band. */
.circle-aura { fill: none; stroke: var(--ink-quiet); stroke-width: var(--ring-aura); opacity: var(--ring-aura-alpha); filter: blur(1.5px); }
.circle-quiet { fill: none; stroke: var(--ink-quiet); stroke-width: var(--ring-core); opacity: 0.5; }

/* A take is a point of light threaded back to the rim (file 83 §1). The point
   itself is LAYERED (K-94d): a hot white-ish core (.point) over a hand-coloured
   body (.point-body) in a soft halo (.halo) — three graduated weights so a
   person reads as a light with depth, never a flat pip. The thread is a weighted
   FILAMENT: a bright thin core (.thread-straight) over a wider faint underlay
   (.thread-under, same endpoints), so the attachment carries more than a single
   hairline (canon 83 "attachment-and-glow", canon 94 §7). */
.point { stroke: none; }
.point-body { stroke: none; opacity: 0.85; }
.thread-straight { stroke-width: var(--thread-core); opacity: 0.55; stroke-linecap: round; }
.thread-under { stroke-width: var(--thread-under); opacity: var(--thread-under-alpha); stroke-linecap: round; }

/* The attachment grammar (file 83 §1.2): weighted filaments between the members
   of a set and a shared glow in that set's hand, NOT a fence drawn around them.
   The member-filament, like the thread, is a bright core (.member-line) over a
   wider faint underlay (.member-under); the glow is a radial gradient (defined
   per-SVG) that fades to nothing — a light, never a filled disc with a hard
   edge. */
.member-line { stroke-width: var(--tie-core); opacity: 0.5; stroke-linecap: round; }
.member-under { stroke-width: var(--tie-under); opacity: var(--tie-under-alpha); stroke-linecap: round; }
.shared-glow { stroke: none; }

/* The pings of a take new or playing now: arcs opening inward (file 83 §1, C+F). */
.ping { fill: none; stroke-linecap: round; opacity: 0.85; }

/* ---- the signature: two-sided emission meeting in the middle (canon 94 §2 ---- */
/* corrected). The island adds these over the structure above; their resting look */
/* lives here so the signal reads even without the island's Web-Animations motion. */

/* A live connection's line lifts to the charge colour; its WEIGHT is untouched
   (the tie's only state, file 77). The island sets stroke/filter inline; this
   keeps a quiet charged resting line as the floor. */
.connection-live { stroke: var(--live); }

/* A stylised voice cue — an opening chevron that reads as "a voice speaking
   toward the middle", never a drawn mouth or figure (file 83 §1.4). */
.voice-cue { pointer-events: none; }

/* A word travelling inward from a voice to the meeting point. */
.emission { pointer-events: none; }

/* The lit mark that settles where the two emissions MEET — the formed tie. At
   rest and under reduced motion this is the whole signal: lit, still, in the
   charge. */
.meeting { pointer-events: none; }

/* A live challenge ring is the shared place the meeting happens. */
.ring-live { stroke: var(--live); }

/* ---- the two registers (canon 94 §2a) -------------------------------------- */
/* The register shifts with the PARTICIPANTS. HUMAN: a person is speaking — warm,
   their portrait is the emitter, in their hand. MECHANICAL: object-to-object with
   no person speaking — cooler, geometric, crisp lights + a quiet pulse, no
   portrait and no mouth. Same charged ground + six hands throughout. */

/* HUMAN register — the warm portrait emitter (a person, in their hand). It is a
   stylised portrait token, NOT a drawn face/figure/cursor (file 83 §1.4). */
.portrait-cue { pointer-events: none; }
.portrait-node { /* filled node in the person's hand; the island sets fill */ }
.portrait-halo { /* a warm halo ring behind the portrait; island sets stroke */ }

/* MECHANICAL register — the structural line reads cool (the island sets the cool
   charge inline); it is mechanism, so it stays crisp and quiet. The cool charge
   falls back to a cool slate when the token layer has not defined --live-cool. */
.register-mechanical { stroke: var(--live-cool, oklch(0.80 0.09 215)); }
.register-human { stroke: var(--live); }

/* The meeting mark in each register: warm (human) vs a quiet cool pulse
   (mechanical). The island sets fill/animation; these are the resting floors. */
.meeting-human { }
.meeting-mechanical { }

/* Emission is for what is live now (file 83 §1.3): a live challenge's rim glows
   in the one loud colour and breathes; at rest the field is quiet and crisp. A
   layered drop-shadow is a light brightening the ground, not a dark halo. */
.live-rim .circle {
  stroke: var(--live);
  filter: drop-shadow(0 0 3px var(--live)) drop-shadow(0 0 10px color-mix(in oklab, var(--live) 70%, transparent));
}
.live-rim .circle { animation: breathe 4.2s ease-in-out infinite; }
/* The ring's aura lifts to the charge on a live challenge too, so the layered
   ring glows as one body of light rather than a lone bright hairline over a dead
   band (K-94d / canon 94 §7). It breathes in antiphase with the core, a touch
   slower, so the ring reads as charged depth, not a flat pulse. */
.live-rim .circle-aura {
  stroke: var(--live);
  opacity: calc(var(--ring-aura-alpha) * 2);
  animation: aura-breathe 4.2s ease-in-out infinite;
}

/* A point that is playing now pulses; the pings open outward from it. */
.point.playing { animation: pulse 2.2s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
.ping.playing { animation: open 2.2s ease-out infinite; transform-box: fill-box; transform-origin: center; }

/* A person is a crisp point of light with a thin halo ring, never a soft blob.
   The halo is now the OUTER of three graduated weights (K-94d): the soft wide
   ring that the hand body and hot core sit inside, giving the light depth
   instead of a flat pip + one hairline. */
.halo { fill: none; stroke-width: 1px; opacity: 0.45; }

/* The challenge's opener words — the only words a ring carries. */
.challenge-words {
  font-size: clamp(14px, 1.1vw, var(--challenge-words-size));
  font-weight: 600;
  line-height: 1.35;
  max-width: 22ch;
  letter-spacing: 0.01em;
  text-wrap: balance;
}

/* ================================================================== */
/* The challenge read view (file 75 §1 Expanded, read side): one       */
/* challenge projected over the field — its ring, words and takes.     */

.challenge-view {
  max-width: 760px;
  margin: 0 auto;
  padding-bottom: calc(var(--step) * 8);
}

.challenge-view header {
  display: grid;
  gap: calc(var(--step) * 1.5);
  justify-items: center;
  text-align: center;
  margin-bottom: calc(var(--step) * 5);
  padding-top: calc(var(--step) * 2);
}
.challenge-view header svg { width: clamp(160px, 22vw, 240px); height: clamp(160px, 22vw, 240px); overflow: visible; }
/* The opened ring blooms softly behind itself: the challenge is PRESENT. */
.challenge-view header.live-rim svg { filter: drop-shadow(0 0 22px color-mix(in oklab, var(--live) 28%, transparent)); }
/* The challenge's artwork: its own picture, not a fenced card (canon 94). It
   sits in the header column under the ring, sized to breathe and never boxed —
   no border, no plate, no background. The image carries its own intrinsic
   aspect; a wide context master (maritime) stays wide, a square one stays
   square. */
.challenge-view .artwork { display: block; line-height: 0; }
.challenge-view .artwork img {
  display: block;
  width: clamp(128px, 20vw, 192px);
  height: auto;
  max-width: 100%;
}
.challenge-view h1 {
  font-size: clamp(22px, 3.4vw, var(--take-short));
  font-weight: 650;
  line-height: 1.15;
  margin: 0;
  max-width: 24ch;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.challenge-view .description {
  font-size: clamp(15px, 1.6vw, var(--take-long));
  color: var(--ink-quiet);
  margin: 0;
  max-width: 46ch;
  line-height: 1.55;
  text-wrap: pretty;
}

/* The takes: points of light, newest first. The point sits in a column to the
   left; the words are prose. A thin ink seam runs down the column so the takes
   read as threaded off one spine, not stacked in boxes. */
.takes {
  display: grid;
  gap: calc(var(--step) * 4);
  margin: 0;
  padding: 0;
  list-style: none;
  position: relative;
}
/* The spine the takes are threaded off is a LAYERED seam, not a lone 1px rule
   (K-94d / canon 94 §7): a crisp core line (::before) riding a wider, softer
   aura (::after) that blurs it into the ground — a charged filament with depth,
   the same core-over-underlay vocabulary the ties use, so the column reads as a
   thread of light rather than a hairline divider. Both fade to nothing at the
   foot (the thread runs out where the field goes quiet). */
.takes::before {
  content: "";
  position: absolute;
  left: calc(var(--touch) / 2);
  top: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(var(--ink-quiet), transparent);
  opacity: var(--spine-core-alpha);
}
.takes::after {
  content: "";
  position: absolute;
  left: calc(var(--touch) / 2 - 1.5px);
  top: 0;
  bottom: 0;
  width: 4px;
  background: linear-gradient(var(--ink-quiet), transparent);
  opacity: var(--spine-aura-alpha);
  filter: blur(1.5px);
  pointer-events: none;
}

.take {
  display: grid;
  grid-template-columns: var(--touch) 1fr;
  gap: calc(var(--step) * 2.5);
  align-items: start;
  position: relative;
}
.take svg { width: var(--touch); height: var(--touch); overflow: visible; position: relative; z-index: 1; }
/* A person's actual words are the one thing set in the reading serif (canon 94
   §4): the take body resolves to the bundled Source Serif 4, never the sans
   chrome. Everything else on the field stays in --font-sans. */
.take .body { font-family: var(--font-serif); font-size: clamp(16px, 1.7vw, var(--take-long)); line-height: 1.55; margin: 0; text-wrap: pretty; }
.take.short .body { font-size: clamp(21px, 2.8vw, var(--take-short)); line-height: 1.25; font-weight: 500; }

.keywords { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 1.5); margin-top: calc(var(--step) * 1.5); padding: 0; list-style: none; }
.keyword {
  font-size: var(--keyword-size);
  font-weight: 600;
  color: var(--ink-quiet);
  letter-spacing: 0.01em;
}

/* Words as first-class objects (product-canon file 86). In the EXPANDED state a
   take's key words ARE the surface — the thing you scan — so a word reads as a
   lit object, not a label: it brightens toward the live charge and gains a soft
   glow on hover/focus (attachment-and-glow, canon 83 "lights, not cells" — no
   fence, no chip border, no box). A word that knows its hand carries that hand's
   ink, so a word is visibly WHOSE. The glow is the only affordance; no cursor or
   hand is ever drawn (file 83 §4). */
.keyword.word {
  color: var(--ink);
  cursor: default; /* the word is draggable to the grammar, not a text caret target */
  border-radius: 2px;
  padding: 1px 2px;
  transition: color 120ms var(--ease-glow), text-shadow 160ms var(--ease-glow);
}
.keyword.word[data-hand] { color: var(--ink); }
.keyword.word:hover,
.keyword.word:focus-visible {
  color: var(--live-hot);
  text-shadow: 0 0 6px var(--live), 0 0 14px var(--live);
  outline: none;
}

/* The expand-in-place disclosure (file 75 / file 86): the word-field is the
   summary; the full take is the detail. Server-rendered, no client code — the
   island only paints liveness over it. The summary carries no default marker
   triangle (lights, not chrome); it is a row of word-objects that reveals the
   take's words when opened. */
.take-expand { margin: 0; }
.take-expand > summary {
  list-style: none;
  cursor: default;
}
.take-expand > summary::-webkit-details-marker { display: none; }
.take-expand > summary::marker { content: ""; }
.take-expand > summary > .words { margin-top: 0; }
/* When opened, the full words rise under the key words with a faint hairline of
   attachment — a line, not a box (canon 83). */
.take-expand[open] > .body {
  margin-top: calc(var(--step) * 1.5);
  padding-top: calc(var(--step) * 1.5);
  border-top: 1px solid var(--hair);
}
@media (prefers-reduced-motion: no-preference) {
  .take-expand[open] > .body { animation: word-expand 180ms ease both; }
}
@keyframes word-expand {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: none; }
}
.when {
  /* A when is chrome, not a person's words (canon 94 §4): it is set in the one
     humanist sans like every other label, never in a monospace readout. */
  display: inline-block;
  margin-top: var(--step);
  font-family: var(--font-sans);
  font-size: var(--when-size);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink-quiet);
  opacity: 0.8;
}

/* The plain truth when nothing is here yet — never a stand-in (file 75 §4). */
.quiet { color: var(--ink-quiet); font-size: var(--take-long); text-align: center; padding: calc(var(--step) * 6) 0; }

/* On the one charged ground a glow blends additively so it only brightens the
   ground, never reads as a shaded disc (file 83 §1; canon 94 §1 — there is one
   ground, so this is unconditional now). The gradient's own falloff carries the
   shape; screen blend keeps it purely additive. */
.shared-glow { mix-blend-mode: screen; }
.live-rim .circle,
.point.playing { will-change: filter; }

/* ================================================================== */
/* Motion (file 77 §3 / file 83 §1.3): emission breathes, a playing    */
/* point pulses, pings open. All disabled under reduced motion.        */

@keyframes breathe {
  0%, 100% { filter: drop-shadow(0 0 3px var(--live)) drop-shadow(0 0 8px color-mix(in oklab, var(--live) 55%, transparent)); }
  50%      { filter: drop-shadow(0 0 5px var(--live)) drop-shadow(0 0 16px color-mix(in oklab, var(--live) 80%, transparent)); }
}
/* The live ring's aura breathes its opacity gently (K-94d) — the charged body
   of the ring swelling and settling, in antiphase with the core's glow so the
   two read as one layered light rather than a single pulsing stroke. */
@keyframes aura-breathe {
  0%, 100% { opacity: calc(var(--ring-aura-alpha) * 1.6); }
  50%      { opacity: calc(var(--ring-aura-alpha) * 2.8); }
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
@keyframes open {
  0%   { opacity: 0.9; transform: scale(0.72); }
  70%  { opacity: 0.12; transform: scale(1.18); }
  100% { opacity: 0;    transform: scale(1.3); }
}

@media (prefers-reduced-motion: reduce) {
  .live-rim .circle,
  .live-rim .circle-aura,
  .point.playing,
  .ping.playing { animation: none !important; }
  .challenge { transition: none; }
}

/* ================================================================== */
/* Landscape phone (file 75 §4 phone; owner: "staring at this          */
/* horizontal"). A short, wide viewport must not push the ring off     */
/* screen or cramp the takes. The challenge view goes two-column: the  */
/* ring/words stay as a fixed-ish header beside the scrolling takes.   */

@media (max-height: 560px) and (orientation: landscape) {
  #field { padding: calc(var(--step) * 1.5) calc(var(--step) * 3); }

  /* The feed keeps more rings in view across the width, each a touch smaller. */
  .feed { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: calc(var(--step) * 2); }
  .challenge svg { width: 110px; height: 110px; }

  /* The challenge view: ring + words as a left rail, takes scrolling at right,
     so the opened challenge stays present while a thumb scrolls the takes. */
  .challenge-view {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(200px, 34%) 1fr;
    gap: calc(var(--step) * 3);
    align-items: start;
    padding-bottom: calc(var(--step) * 3);
  }
  .challenge-view header {
    margin-bottom: 0;
    padding-top: 0;
    position: sticky;
    top: calc(var(--step) * 1.5);
    gap: var(--step);
  }
  .challenge-view header svg { width: clamp(96px, 20vh, 150px); height: clamp(96px, 20vh, 150px); }
  .challenge-view h1 { font-size: clamp(18px, 4.5vh, 26px); }
  .takes { gap: calc(var(--step) * 3); }
  .take.short .body { font-size: clamp(18px, 4vh, 24px); }
}

/* Very narrow portrait (small phones, e.g. iPhone SE): tighten the take column
   so the point does not crowd the prose. */
@media (max-width: 380px) {
  .take { grid-template-columns: calc(var(--touch) * 0.8) 1fr; gap: calc(var(--step) * 1.5); }
  .takes::before { left: calc(var(--touch) * 0.4); }
}

/* ================================================================== */
/* The firsts offer/demo (product-canon file 78 §3, K-97). The node    */
/* draws the stroke on the person's real objects, once, then withdraws.*/
/* No hand/finger/cursor is ever drawn (file 83 §4); the node is the    */
/* only figure. No count, no checklist. Charged vocabulary (canon 94):  */
/* crisp points, a straight line / closed loop, the viewer's hand.      */

.firsts-offer {
  /* An overlay on the one surface — it floats over the live field and          */
  /* nothing waits on it (file 78 §2 "never blocking"). Centred, bounded.       */
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  z-index: 30;
  pointer-events: none; /* the surface underneath stays live */
}

/* A tap outside the demo dismisses it (file 78 §2). The dismiss button fills   */
/* the overlay BEHIND the demo and is the only pointer target besides the demo. */
.firsts-dismiss {
  position: absolute;
  inset: 0;
  border: 0;
  margin: 0;
  padding: 0;
  background: color-mix(in oklab, var(--ground-deep) 42%, transparent);
  cursor: default;
  pointer-events: auto;
}

.firsts-demo {
  position: relative;
  width: clamp(220px, 44vmin, 360px);
  height: clamp(220px, 44vmin, 360px);
  overflow: visible;
  pointer-events: none; /* the demo is shown, not operated */
}

/* The demo objects: points of light in their own hands (file 78 §3). */
.firsts-object.point { stroke: none; }
.firsts-object.halo { fill: none; stroke-width: 1px; opacity: 0.5; }

/* The stroke the node draws. A dash the length of the path, offset to full so   */
/* the line is invisible, then animated to 0 so it appears to be TRACED by the   */
/* node travelling it (file 78 §3). pathLength normalises the dash across any     */
/* path length so the trace reads the same for a loop or a line.                  */
.firsts-stroke {
  fill: none;
  stroke-width: var(--line-proposed);
  stroke-linecap: round;
  stroke-linejoin: round;
  pathLength: 100;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  opacity: 0.9;
  animation: firsts-draw 1600ms var(--settle, 320ms) ease-out both;
}
/* The stroke's soft underlay (K-94d): a wider, fainter filament traced beneath
   the bright core on the same path and the same dash timing, giving the drawn
   mark weight and depth instead of a single hairline (canon 94 §7). */
.firsts-stroke-under {
  fill: none;
  stroke-width: calc(var(--line-proposed) * 4);
  stroke-linecap: round;
  stroke-linejoin: round;
  pathLength: 100;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  opacity: 0.18;
  animation: firsts-draw 1600ms var(--settle, 320ms) ease-out both;
}
/* On settle the proposed-weight line is what remains for a beat (file 77 §3),   */
/* then the whole offer withdraws. The loop's enclosure is the soft shared glow  */
/* (file 83 §1.2), faded in as the loop closes.                                   */
.firsts-glow {
  opacity: 0;
  animation: firsts-glow-in 900ms 1200ms ease-out both;
}

/* The person's own node: crisp point + thin halo, in their hand. It lifts off   */
/* the rim (--node-x0,--node-y0), travels to the settle point (--node-x1,        */
/* --node-y1) as the stroke is drawn, holds, and returns — the one figure, never */
/* a hand/finger/cursor (file 83 §4). transform drives the travel so the node's  */
/* own geometry (point + halo) rides along as a group.                            */
.firsts-node {
  transform: translate(calc(var(--node-x0) * 1px), calc(var(--node-y0) * 1px));
  animation: firsts-node-travel 2600ms ease-in-out both;
}
.firsts-node-body { stroke: none; opacity: 0.85; }
.firsts-node-point { stroke: none; }
.firsts-node-halo { fill: none; stroke-width: 1.5px; opacity: 0.6; }

.firsts-words {
  pointer-events: none;
  margin-top: calc(var(--step) * 1.5);
  max-width: 34ch;
  text-align: center;
  color: var(--ink);
  font-size: var(--challenge-words-size);
  text-wrap: balance;
}

@keyframes firsts-draw {
  from { stroke-dashoffset: 100; }
  to   { stroke-dashoffset: 0; }
}
@keyframes firsts-glow-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* The node lifts off the rim, travels to the settle point as the stroke draws,  */
/* holds while the line settles, and returns toward its place.                    */
@keyframes firsts-node-travel {
  0%   { transform: translate(calc(var(--node-x0) * 1px), calc(var(--node-y0) * 1px)); }
  22%  { transform: translate(calc(var(--node-x1) * 1px), calc(var(--node-y1) * 1px)); }
  78%  { transform: translate(calc(var(--node-x1) * 1px), calc(var(--node-y1) * 1px)); }
  100% { transform: translate(calc(var(--node-x0) * 1px), calc(var(--node-y0) * 1px)); }
}

/* The still form (file 78 §3): with motion reduced, the FINISHED shape fades in */
/* and out instead of being drawn, and the node does not travel — it rests at    */
/* the settle point. No trace, no rim-lift, no loop-draw.                         */
.firsts-offer.reduced .firsts-stroke,
.firsts-offer.reduced .firsts-stroke-under {
  stroke-dasharray: none;
  stroke-dashoffset: 0;
  animation: firsts-still 2600ms ease-in-out both;
}
.firsts-offer.reduced .firsts-glow { animation: firsts-still 2600ms ease-in-out both; }
.firsts-offer.reduced .firsts-node {
  transform: translate(calc(var(--node-x1) * 1px), calc(var(--node-y1) * 1px));
  animation: firsts-still 2600ms ease-in-out both;
}
@keyframes firsts-still {
  0%   { opacity: 0; }
  20%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { opacity: 0; }
}

/* Honour the OS preference too, not only the server-passed .reduced flag: a      */
/* viewer with prefers-reduced-motion gets the still form even if the server did  */
/* not know (file 78 §2 "a person with motion reduced gets the still form").      */
@media (prefers-reduced-motion: reduce) {
  .firsts-stroke,
  .firsts-stroke-under {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    animation: firsts-still 2600ms ease-in-out both;
  }
  .firsts-glow { animation: firsts-still 2600ms ease-in-out both; }
  .firsts-node {
    transform: translate(calc(var(--node-x1) * 1px), calc(var(--node-y1) * 1px));
    animation: firsts-still 2600ms ease-in-out both;
  }
}

/* ================================================================== */
/* ================================================================== */
/* THE FIELD CONTROL (canon 94 §2, §5) — the one first-class actionable */
/* control idiom. Every button/act on the field wears `.control`; there */
/* is no second button language and nothing ever falls back to UA       */
/* chrome. It is a POINT OF CHARGE, not a cell: crisp round-not-square   */
/* geometry (no bevel, no fence), the one --live charge, attachment-and- */
/* glow for every interactive state, and --touch as the minimum target. */
/*                                                                       */
/* The states ARE the signal language the rest of the field already     */
/* speaks (.keyword.word, the live-rim):                                 */
/*   rest   — a crisp --live-outlined control on the ground, text in     */
/*            --live, a breath of charge in the fill. It reads as live   */
/*            but idle: present, not shouting.                           */
/*   hover  — it lights: text to --live-hot, the ring to full --live,    */
/*   /focus  the fill warms, and an ADDITIVE emission glow blooms around */
/*            it (not a shaded disc — the same blend the surface uses).  */
/*            :focus-visible shows the identical glow so the keyboard    */
/*            path is first-class, never a flat UA ring.                 */
/*   active — the charge deepens and the control seats by a hair: the    */
/*            press is felt as more current, not a 3-D bevel.            */
/* The whole lift respects prefers-reduced-motion: the state still       */
/* changes (colour/charge are the affordance), only the transition and   */
/* the translate are dropped. */
.control {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--step) * 0.75);
  box-sizing: border-box;
  min-height: var(--touch);
  padding: calc(var(--step) * 1.25) calc(var(--step) * 2.75);
  font-family: var(--font-sans);
  font-size: var(--take-long);
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1;
  text-decoration: none;
  color: var(--live);
  background: color-mix(in oklab, var(--live) 7%, transparent);
  border: var(--line-ring) solid color-mix(in oklab, var(--live) 50%, transparent);
  border-radius: 999px; /* round, not square — there is no square on the field */
  cursor: pointer;
  /* a hair of resting charge: the control is attached to the ground, not
     floated on a drop-shadow card (attachment-and-glow, canon 83). */
  box-shadow: 0 0 0 color-mix(in oklab, var(--live) 0%, transparent);
  transition:
    color var(--lift) ease,
    background var(--lift) ease,
    border-color var(--lift) ease,
    box-shadow var(--lift) ease,
    transform var(--lift) ease;
}
/* The charge lifts on hover AND on keyboard focus — the keyboard path is
   first-class, the same bloom, never a flat UA outline. */
.control:hover,
.control:focus-visible {
  color: var(--live-hot);
  background: color-mix(in oklab, var(--live) 16%, transparent);
  border-color: var(--live);
  /* additive emission: a tight hot core over a wider soft halo, the
     two-weight glow the token layer names for a point of light. */
  box-shadow:
    0 0 7px color-mix(in oklab, var(--live) 75%, transparent),
    0 0 18px color-mix(in oklab, var(--live) 45%, transparent);
  /* The glow IS the focus indicator in normal mode, so the default UA outline
     is replaced — but with a TRANSPARENT outline (not `none`), so forced-colors
     / high-contrast mode, which ignores the glow and box-shadow, still paints a
     system-colour focus ring on it. Accessibility: never leave a keyboard-
     focusable control with no indicator in forced-colors mode.
     .control.mechanical inherits this rule — no duplicate needed. */
  outline: 2px solid transparent;
  outline-offset: 2px;
}
/* The press: more current, seated by a hair. Not a bevel. */
.control:active {
  background: color-mix(in oklab, var(--live) 24%, transparent);
  border-color: var(--live-hot);
  transform: translateY(0.5px);
  box-shadow:
    0 0 9px color-mix(in oklab, var(--live-hot) 70%, transparent),
    0 0 22px color-mix(in oklab, var(--live) 50%, transparent);
}
/* A disabled control is spent charge: it keeps its shape, loses the live
   signal, and takes no pointer. */
.control:disabled,
.control[aria-disabled="true"] {
  color: var(--ink-quiet);
  background: transparent;
  border-color: var(--hair);
  box-shadow: none;
  cursor: default;
  opacity: 0.6;
}
/* The MECHANICAL register of the same control (canon 94 §2a): a control
   that is not a person's warm act reads on the cool charge instead. Same
   geometry, same glow grammar, the cool token family. */
.control.mechanical {
  color: var(--live-cool);
  background: color-mix(in oklab, var(--live-cool) 8%, transparent);
  border-color: color-mix(in oklab, var(--live-cool) 50%, transparent);
}
.control.mechanical:hover,
.control.mechanical:focus-visible {
  color: var(--live-cool-hot);
  background: color-mix(in oklab, var(--live-cool) 16%, transparent);
  border-color: var(--live-cool);
  box-shadow:
    0 0 7px color-mix(in oklab, var(--live-cool) 70%, transparent),
    0 0 18px color-mix(in oklab, var(--live-cool) 42%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .control { transition: none; }
  .control:active { transform: none; }
}

/* ================================================================== */
/* The L4 write-handler fragments (file 87 §4b): the moderator's queue  */
/* and the two quiet write-confirmations. The Accept button IS a        */
/* `.control` (set in internal/field/write_handlers.go), so the idiom   */
/* above owns its whole look; this block only lays the surface out.     */

/* The queue is a column of pending submissions, breathing on --step.   */
/* It sits on the one charged ground like everything else — no card.    */
.moderation-queue {
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 2) 0;
}

/* One pending submission. A row of attachment, not a boxed card: a      */
/* faint hairline on the leading edge draws the eye down the column      */
/* (a line, not a fence — canon 83), the content aligned on --step.      */
.moderation-item {
  display: flex;
  flex-direction: column;
  gap: var(--step);
  padding: 0 0 0 calc(var(--step) * 2);
  border-left: var(--line-faint) solid var(--hair);
}

/* The emergent headline — the submitter's question. Chrome weight in    */
/* the humanist sans (it is a label of what is proposed, not the         */
/* person's own prose, so no serif), lit to full ink.                    */
.moderation-item .headline {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--challenge-words-size);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}

/* The required reason the moderator reads. Quiet — it is context under   */
/* the headline, not the loud thing, so it reads in the faint ink at a    */
/* reading size.                                                          */
.moderation-item .reason {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--take-long);
  line-height: 1.5;
  color: var(--ink-quiet);
}

/* The accept control sits at the row's leading edge, left-aligned under  */
/* the reason — the look is the `.control` idiom; this only places it.    */
.moderation-item form { margin: 0; }
.moderation-item form .control { align-self: flex-start; }

/* The two write-confirmations (cross-tie proposed, a submission landed):  */
/* quiet, true statements. They carry a <p class="quiet"> already (the      */
/* shared quiet treatment owns the faint ink and the centred calm); the     */
/* container only needs to not reintroduce a card and to breathe on --step. */
.cross-tie-proposed,
.submitted {
  padding: calc(var(--step) * 2) 0;
}
.cross-tie-proposed .quiet,
.submitted .quiet {
  /* These confirmations are short true lines, not an empty-state void, so   */
  /* they do not want the tall empty-queue padding the bare .quiet carries.  */
  padding: 0;
}
