/* =====================================================================
   home-guide.css  —  the Home Guide demo component
   ---------------------------------------------------------------------
   Everything here is scoped under .kg and owns its own tokens. That is
   deliberate: the component sits inside a page whose palette is also dark
   now, and it must not inherit from it or leak into it. Change the page
   ladder freely — this file does not care.

   Extracted from designs/home-guide-kinetic-directions.html, which stays
   as the reviewable prototype (it has the beat rig and the design notes).
   The engine lives in Scripts/home-guide.js.
   ================================================================== */

@property --angle   { syntax: "<angle>";      inherits: false; initial-value: 0deg; }
@property --glow    { syntax: "<number>";     inherits: false; initial-value: 0; }
@property --sweep   { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --rim     { syntax: "<number>";     inherits: false; initial-value: 0.25; }

/* ============================================================================
   THE DESIGN STARTS HERE
   ---------------------------------------------------------------------------
   .kg is the component. Every token is scoped to it so several designs can
   share this page and so the real component can be dropped into the live page
   without leaking a single rule — which is the whole "orthogonal" ask.

   The language is a departure and I want that on the record. The live home
   page is light brutalist: #fafafa ground, radius 0, one amber pop. This is a
   dark instrument. The reason is not fashion. A graph that assembles out of
   nothing needs a black ground for light to mean anything — bloom, travelling
   pulses, and crystallizing strokes all read as grime on #fafafa. The notes
   at the bottom give three honest ways to resolve the clash.
   ========================================================================= */
.kg {
  /* ground */
  --bg:        #07080b;
  --bg-2:      #0a0c11;
  --bg-3:      #0e1118;
  --line:      #1a1f29;
  --line-2:    #262d3a;

  /* ink */
  --ink:       #f3f5f9;
  --ink-soft:  #9aa3b4;
  --ink-dim:   #5c6575;
  --ink-faint: #333b48;

  /* one cool accent, one warm signal, one confirm. Same discipline as the
     live page: the warm is reserved and never decorative. */
  --acc:       #4d8dff;
  --acc-deep:  #0048BA;
  --acc-wash:  #12233f;
  --cyan:      #46e0d0;
  --signal:    #F5A300;
  --good:      #35d07f;

  --font: "Space Grotesk", system-ui, sans-serif;
  --mono: "Space Mono", ui-monospace, monospace;

  position: relative;
  display: block;
  width: 100%;
  height: 720px;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  isolation: isolate;

  /* The component is its own container, so every @container rule below keys
     off the width of THIS element rather than the viewport. That is what lets
     it sit in a narrow page column and collapse correctly without the page
     having to tell it anything.

     Worth being loud about: in the prototype this declaration lived on the
     review chrome that wrapped the two stages, so extracting the component
     on its own left every @container block below unmatched — the phone layout
     was silently dead, dest pills overhanging by 119px and the chip row by
     132px. It belongs here, on the component. */
  container-type: inline-size;
  /* the cursor lens reads these; JS writes them on pointermove */
  --mx: 50%;
  --my: 40%;
}

.kg.is-phone { height: 100%; }


/* ---------------------------------------------------------------------------
   DEPTH — four stacked grounds. This is the cheapest way to stop a dark UI
   reading as flat #111, and it costs no layout.
     1  two parallax dot fields at different scales (graph paper, receding)
     2  a slow aurora wash driven by an animated @property angle
     3  a cursor lens: a radial gradient that follows the pointer
     4  film grain from an inline feTurbulence, 3.5% — the single highest
        ratio of "looks expensive" to bytes in the whole file
   ------------------------------------------------------------------------ */
.kg-ground { position: absolute; inset: 0; z-index: 0; pointer-events: none; }

.kg-dots {
  position: absolute; inset: -12%;
  background-image: radial-gradient(circle, #1d2532 1px, transparent 1.4px);
  background-size: 34px 34px;
  opacity: .55;
  will-change: transform;
}
.kg-dots.far {
  background-image: radial-gradient(circle, #141a24 1px, transparent 1.4px);
  background-size: 96px 96px;
  opacity: .8;
}

.kg-aurora {
  position: absolute; inset: -30%;
  --angle: 0deg;
  background:
    conic-gradient(from var(--angle) at 32% 42%,
      rgba(77,141,255,.16), rgba(70,224,208,.07), transparent 42%, rgba(0,72,186,.14), rgba(77,141,255,.16));
  filter: blur(80px);
  opacity: .8;
  animation: kg-spin 42s linear infinite;
}
@keyframes kg-spin { to { --angle: 360deg; } }

.kg-lens {
  position: absolute; inset: 0;
  background: radial-gradient(420px circle at var(--mx) var(--my),
              rgba(77,141,255,.13), rgba(77,141,255,.04) 45%, transparent 68%);
  transition: opacity .5s ease;
}

.kg-vig {
  position: absolute; inset: 0;
  background:
    radial-gradient(130% 92% at 50% 8%, transparent 42%, rgba(0,0,0,.72) 100%),
    linear-gradient(to top, rgba(0,0,0,.66), transparent 46%);
}

.kg-grain {
  position: absolute; inset: 0; opacity: .035; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}


/* ---------------------------------------------------------------------------
   SHELL — three zones, and none of them is a sidebar.
     top    a thin instrument bar: mark, the three destinations, the ceiling
     centre the artifact, which is the point
     bottom a lower-third beam of speech plus the composer
   The ledger floats top-left over the stage: the memory made visible, because
   "we remember what you told us" is a promise you have to show, not claim.
   ------------------------------------------------------------------------ */
.kg-shell {
  position: relative; z-index: 2;
  height: 100%; display: flex; flex-direction: column; min-height: 0;
}

/* ---- instrument bar ---- */
.kg-bar {
  flex: 0 0 auto; display: flex; align-items: center; gap: 18px;
  padding: 15px 22px 14px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(to bottom, rgba(10,12,17,.86), rgba(10,12,17,.2));
  backdrop-filter: blur(10px);
}

.kg-mark { display: flex; align-items: center; gap: 9px; flex-shrink: 0; }
.kg-mark .dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 3px rgba(53,208,127,.16), 0 0 14px rgba(53,208,127,.7);
  animation: kg-pulse 2.6s ease-in-out infinite;
}
@keyframes kg-pulse { 0%,100% { --rim: .25; opacity: 1; } 50% { opacity: .55; } }
.kg-mark .nm {
  font-family: var(--mono); font-size: 11px; letter-spacing: .17em;
  text-transform: uppercase; color: var(--ink-soft);
}

/* the three destinations. Explicit, because the ask was that a few questions
   land somewhere real — so name the somewheres up front. */
.kg-dest { display: flex; align-items: center; gap: 6px; margin: 0 auto; }
.kg-dest .step {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 12px 5px 9px;
  border: 1px solid var(--line);
  background: rgba(14,17,24,.6);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-dim);
  transition: color .45s ease, border-color .45s ease, background .45s ease;
}
.kg-dest .step .pip {
  width: 6px; height: 6px; border: 1px solid var(--ink-faint);
  transition: background .45s ease, border-color .45s ease, box-shadow .45s ease;
}
.kg-dest .step[data-on="1"] { color: var(--ink); border-color: var(--acc); background: rgba(18,35,63,.7); }
.kg-dest .step[data-on="1"] .pip {
  background: var(--acc); border-color: var(--acc);
  box-shadow: 0 0 10px var(--acc), 0 0 22px rgba(77,141,255,.55);
}
.kg-dest .step[data-done="1"] { color: var(--ink-soft); border-color: var(--line-2); }
.kg-dest .step[data-done="1"] .pip { background: var(--good); border-color: var(--good); box-shadow: 0 0 8px rgba(53,208,127,.6); }
.kg-dest .arrow { color: var(--ink-faint); font-size: 11px; }

/* ---- centre: the artifact ---- */
/* overflow:hidden is load-bearing. The answer slab and the keep card park
   OUTSIDE this box when closed \u2014 the slab 102% down, the card 100%+30 to the
   right. Without clipping here they still paint, and at z-index 5 the parked
   slab lands on top of the speech beam below. */
.kg-stage { flex: 1; min-height: 0; position: relative; overflow: hidden; }
/* Width AND height said outright, because an svg with a viewBox is a replaced
   element: leave either one auto and it sizes itself from the viewBox's own ratio
   and ignores the edge opposite. Measured after that was tried - the drawing came
   out 431 wide in a 907 stage with `left: 268px; right: 0` in force, which put a
   third of the graph past the right edge with its lines running off to boxes that
   were not there.

   So one mechanism positions the drawing and it is the engine: syncStageInset
   measures the room the ledger and the keep card take and sets left and width
   together. Two mechanisms - an inline left here, a right in this file - is what
   produced the conflict this replaced. */
.kg-svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* ---- ledger: what we heard, as we hear it ---- */
.kg-ledger {
  position: absolute; top: 16px; left: 18px; z-index: 3;
  width: 232px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--acc);
  background: rgba(8,10,14,.78);
  backdrop-filter: blur(14px);
  padding: 11px 13px 5px;
  opacity: 0; transform: translateX(-10px);
  transition: opacity .6s ease, transform .6s cubic-bezier(.22,1,.28,1);
}
.kg-ledger[data-on="1"] { opacity: 1; transform: none; }
.kg-ledger .hd {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .15em;
  text-transform: uppercase; color: var(--ink-dim);
  padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.kg-ledger .hd .ct { color: var(--good); margin-left: 7px; }

/* Collapsed once the star forms. Measured reason, not taste: expanded, this
   card covers viewBox x[-176,170] y[24,367], and the star's left spoke lands
   at x[74,230] y[256,308] — straight underneath it. The right side is worse.
   So the ledger is a graph-beat affordance: while you are describing, the
   memory is the proof; once the artifact exists, the artifact is. */
.kg-ledger[data-collapsed="1"] {
  width: auto;
  padding: 9px 12px;
}
.kg-ledger[data-collapsed="1"] ul { display: none; }
.kg-ledger[data-collapsed="1"] .hd {
  padding-bottom: 0; margin-bottom: 0; border-bottom: 0;
}
.kg-ledger ul { list-style: none; margin: 0; padding: 0; }
.kg-ledger li {
  display: flex; gap: 8px; align-items: flex-start;
  font-size: 11.5px; line-height: 1.42; color: var(--ink-soft);
  padding: 0 0 8px;
  opacity: 0; transform: translateY(6px);
}
.kg-ledger li.in { animation: kg-ledger-in .5s cubic-bezier(.22,1,.28,1) forwards; }
@keyframes kg-ledger-in { to { opacity: 1; transform: none; } }
.kg-ledger li .ck { color: var(--good); flex-shrink: 0; font-size: 10px; line-height: 1.6; }

/* ---- bottom: the beam ----
   Speech as a film's lower third. The current line is large and lit; the ones
   before it recede — smaller, dimmer, very slightly blurred, as if falling
   away from the reader. This is the specific move that stops it being a chat
   log: there is no scrollback to graze, only a present tense. ------------- */
.kg-beam {
  flex: 0 0 auto; position: relative; z-index: 3;
  padding: 16px 26px 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 132px;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 26%);
          mask-image: linear-gradient(to bottom, transparent, #000 26%);
}
.kg-line {
  display: flex; gap: 12px; align-items: baseline;
  transform-origin: left bottom;
  transition: opacity .7s ease, transform .7s cubic-bezier(.22,1,.28,1), filter .7s ease;
  margin-bottom: 9px;
}
.kg-line .who {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-dim);
  flex: 0 0 44px; text-align: right; padding-top: 6px;
}
.kg-line .txt { font-size: 19px; line-height: 1.42; letter-spacing: -.005em; max-width: 74ch; }
.kg-line[data-who="you"] .txt { color: var(--ink); font-weight: 500; }
.kg-line[data-who="you"] .who { color: var(--cyan); }
.kg-line[data-who="guide"] .txt { color: #dfe6f2; font-weight: 300; }
.kg-line[data-age="1"] { opacity: .40; transform: scale(.925); filter: blur(.4px); }
.kg-line[data-age="2"] { opacity: .16; transform: scale(.87);  filter: blur(1px); }

/* per-word arrival. Words do not fade — they resolve out of blur and rise a
   hair, which is what makes streamed text feel spoken rather than printed. */
.kg-w { display: inline-block; opacity: 0; filter: blur(7px); transform: translateY(7px); white-space: pre; }
.kg-w.in { animation: kg-word .46s cubic-bezier(.2,.9,.24,1) forwards; }
@keyframes kg-word { to { opacity: 1; filter: blur(0); transform: none; } }

/* the guide's own emphasis: a word it wants you to keep */
.kg-em { color: var(--ink); font-weight: 600; box-shadow: inset 0 -2px 0 rgba(77,141,255,.45); }

.kg-caret {
  display: inline-block; width: 2px; height: 1.02em; vertical-align: -2px;
  margin-left: 3px; background: var(--cyan);
  box-shadow: 0 0 8px var(--cyan);
  animation: kg-blink 1.05s steps(1) infinite;
}
@keyframes kg-blink { 0%,49% { opacity: 1; } 50%,100% { opacity: 0; } }

/* ---- chips: a dealt hand, not a button row ----
   Each chip is rotated and lifted on an arc so the set reads as offered
   rather than listed, and each one leans toward the cursor with distance
   falloff. Magnetism is the difference between a control and an invitation. */
.kg-chips {
  flex: 0 0 auto; position: relative; z-index: 4;
  display: flex; gap: 9px; justify-content: center; align-items: flex-end;
  padding: 4px 26px 2px; min-height: 46px;
  perspective: 700px;
}
.kg-chip {
  --pull-x: 0px; --pull-y: 0px; --lift: 0px; --tilt: 0deg;
  font-family: var(--font); font-size: 12.5px; font-weight: 500; letter-spacing: .005em;
  color: #cfd8e8;
  background: rgba(14,17,24,.82);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  padding: 8px 15px;
  cursor: pointer;
  white-space: nowrap;
  backdrop-filter: blur(8px);
  transform: translate3d(var(--pull-x), calc(var(--pull-y) + var(--lift)), 0) rotate(var(--tilt));
  transition: transform .34s cubic-bezier(.22,1,.28,1), color .28s ease,
              border-color .28s ease, background .28s ease, box-shadow .28s ease;
  opacity: 0;
}
.kg-chip.in { animation: kg-chip-in .58s cubic-bezier(.22,1,.28,1) forwards; }
@keyframes kg-chip-in { from { opacity: 0; transform: translate3d(0,16px,0) rotate(var(--tilt)) scale(.94); } to { opacity: 1; } }
.kg-chip:hover, .kg-chip:focus-visible {
  color: #fff; border-color: var(--acc);
  background: rgba(18,35,63,.9);
  box-shadow: 0 0 0 1px rgba(77,141,255,.35), 0 8px 26px -8px rgba(77,141,255,.55);
  outline: none;
  --lift: -4px;
}
.kg-chip:active { --lift: -1px; }
.kg-chip .k {
  font-family: var(--mono); font-size: 9.5px; color: var(--ink-dim);
  margin-right: 7px; letter-spacing: .1em;
}

/* ---------------------------------------------------------------------------
   COMPOSER — the only thing on the stage a visitor can actually touch, so it
   has to read as live rather than as chrome.

   It did not. It was rgba(8,10,14,.86) sitting on a #07080b ground: a panel
   DARKER than the surface behind it, which is how you make something recede.
   Value is the whole fix — the idle field is now lifted well above the ground
   and rimmed in accent, so the eye lands on it before anything else static.

   Two states, and the discipline matters more than the styling:

     idle    (data-idle="1")  before the run, and again once it finishes.
                              Lifted, accent rim, breathing glow, a live caret
                              typing example sentences.
     running (data-idle="0")  it steps back to a flat, dim slot. The stage is
                              the story while the story is playing; a pulsing
                              field beside it would just be two things shouting.

   The glow breathes on --glow, a registered @property, so it is one animated
   number driving both spread and alpha rather than two keyframed shadows.
   ------------------------------------------------------------------------ */
.kg-composer {
  --glow: .4;
  flex: 0 0 auto; position: relative; z-index: 4;
  margin: 8px 22px 20px;
  display: flex; align-items: center; gap: 14px;
  border: 1px solid var(--line-2);
  background: rgba(8,10,14,.86);
  backdrop-filter: blur(14px);
  padding: 13px 15px 13px 17px;
  transition: border-color .34s ease, background .34s ease,
              box-shadow .34s ease, opacity .34s ease, transform .34s ease;
}

/* the resting invitation */
.kg-composer[data-idle="1"] {
  cursor: text;
  border-color: rgba(77,141,255,.4);
  background: linear-gradient(180deg, rgba(28,39,58,.9), rgba(14,19,29,.93));
  box-shadow:
    0 0 calc(26px + var(--glow) * 28px) -16px rgb(77 141 255 / calc(.3 + var(--glow) * .36)),
    inset 0 1px 0 rgb(255 255 255 / .05);
  animation: kg-breathe 4.2s ease-in-out infinite;
}
.kg-composer[data-idle="1"]:hover {
  border-color: var(--acc);
  transform: translateY(-1px);
  animation-play-state: paused;
  box-shadow:
    0 0 0 1px rgb(77 141 255 / .2),
    0 0 46px -14px rgb(77 141 255 / .72),
    inset 0 1px 0 rgb(255 255 255 / .07);
}

/* out of the way while the stage is talking */
.kg-composer[data-idle="0"] { opacity: .62; }

.kg-composer:focus-within {
  border-color: var(--acc);
  box-shadow: 0 0 0 1px rgba(77,141,255,.3), 0 0 40px -14px rgba(77,141,255,.7);
}
@keyframes kg-breathe { 0%,100% { --glow: .25; } 50% { --glow: 1; } }

.kg-composer .ph {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 3px;
  overflow: hidden;
}
/* Bright and light-weight, because grey placeholder text is a thing people are
   trained to ignore. At this value it reads as something already typed, which
   is the point: it shows the shape of the sentence they are meant to give. */
.kg-composer .ph .t {
  font-size: 14.5px; font-weight: 300; color: var(--ink);
  white-space: pre; overflow: hidden; text-overflow: ellipsis;
}
.kg-composer[data-idle="0"] .ph .t { color: var(--ink-dim); }

/* the caret. 2px, accent, glowing — it is the single strongest "you can type
   here" signal available and it costs nothing. */
.kg-cw {
  flex-shrink: 0; width: 2px; height: 16px;
  background: var(--acc); box-shadow: 0 0 9px var(--acc);
  animation: kg-blink 1.06s step-end infinite;
}
.kg-composer[data-idle="0"] .kg-cw { display: none; }

/* The alternative, said separately rather than jammed into the same grey
   string as the invitation. Two affordances, two places. */
.kg-or {
  flex-shrink: 0;
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-dim);
  padding-left: 14px; border-left: 1px solid var(--line-2);
}

/* the send button's rim is a rotating conic gradient masked to a 1px ring —
   an animated @property angle, no JS. It says "live" without a spinner. */
.kg-send {
  --angle: 0deg;
  position: relative; flex-shrink: 0;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .14em;
  text-transform: uppercase; font-weight: 700;
  color: #fff; background: var(--acc-deep);
  border: 0; padding: 11px 18px; cursor: pointer;
  white-space: nowrap;
  transition: background .25s ease, transform .25s cubic-bezier(.22,1,.28,1),
              box-shadow .25s ease;
}
/* A cast shadow in the accent, so the button sits ON the field rather than
   being a rectangle punched into it. */
.kg-composer[data-idle="1"] .kg-send { box-shadow: 0 7px 20px -9px rgb(0 72 186 / .95); }
.kg-send::before {
  content: ""; position: absolute; inset: -1px; z-index: -1;
  background: conic-gradient(from var(--angle), var(--acc), var(--cyan), var(--signal), var(--acc));
  animation: kg-spin 3.4s linear infinite;
}
.kg-send:hover { background: #0055dd; transform: translateY(-1px); }

/* ---------------------------------------------------------------------------
   ANSWER SLAB — slides up over the lower stage when a question resolves.
   The number lands with a hard deceleration and tabular figures so the digits
   do not shuffle while counting. Beside it, the rows behind the number grow
   in as bars — not code. See the note on .kg-bars for why.
   ------------------------------------------------------------------------ */
.kg-answer {
  position: absolute; left: 18px; right: 18px; bottom: 0; z-index: 5;
  border: 1px solid var(--line-2); border-bottom: 0;
  background: linear-gradient(to bottom, rgba(11,14,20,.95), rgba(7,8,11,.98));
  backdrop-filter: blur(18px);
  padding: 16px 20px 18px;
  transform: translateY(102%);
  transition: transform .78s cubic-bezier(.19,1,.22,1);
  display: flex; gap: 24px; align-items: flex-start;
}
.kg-answer[data-on="1"] { transform: none; }
/* IT CANNOT LOSE ITS TOP. It is pinned to the bottom of a stage that clips, with
   no top edge and no height, so anything taller than the stage grew UPWARD and
   what went off the edge first was the caption and the figure - the two things
   the whole beat exists to show. Reported on a laptop and a tablet, and the
   cause is that a 1440x800 laptop is "wide" with a ~300px stage while the blocks
   that made the slab ~300px tall were only ever dropped below 720px of WIDTH.
   Those blocks have gone for a better reason, so this is the belt rather than
   the fix: whatever it ends up holding, it is bounded by the room it has, and
   what does not fit scrolls instead of vanishing off the top. */
.kg-answer { top: auto; max-height: 100%; overflow-y: auto; }
/* The slab retreats when keep opens, at every width. The card's whole claim is
   "keep the star you just built" — and measured, the slab covered everything
   but the top dimension, so the sentence was next to a hidden star. The
   breakdown already made its point one beat earlier; the number survives in
   the card's receipt. Answer beat = proof. Keep beat = ownership. */
.kg[data-keep="1"] .kg-answer { transform: translateY(102%); }
.kg-answer .big { flex-shrink: 0; }
.kg-answer .cap {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .15em;
  text-transform: uppercase; color: var(--ink-dim); margin-bottom: 6px;
}
.kg-answer .num {
  font-size: 42px; font-weight: 700; letter-spacing: -.03em; line-height: 1;
  font-variant-numeric: tabular-nums;
  color: #fff;
  text-shadow: 0 0 32px rgba(77,141,255,.5);
}
.kg-answer .sub { font-size: 12px; color: var(--ink-soft); margin-top: 7px; }
.kg-answer .sub b { color: var(--good); }

/* ---- the left column, below the number ----
   This was dead space. What belongs in it is not more of the answer but the
   context a practice owner would ask for next, unprompted: is it up or down,
   what does the shape look like over time, and what is the one thing worth
   noticing. All three are readable in about two seconds and none of them
   requires knowing what a star schema is. ------------------------------- */



/* ---- the breakdown ----
   This space used to hold generated SQL. It does not any more, on purpose. The
   audience is a business owner deciding whether this tool is worth an email
   address; a SELECT statement asks them to audit a language they do not read,
   and it makes the product look like a developer tool. What actually earns
   trust at that moment is the answer itself — the ranked rows behind the one
   number, which they can check against what they already know about their own
   practice — plus one plain sentence saying how it was worked out. The bars
   also sum to the headline figure, so the slab is internally auditable
   without a single reserved word. -------------------------------------- */
.kg-bars { flex: 1; min-width: 0; border-left: 1px solid var(--line); padding-left: 22px; }
.kg-bars .cap { display: flex; align-items: center; gap: 8px; margin-bottom: 11px; }
.kg-bars .cap .tag {
  color: var(--good); border: 1px solid rgba(53,208,127,.4);
  padding: 1px 5px; font-size: 8.5px; letter-spacing: .1em;
}

.kg-brow { display: flex; align-items: center; gap: 11px; margin-bottom: 8px; }
.kg-brow .nm {
  flex: 0 0 116px; min-width: 0;
  font-size: 12px; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kg-brow .track { flex: 1; min-width: 0; height: 10px; background: #101725; position: relative; overflow: hidden; }
.kg-brow .fill {
  position: absolute; top: 0; bottom: 0; left: 0; width: 100%;
  transform-origin: left center; transform: scaleX(0);
  background: linear-gradient(to right, var(--acc-deep), var(--acc));
  transition: transform .95s cubic-bezier(.19,1,.22,1);
}
.kg-brow[data-on="1"] .fill { transform: scaleX(var(--v)); }
.kg-brow .val {
  flex: 0 0 64px; text-align: right;
  font-family: var(--mono); font-size: 11.5px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.kg-brow.top .nm { color: var(--ink); font-weight: 600; }
.kg-brow.top .fill { background: linear-gradient(to right, var(--acc), var(--cyan)); }
.kg-brow.top .val { color: var(--cyan); }

.kg-prov {
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line);
  display: flex; gap: 10px; align-items: baseline;
}
.kg-prov .tag {
  flex-shrink: 0; color: var(--good);
  border: 1px solid rgba(53,208,127,.4); padding: 1px 5px;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: .1em;
  text-transform: uppercase; white-space: nowrap;
}
.kg-prov .txt { font-size: 12px; line-height: 1.5; color: var(--ink-soft); }
.kg-prov .txt b { color: var(--ink); font-weight: 600; }

/* ---------------------------------------------------------------------------
   KEEP — the close. It arrives only after something exists to lose, which is
   the entire theory of this CTA: no ask before there is an artifact with the
   user's own words in it.
   ------------------------------------------------------------------------ */
.kg-keep {
  position: absolute; top: 50%; right: 20px; z-index: 6;
  width: 316px;
  transform: translate(calc(100% + 30px), -50%);
  /* Hidden once its exit has finished, not merely parked. Parking relied on the
     stage's overflow to clip it, which held at wide (it parks to the right) and
     failed at narrow, where it parks 140px below its own height and the
     stage in working mode is taller than that: measured on the live page,
     the closed card sat 454 to 758 units down a 598-unit stage, over the
     drawing, after the visitor took a way in. The delay keeps the slide out;
     the arrival below switches visibility on at once so the slide in is
     untouched. */
  visibility: hidden;
  transition: transform .82s cubic-bezier(.19,1,.22,1), visibility 0s linear .82s;
  /* Never taller than the stage it sits in. A short stage (a small window
     with the conversation taking its share) clipped the card's last line and
     its fine print; now the card scrolls inside itself instead. */
  max-height: calc(100% - 20px);
  overflow: auto;
  border: 1px solid var(--acc);
  background: linear-gradient(160deg, rgba(18,35,63,.96), rgba(8,10,14,.97));
  backdrop-filter: blur(20px);
  box-shadow: 0 30px 80px -30px rgba(0,0,0,.95), 0 0 60px -22px rgba(77,141,255,.65);
  padding: 18px 19px 17px;
}
.kg-keep[data-on="1"] {
  transform: translate(0, -50%);
  visibility: visible;
  transition: transform .82s cubic-bezier(.19,1,.22,1), visibility 0s;
}
.kg-keep .eyebrow {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--signal); margin-bottom: 10px;
}
.kg-keep h4 {
  margin: 0 0 8px; font-size: 21px; font-weight: 700; letter-spacing: -.02em;
  line-height: 1.2;
}
.kg-keep .body { font-size: 12.5px; line-height: 1.6; color: #c3ccdc; margin: 0 0 14px; }
.kg-keep .body b { color: #fff; }

/* the artifact receipt — the thing being promised, drawn not described */
.kg-receipt {
  display: flex; align-items: center; gap: 11px;
  border: 1px solid var(--line-2); background: rgba(7,8,11,.7);
  padding: 9px 11px; margin: 0 0 14px;
}
.kg-receipt .meta { min-width: 0; }
.kg-receipt .meta .a {
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--good); margin-bottom: 3px;
}
.kg-receipt .meta .b { font-size: 11.5px; color: var(--ink-soft); line-height: 1.4; }
/* The receipt names what is being promised, including the number, because the
   answer slab is gone by the time this card is on screen. */
.kg-receipt .meta .c { display: block; font-size: 11.5px; color: var(--ink-soft); line-height: 1.4; margin-top: 3px; }
.kg-receipt .meta .c b { color: #fff; font-variant-numeric: tabular-nums; }

/* Two buttons, no field: the address form the card used to carry ran no code.
   The bright one keeps (or, after the showcase, hands over the field); the
   quiet one puts the card away. */
.kg-keep-act { display: flex; align-items: stretch; gap: 8px; margin-bottom: 9px; }
.kg-keep-go {
  flex: 1; border: 0; cursor: pointer;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em;
  text-transform: uppercase; font-weight: 700; color: #05060a;
  background: var(--signal); padding: 12px 14px;
  white-space: nowrap;
  transition: background .2s ease, opacity .2s ease;
}
.kg-keep-go:hover { background: #ffb92a; }
.kg-keep-go:disabled { opacity: .55; cursor: default; }
.kg-keep-later {
  flex-shrink: 0; cursor: pointer;
  border: 1px solid var(--line-2); background: transparent;
  font-family: var(--mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft); padding: 0 13px;
  transition: border-color .2s ease, color .2s ease;
}
.kg-keep-later:hover { border-color: var(--ink-dim); color: var(--ink); }
.kg-keep-go:focus-visible, .kg-keep-later:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }
.kg-keep .fine { font-size: 10.5px; line-height: 1.55; color: var(--ink-dim); margin: 0; }

/* ---------------------------------------------------------------------------
   REST — before anyone speaks. Not an empty box. An invitation with a
   heartbeat, and one visible half-built shape so the surface is never dead.
   ------------------------------------------------------------------------ */
.kg-rest {
  position: absolute; inset: 0; z-index: 4;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 0 30px;
  transition: opacity .6s ease, transform .8s cubic-bezier(.22,1,.28,1);
  pointer-events: none;
}
.kg-rest[data-off="1"] { opacity: 0; transform: scale(1.04); }
.kg-rest .kicker {
  font-family: var(--mono); font-size: 10px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--acc); margin-bottom: 16px;
}
.kg-rest h3 {
  margin: 0 0 14px; font-size: 46px; font-weight: 300; letter-spacing: -.035em;
  line-height: 1.06; max-width: 20ch;
}
.kg-rest h3 b { font-weight: 700; }
.kg-rest .sub {
  margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-soft);
  max-width: 54ch; font-weight: 300;
}


/* ---------------------------------------------------------------------------
   GRAM NOTATION, dark set.
   The notation is the product. It is drawn to spec, not decorated:
     entity        hollow box, hard corners
     event entity  filled — a happening, not a thing
     value type    dashed box, lowercase label
     role pair     two adjacent squares on the fact line
     uniqueness    a bar over the role(s) it constrains
     mandatory     a filled dot where the line meets the entity

   The bar and dot are readable here at a 900-unit viewBox because the stage
   is full-bleed. That is a real gain over the static prototype, where the
   overview drawings had to compromise to a single role mark.
   ------------------------------------------------------------------------ */
.kg .nbox { fill: var(--bg-3); stroke: #7d8798; stroke-width: 1.6; }
.kg .nbox.entity { fill: #0d1017; stroke: #8b95a8; }
.kg .nbox.event  { fill: var(--acc-wash); stroke: var(--acc); stroke-width: 2; }
.kg .nbox.vt     { fill: rgba(14,17,24,.6); stroke: var(--ink-dim); stroke-width: 1.4; stroke-dasharray: 6 5; }
.kg .nbox.fact   { fill: #14263f; stroke: var(--acc); stroke-width: 2.4; }
.kg .nbox.dim    { fill: #0f1319; stroke: #93a0b5; stroke-width: 1.8; }

.kg .nlab {
  fill: var(--ink); font-family: var(--font); font-size: 14px; font-weight: 600;
  text-anchor: middle; letter-spacing: .01em;
}
.kg .nlab.vt  { fill: var(--ink-soft); font-size: 12.5px; font-weight: 400; font-style: italic; }
.kg .nlab.big { font-size: 15.5px; font-weight: 700; }
.kg .nsub {
  fill: var(--ink-dim); font-family: var(--mono); font-size: 9.5px;
  text-anchor: middle; letter-spacing: .06em;
}
.kg .nmeas {
  fill: var(--cyan); font-family: var(--mono); font-size: 10px;
  text-anchor: middle; letter-spacing: .04em;
}
/* WHAT RIDES THE ROW AND IS NEVER ADDED UP: an order number, a status, a
   transaction type. Kimball's degenerate dimension, in the words the business
   used rather than the term.

   It has to be legible AS not-a-measure, because the cyan above it is a claim
   that a thing gets summed and these are exactly the things that do not. So it
   takes the dim ink and the italic that value types already wear elsewhere on
   this stage, which is what these are, and keeps the mono size of the measures
   so the fact box reads as one block rather than two.

   It had no rule at all until now. The class was drawn from the moment the
   layout learned to place it, and with nothing matching it, SVG's initial
   fill applied: black text at the inherited 16px, on a #07080b stage, sized by
   a layout that had measured it at 10. Invisible, and too big for the box it
   was given. Nobody saw the line, which is why nobody reported it. */
.kg .nrides {
  fill: var(--ink-dim); font-family: var(--mono); font-size: 10px;
  text-anchor: middle; letter-spacing: .04em; font-style: italic;
}

.kg .edge { stroke: #46516333; stroke-width: 1.4; fill: none; }
.kg .edge.live { stroke: #5a6879; stroke-width: 1.6; }
.kg .edge.spoke { stroke: var(--acc); stroke-width: 1.8; opacity: .55; }

.kg .rbox { fill: #0d1017; stroke: #8b95a8; stroke-width: 1.3; }
.kg .ubar { stroke: var(--acc); stroke-width: 2.6; stroke-linecap: butt; }
.kg .mdot { fill: #cdd6e4; }
.kg .rlab {
  fill: var(--ink-dim); font-family: var(--mono); font-size: 8.5px;
  text-anchor: middle; letter-spacing: .05em;
}

.kg .spark { fill: none; stroke: var(--cyan); stroke-width: 1.4; }
.kg .pulse { fill: var(--cyan); }
.kg .halo  { fill: none; stroke: var(--acc); stroke-width: 1; opacity: .3; }

/* bloom. Applied to the accent layer only, never to text, or everything
   turns to soup. */
.kg .bloomed { filter: url(#kg-bloom); }


/* ---------------------------------------------------------------------------
   NARROW — the frames are containers, so this fires on the component's own
   width, not the viewport's. Below 720 the ledger, the destination rail and
   the breakdown column cannot hold anything readable, so they are not there. The
   phone reaches the same conclusion by itself rather than by a separate
   stylesheet, which is the point of writing it as a container query.
   ------------------------------------------------------------------------ */
@container (max-width: 720px) {
  .kg-bar { padding: 12px 14px 11px; gap: 10px; }
  .kg-dest { display: none; }
  .kg-ledger { display: none; }
  /* Cap it, do not just floor it. The beam is bottom-anchored (justify-content:
     flex-end) with a mask that fades its top edge, so a max-height clips the
     OLDEST line — the one already down at 16% opacity — and nothing readable
     is lost. Uncapped, the copy rewraps at this width and the beam grew to
     259px, squeezing the stage to 271px when the answer slab needs 387px: the
     headline number was cropped off the top of the stage entirely. */
  .kg-beam {
    padding: 12px 16px 0;
    min-height: 108px; max-height: 112px; overflow: hidden;
  }
  .kg-line .txt { font-size: 15.5px; }
  .kg-line .who { flex-basis: 34px; font-size: 8.5px; }
  /* Three chips will not fit 390px, and centring them clipped the first and
     last. Scroll instead, left-aligned, and drop the fan — a row you drag
     should not also be rotated. */
  .kg-chips {
    padding: 4px 14px 2px; flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    /* The scrollbar is hidden, so without this the row just ends in a chip cut
       mid-word and reads as a layout bug rather than as a scroller. Measured at
       390px: 583px of chips in a 346px row, with the third one 224px off-canvas.
       Fading the right edge is what turns that cut into an invitation — and the
       composer below now says "or pick one", so the row has to look draggable. */
    mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 34px), transparent 100%);
  }
  .kg-chips::-webkit-scrollbar { display: none; }
  .kg-chip { font-size: 11.5px; padding: 7px 12px; --tilt: 0deg; --pull-y: 0px; }
  .kg-composer { margin: 6px 14px 16px; padding: 11px 12px 11px 14px; gap: 10px; }
  .kg-composer .ph .t { font-size: 13px; }
  .kg-cw { height: 14px; }
  /* No room for the second affordance at this width, and the chips it points at
     are already right above the field. The caret carries the invitation alone. */
  .kg-or { display: none; }
  .kg-send { padding: 10px 14px; }
  .kg-answer { left: 10px; right: 10px; flex-direction: column; gap: 12px; padding: 13px 14px 15px; }
  .kg-answer .num { font-size: 32px; }
  /* Narrow keeps the number, the delta, all five rows and the how-line. The
     trend and the insight go: the trend needs width to read as a shape, and
     the insight only restates what the bars already show. Keeping all five
     rows matters more than either \u2014 they sum to the headline, and that is what
     makes the slab checkable. */
  .kg-bars { border-left: 0; border-top: 1px solid var(--line); padding: 12px 0 0; }
  .kg-brow .nm { flex-basis: 92px; font-size: 11px; }
  .kg-brow .val { flex-basis: 56px; font-size: 10.5px; }
  .kg-prov .txt { font-size: 11px; }
  /* Anchored to the top, not the bottom. Bottom-anchoring made the card's
     fit depend on stage height, and it clipped 2px at 599 wide; from the top
     it cannot underflow no matter how short the stage gets. It still ENTERS
     from below \u2014 that is the transform's job, not the offset's. */
  .kg-keep { left: 10px; right: 10px; width: auto; top: 10px; bottom: auto; transform: translateY(calc(100% + 140px)); }
  .kg-keep[data-on="1"] { transform: none; }
  .kg-keep h4 { font-size: 18px; }
  .kg-rest h3 { font-size: 29px; }
  .kg-rest .sub { font-size: 13px; }
}


/* ---------------------------------------------------------------------------
   WORK MODE

   Same stage, story removed. Three things change and nothing else does:

     1. The facsimile becomes a real input, styled to look identical to it, so
        clicking the field does not make the field jump.
     2. The beam becomes a transcript — it keeps every line, it scrolls, and it
        stops dimming what was said earlier, because earlier is the reason the
        drawing looks the way it does.
     3. The narration furniture goes. The ledger is a device for a story that
        is over, and the answer slab is a number nobody has computed: work mode
        draws the model, and showing a figure it did not calculate would be the
        one genuinely dishonest thing this component could do.
   ------------------------------------------------------------------------ */

/* Styled to match .kg-composer .ph .t exactly, so the swap is invisible. No
   border and no outline of its own — the composer already draws a focus ring
   with :focus-within, which is why that rule exists. */
.kg-input {
  flex: 1; min-width: 0;
  font-family: inherit; font-size: 14.5px; font-weight: 300;
  color: var(--ink); background: none;
  border: 0; padding: 0; margin: 0;
  outline: none; box-shadow: none;
  caret-color: var(--acc);
}
.kg-input::placeholder { color: var(--ink-dim); opacity: 1; }
.kg-input:disabled { color: var(--ink-soft); cursor: progress; }

/* In work mode the field is the primary control at all times, so it never
   retreats to .62 the way it does while the showcase is talking. */
.kg[data-mode="work"] .kg-composer[data-idle="0"] { opacity: 1; }
.kg[data-mode="work"] .kg-composer {
  cursor: text;
  border-color: rgba(77,141,255,.4);
  background: linear-gradient(180deg, rgba(28,39,58,.9), rgba(14,19,29,.93));
}

/* Working. A sweep along the bottom rim rather than a spinner: there is no room
   for one, the composer is already the thing being waited on, and a bar that
   travels reads as progress where a rotating shape reads as a stall.

   The travel is background-position rather than transform. A translated bar has
   to be clipped by an overflow on the parent, and turning overflow on and off
   with a state is the kind of thing that clips something else six months later.
   A half-width no-repeat gradient slid from -50% to 150% cannot escape the box
   it is painted in. */
.kg-composer[data-busy="1"] { cursor: progress; }
.kg-composer[data-busy="1"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background-image: linear-gradient(90deg,
    transparent, var(--acc), var(--cyan), var(--acc), transparent);
  background-size: 50% 100%;
  background-repeat: no-repeat;
  animation: kg-work 1.15s linear infinite;
}
@keyframes kg-work {
  from { background-position: -50% 0; }
  to   { background-position: 150% 0; }
}
.kg-composer[data-busy="1"] .kg-send { opacity: .45; pointer-events: none; }

/* The transcript.

   justify-content flips to flex-start on purpose. The showcase stacks its three
   lines from the bottom, which is right for a beam, but flex-end plus
   overflow-y is the combination that leaves scrolled-up content unreachable in
   more than one engine. Started from the top and scrolled to the bottom in JS,
   it behaves the same and can actually be read back.

   max-height is an absolute cap rather than only a share, because the share is
   what broke it: 34% of the 560px floor is 190px, which left the stage under
   200px. 168px is about five lines of the beam's 19px type, which is one
   exchange plus a wrapped reply, and the rest scrolls. The demo band gives work
   mode a taller box to spend this out of — see home-terminal-press.css. */
.kg[data-mode="work"] .kg-beam {
  justify-content: flex-start;
  /* A PLAIN PIXEL CAP, not a percentage.
     This read min(30%, 168px) while the beam was a child of .kg-shell, which has
     a definite height, so the 30% resolved against it. Wrapping the beam in the
     light plinth put it inside an auto-height parent, where a percentage
     max-height resolves to nothing at all - so the cap stopped binding and a long
     guide reply grew the beam, grew the plinth, and took the room out of the
     drawing. Measured with one real reply in it: the plinth went 300 to 429 and
     the stage 424 to 294. */
  max-height: 168px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 8px;
  /* A long fade would eat the top line of something being read. Short enough to
     keep the edge soft, short enough not to hide a word. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 7%);
          mask-image: linear-gradient(to bottom, transparent, #000 7%);
  scrollbar-width: thin;
  scrollbar-color: var(--line-2) transparent;
}
.kg[data-mode="work"] .kg-beam::-webkit-scrollbar { width: 6px; }
.kg[data-mode="work"] .kg-beam::-webkit-scrollbar-track { background: transparent; }
.kg[data-mode="work"] .kg-beam::-webkit-scrollbar-thumb {
  background: var(--line-2); border-radius: 3px;
}

/* Nothing ages out of a conversation. */
.kg[data-mode="work"] .kg-line[data-age="1"],
.kg[data-mode="work"] .kg-line[data-age="2"],
.kg[data-mode="work"] .kg-line {
  opacity: 1; transform: none; filter: none;
}

/* Narration furniture, retired.

   The ledger is a device for a story that is over. The answer slab is a number
   nobody computed. The rest panel still reads "DESCRIBE IT ONCE / Say what your
   business does" — an instruction to do the thing they are already doing, and
   measured, it was painted across three of seven boxes thirteen turns in. The
   free-turn meter is worse than redundant: it says "6 turns left" and never
   moves, so it is a claim the component cannot keep. A turn limit belongs on the
   server with the endpoint, not in a counter the page can lie about.

   Declared here as well as set in JS on purpose. data-off is the rest panel's
   own mechanism and one cue already toggles it; a rule that cannot be undone by
   an attribute is what stops the next cue from putting it back. The free-turn
   meter that used to sit in this list is gone from the markup altogether: the
   budget is not a factor (Edward, 2026-09-15), and a count the page cannot keep
   is worse than none. */
.kg[data-mode="work"] .kg-ledger,
.kg[data-mode="work"] .kg-answer,
.kg[data-mode="work"] .kg-rest { display: none; }

/* ---------------------------------------------------------------------------
   THE INVITATION

   The story plays untouched, field and all. When it ends, the guide says one
   line to the visitor and the component marks itself data-invite="1": the
   lower half reorders from (speech, chips, field) to (speech, field, openers),
   and the field grows. Flex order does the move, so the story's own markup and
   timing are not touched. Entering work mode raises the field the same way.

   Chosen over a beacon and a pair of doors on 2026-09-15; all three are drawn
   in designs/home-guide-field-directions.html.
   ------------------------------------------------------------------------ */
.kg[data-invite="1"] .kg-beam { order: 1; }
.kg[data-invite="1"] .kg-composer {
  order: 2;
  margin: 6px 22px 6px;
  padding: 17px 17px 17px 20px;
  border-color: rgba(77,141,255,.75);
  box-shadow:
    0 0 0 1px rgba(77,141,255,.3),
    0 0 56px -10px rgba(77,141,255,.6),
    inset 0 1px 0 rgba(255,255,255,.06);
  animation: kg-invite-rise .9s cubic-bezier(.22,1,.28,1) both;
}
.kg[data-invite="1"] .kg-composer .ph .t,
.kg[data-invite="1"] .kg-input { font-size: 17px; }
.kg[data-invite="1"] .kg-cw { height: 19px; }
.kg[data-invite="1"] .kg-chips { order: 3; padding-bottom: 16px; }
@keyframes kg-invite-rise { from { opacity: .4; transform: translateY(22px); } to { opacity: 1; transform: none; } }

/* The guide's own line to the visitor, a touch brighter than the story's. */
.kg-line[data-invite-line="1"] .txt { color: var(--ink); font-weight: 400; }

/* The two ways in, as chips: the same chip, a touch larger, lettered by what
   it leads to rather than numbered. */
.kg-chip.opener { font-size: 13px; padding: 9px 17px; }

/* ---------------------------------------------------------------------------
   THE KEEP CARD IN WORK MODE has its own room. Over the showcase it slides in
   across the answer slab, which is authored to make way for it. Over a
   visitor's drawing it covered the boxes (measured: the closed card's column
   at wide, the whole top of the stage at narrow), so in work mode the drawing
   gives it the column beside it at wide and the strip above it at narrow, and
   setKeep fits the drawing to what is left. --keep-h is the card's measured
   height plus its offset, set by the engine when the card opens.
   ------------------------------------------------------------------------ */
/* No transition on the inset: the engine reads the stage's size the moment
   the card opens and fits to it, and a size still on its way would be fitted
   wrong. The window onto the drawing is what glides, on its own spring. */
/* width/height auto, because the base rule's 100% would keep the full size
   and simply overflow the inset edge: measured, the svg stayed 787 wide under
   a 356px right inset, and 569 tall under a 353px top inset, and the engine
   fitted the drawing to a stage that had not changed. */
/* The card's room is taken by the engine now, in syncStageInset, alongside the
   ledger's. See .kg-svg above for why it cannot be two mechanisms. */
@container (max-width: 720px) {
  /* Not height:auto: an svg with a viewBox sizes itself by that ratio when
     its height is auto (measured 407 tall in a 216 strip) and ignores the
     edges. The height is said outright. */
  .kg[data-keep="1"]:not([data-keep-fit="alone"]) .kg-svg { top: var(--keep-h, 0px); height: calc(100% - var(--keep-h, 0px)); }
}
.kg-chip.opener .k { color: var(--cyan); }

/* A chip that does something to the drawing here, with no call: the fold, the
   unfold, the other star. Marked by the arrows rather than a number, because it
   is not a sentence to say. */
.kg-chip.act .k { color: var(--acc); }

/* The words said while a turn is in flight ("Reading that."). Quieter than a
   reply, because they are not one; the reply takes the line over in place. */
.kg-line[data-pending="1"] .txt { color: var(--ink-dim); font-weight: 300; }

@container (max-width: 720px) {
  .kg[data-invite="1"] .kg-composer { margin: 6px 14px 4px; padding: 13px 12px 13px 14px; }
  .kg[data-invite="1"] .kg-composer .ph .t,
  .kg[data-invite="1"] .kg-input { font-size: 15px; }
  .kg[data-invite="1"] .kg-cw { height: 16px; }
  .kg-chip.opener { font-size: 12px; padding: 8px 13px; }
}
@media (prefers-reduced-motion: reduce) {
  .kg[data-invite="1"] .kg-composer { animation: none; }
}
.kg[data-reduced="1"][data-invite="1"] .kg-composer { animation: none; }

/* A chip that leaves. Signal rather than accent, because it is not another
   sentence to say — it is the way out of the conversation, and it should not
   look like one of the three things you might say next. */
.kg-chip.go {
  border-color: rgba(245,163,0,.5);
  color: var(--ink);
  text-decoration: none;
}
.kg-chip.go .k { color: var(--signal); }
.kg-chip.go:hover, .kg-chip.go:focus-visible {
  border-color: var(--signal);
  box-shadow: 0 0 0 1px rgba(245,163,0,.22), 0 10px 26px -14px rgba(245,163,0,.6);
}

@media (max-width: 760px) {
  /* Narrow has less stage to give away, and the drawing is the thing that
     suffers first. Roughly four lines visible, the rest a scroll. */
  .kg[data-mode="work"] .kg-beam { max-height: 140px; }
}


/* ---------------------------------------------------------------------------
   REDUCED MOTION — not a downgrade to nothing. The story still tells; it
   just cuts between states instead of travelling between them. Springs are
   clamped to a snap in JS; here the ambient loops stop.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .kg-aurora, .kg-send::before { animation: none; }
  .kg-mark .dot { animation: none; }
  .kg-caret { animation: none; opacity: 1; }
  /* The field keeps its lift and its rim — those are static contrast and they
     are what makes it call out. Only the motion stops: the glow settles at the
     top of its breath and the caret stays solid instead of blinking. */
  .kg-composer[data-idle="1"] { animation: none; --glow: .85; }
  .kg-cw { animation: none; opacity: 1; }
  .kg-w { animation-duration: .01s; }
  /* The sweep is the only signal that a turn is in flight, so it stays — it
     just stops travelling and holds as a lit rim instead. */
  .kg-composer[data-busy="1"]::after {
    animation: none; background-size: 100% 100%; background-position: 0 0;
  }
  .kg *, .kg *::before, .kg *::after { transition-duration: .12s !important; }
}
/* the review rig can force the same path for comparison */
.kg[data-reduced="1"] .kg-aurora,
.kg[data-reduced="1"] .kg-send::before,
.kg[data-reduced="1"] .kg-mark .dot { animation: none; }
.kg[data-reduced="1"] .kg-composer[data-idle="1"] { animation: none; --glow: .85; }
.kg[data-reduced="1"] .kg-cw { animation: none; opacity: 1; }
.kg[data-reduced="1"] .kg-w { animation-duration: .01s; }
.kg[data-reduced="1"] .kg-composer[data-busy="1"]::after {
  animation: none; background-size: 100% 100%; background-position: 0 0;
}
.kg[data-reduced="1"] * { transition-duration: .12s !important; }


/* ===========================================================================
   THE PLINTH — the component ends on a light surface and the field lives on it.

   Lifted from designs/home-guide-layout.css, which is where it was drawn and
   looked at across eight screen shapes before any of it reached this file. The
   prototype scoped itself to [data-proto="plinth"]; here it is [data-surface],
   set once by the engine, so the tone is one attribute rather than a stylesheet
   to unpick.

   WHY A SURFACE AT ALL. Measured on the page as it was: a panel against the
   ground came out at 1.06:1 and the line meant to separate them at 1.20:1. There
   was nothing to see. You cannot segment in the basement, so the bottom third
   gets a floor to stand on and the field becomes the brightest object in the
   component instead of one more dark rectangle among several.

   WHAT STAYS DARK. The stage, the instrument bar and the whole ground. The
   drawing is the work and it keeps its own room; the light is only under the
   words. Absolute Zero reads 6.8:1 on this shelf and 2.5:1 on the dark ground,
   which is the whole reason the accent could never be ours until the surface
   moved with it.
   ======================================================================== */
.kg[data-surface="plinth"] {
  /* The page's own light rung, verbatim from home-terminal-press.css, so the
     component's light surface IS the page's light surface rather than a second
     opinion about what light means here. */
  --l-mist: #eaeef4;
  --l-paper: #f4f6f9;
  --l-ink: #131822;
  --l-soft: #3c4759;
  --l-dim: #4c586b;
  --l-line: #d3dae4;
  --l-line2: #c2cbd8;
  --brand: #0048ba; /* Absolute Zero. 6.8:1 on the plinth; 2.5:1 on the ground. */
}

/* ---------------------------------------------------------------------------
   THE PLINTH — the component ends on a light surface and the field lives on it.

   Frosted rather than opaque. The aurora, both parallax fields, the lens and the
   vignette keep running underneath and read as a shimmer through the glass,
   which is the whole reason this is a backdrop-filter and not a flat fill. It is
   the same device .kg-bar, .kg-composer, .kg-answer and .kg-keep already use,
   pointed the other way.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"] .kg-plinth {
  position: relative;
  z-index: 4;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  /* So a child that wants to be tall is clipped by its own cap rather than
     pushing this box open and taking the height out of the stage. */
  min-height: 0;
  background: rgba(234, 238, 244, 0.94);
  -webkit-backdrop-filter: blur(22px) saturate(1.08);
  backdrop-filter: blur(22px) saturate(1.08);
  color: var(--l-ink);
}

/* The invitation reorders the shell to speech, field, openers. The shipped rules put
   the beam at order 1 and the field and the openers at 2 and 3, and those still reach
   the field and the openers inside this wrapper, so the wrapper's own order is the one
   thing missing: measured, it defaulted to 0 and the whole plinth rose ABOVE the
   speech, which is the one arrangement the invitation must not produce. */
.kg[data-surface="plinth"][data-invite="1"] .kg-plinth { order: 2; }

/* `hidden` is display:none at the user agent's specificity, and .kg-receipt sets
   display:flex, so the attribute lost and 54px of the SAMPLE's receipt stayed in a
   card that has nothing of the visitor's to count yet. */
.kg[data-surface="plinth"] .kg-receipt[hidden] { display: none; }

/* The seam. The page settled this one already: at a step this size a hairline
   reads as a tear, so the crossing is a gradient. 56px there, 44 here, because
   the component's band is shorter than a page section. */
.kg[data-surface="plinth"] .kg-plinth::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -34px;
  height: 34px;
  background: linear-gradient(to bottom, rgba(7, 8, 11, 0), rgba(234, 238, 244, 0.94));
  pointer-events: none;
}

/* ---- the field, which is now the brightest object in the component ---- */

.kg[data-surface="plinth"] .kg-composer {
  background: #fff;
  border-color: var(--l-line2);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* --glow is carried through so kg-breathe still drives it. */
.kg[data-surface="plinth"] .kg-composer[data-idle="1"] {
  border-color: rgba(0, 72, 186, 0.55);
  background: linear-gradient(180deg, #fff, #f7f9fc);
  box-shadow:
    0 0 calc(24px + var(--glow) * 26px) -14px rgb(0 72 186 / calc(0.3 + var(--glow) * 0.34)),
    inset 0 1px 0 rgb(255 255 255 / 0.9);
}
.kg[data-surface="plinth"] .kg-composer[data-idle="1"]:hover {
  border-color: var(--brand);
  box-shadow:
    0 0 0 1px rgb(0 72 186 / 0.22),
    0 0 44px -14px rgb(0 72 186 / 0.55),
    inset 0 1px 0 rgb(255 255 255 / 0.9);
}
.kg[data-surface="plinth"] .kg-composer:focus-within {
  border-color: var(--brand);
  box-shadow: 0 0 0 1px rgba(0, 72, 186, 0.3), 0 0 36px -14px rgba(0, 72, 186, 0.6);
}
.kg[data-surface="plinth"] .kg-composer .ph .t,
.kg[data-surface="plinth"] .kg-input {
  color: var(--l-ink);
}
.kg[data-surface="plinth"] .kg-composer[data-idle="0"] .ph .t {
  color: var(--l-dim);
}
.kg[data-surface="plinth"] .kg-cw {
  background: var(--brand);
  box-shadow: none;
}
.kg[data-surface="plinth"] .kg-or {
  color: var(--l-dim);
}
.kg[data-surface="plinth"] .kg-send {
  background: var(--brand);
  color: #fff;
}
.kg[data-surface="plinth"] .kg-send:hover {
  background: #0053d6;
}

/* The invitation, on light. Same rise, same order flip, same growth. */
.kg[data-surface="plinth"][data-invite="1"] .kg-composer {
  border-color: var(--brand);
  box-shadow:
    0 0 0 1px rgba(0, 72, 186, 0.25),
    0 14px 44px -18px rgba(0, 72, 186, 0.55),
    inset 0 1px 0 rgb(255 255 255 / 0.9);
}

/* ---- the chips, on light ---- */

.kg[data-surface="plinth"] .kg-chip {
  background: #fff;
  border-color: var(--l-line2);
  color: var(--l-ink);
}
.kg[data-surface="plinth"] .kg-chip .k {
  color: var(--l-dim);
}
.kg[data-surface="plinth"] .kg-chip:hover,
.kg[data-surface="plinth"] .kg-chip:focus-visible {
  border-color: var(--brand);
  color: var(--brand);
}
/* The two ways in read as the doors they are. */
.kg[data-surface="plinth"] .kg-chip.opener {
  border-color: rgba(0, 72, 186, 0.45);
  color: var(--brand);
  font-weight: 600;
}
.kg[data-surface="plinth"] .kg-chip.opener .k {
  color: var(--brand);
}

/* ---------------------------------------------------------------------------
   THE BUDGET — the stage takes back the height, out of chrome and out of a beam
   that was never capped above 720px wide.

   The beam keeps every part of its character: bottom-anchored, the mask fade at
   its top edge, and the ageing that recedes each line as the next arrives. The
   cap clips the OLDEST line, already down at 16% opacity, which is exactly the
   reasoning the narrow container query has carried since it was written.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"] .kg-bar {
  padding: 10px 22px 9px;
}
/* :not([data-mode="work"]).
 *
 * This rule ties with `.kg[data-mode="work"] .kg-beam` on specificity and sits
 * 364 lines later, so it was winning every declaration the two share - and it
 * shares four. Work mode lost its `max-height: min(30%, 168px)` to a flat 112,
 * lost `overflow-y: auto` to `hidden`, and lost its 8px bottom padding to 30.
 *
 * The overflow is the one that mattered most and the one nobody would have
 * noticed: a conversation culls nothing, so every line stays in the DOM, and
 * with the beam clipped to 112px and no scrollbar the older turns were still
 * there and simply unreachable. Measured in work mode: the beam rendered at 96px
 * with two lines visible and no way back to the rest.
 *
 * So the plinth dresses the showcase's beam and leaves the conversation's alone.
 */
.kg[data-surface="plinth"]:not([data-mode="work"]) .kg-beam {
  min-height: 96px;
  /* 150, not 112. The showcase's star draws 300 units into a 405px stage and
     needs 347 of them to hold its floor, so there are 58px spare and this takes
     38 of them - one more line of transcript, which is what was missing. */
  max-height: 150px;
  overflow: hidden;
  /* The bottom padding is the seam's clearance, not decoration. The beam is
     bottom-anchored, so its NEWEST and brightest line sits exactly where the
     crossing into the plinth begins: looked at on the laptop frame, the live line
     was being washed by the gradient. The padding lifts it clear. It costs nothing
     from the stage, because the cap above fixes the beam's outer height either way;
     what gives is a little more of the oldest line, already down at 16%. */
  padding: 12px 26px 30px;
}
.kg[data-surface="plinth"] .kg-line .txt {
  font-size: 17px;
}
/* The narrow container query sets 15.5px and 16px of side padding on the beam,
   and both were losing to the plinth rules above on specificity - so a phone was
   reading desktop type in desktop gutters. Said again at a weight that wins. */
@container (max-width: 720px) {
  .kg[data-surface="plinth"] .kg-line .txt { font-size: 15.5px; }
  .kg[data-surface="plinth"]:not([data-mode="work"]) .kg-beam {
    padding: 12px 16px 24px;
    min-height: 108px;
    max-height: 132px;
  }
  .kg[data-surface="plinth"][data-mode="work"] .kg-beam { padding: 12px 16px 8px; }
}
.kg[data-surface="plinth"] .kg-chips {
  padding: 10px 26px 4px;
  min-height: 42px;
}
.kg[data-surface="plinth"] .kg-composer {
  margin: 4px 22px 14px;
}
.kg[data-surface="plinth"][data-invite="1"] .kg-composer {
  margin: 4px 22px 10px;
}
.kg[data-surface="plinth"][data-invite="1"] .kg-chips {
  padding-bottom: 14px;
}

/* ---------------------------------------------------------------------------
   THE CARD — the one pale thing in the theatre, so the signup is the second
   brightest object after the field.

   And one invitation at a time. The component's own rule is that the warm signal
   is reserved and never decorative, so while the field is being offered the card
   does not put a signal-coloured button next to it for the same click. The warm
   returns at data-keep-state="keep", when there is something of theirs to hold.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"] .kg-keep {
  background: linear-gradient(160deg, #fff, var(--l-paper));
  border-color: var(--l-line2);
  color: var(--l-ink);
  box-shadow: 0 30px 80px -28px rgba(0, 0, 0, 0.75), 0 0 60px -26px rgba(0, 72, 186, 0.35);
}
.kg[data-surface="plinth"] .kg-keep .eyebrow {
  color: var(--l-dim);
}
.kg[data-surface="plinth"] .kg-keep h4 {
  color: var(--l-ink);
}
.kg[data-surface="plinth"] .kg-keep .body {
  color: var(--l-soft);
}
.kg[data-surface="plinth"] .kg-keep .body b {
  color: var(--l-ink);
}
.kg[data-surface="plinth"] .kg-keep .fine {
  color: var(--l-dim);
}
.kg[data-surface="plinth"] .kg-keep-go {
  background: #fff;
  color: var(--brand);
  border: 1px solid rgba(0, 72, 186, 0.5);
}
.kg[data-surface="plinth"] .kg-keep-go:hover {
  background: var(--brand);
  color: #fff;
}
.kg[data-surface="plinth"] .kg-keep-later {
  border-color: var(--l-line2);
  color: var(--l-dim);
}
.kg[data-surface="plinth"] .kg-keep-later:hover {
  border-color: var(--l-soft);
  color: var(--l-ink);
}

/* Their own star exists. The warm comes back, and it is the only warm thing. */
.kg[data-surface="plinth"][data-keep-state="keep"] .kg-keep {
  border-color: var(--acc);
  box-shadow: 0 30px 80px -28px rgba(0, 0, 0, 0.8), 0 0 70px -22px rgba(245, 163, 0, 0.45);
}
.kg[data-surface="plinth"][data-keep-state="keep"] .kg-keep .eyebrow {
  color: #a66a00;
}
.kg[data-surface="plinth"][data-keep-state="keep"] .kg-keep-go {
  background: var(--signal);
  color: #05060a;
  border-color: var(--signal);
}
.kg[data-surface="plinth"][data-keep-state="keep"] .kg-keep-go:hover {
  background: #ffb92a;
  color: #05060a;
}

/* ---------------------------------------------------------------------------
   THE CARD READS THE SPACE TOO.

   Three placements, chosen from the stage rather than from a width guess, because
   the same rule that governs the drawing should govern what sits on top of it.

     column   the stage is wide enough to give the card 356px and still leave the
              drawing room for a star. This is the arrangement today.
     compact  the stage is short. Same card, tighter: measured, the full one is
              259px and a tablet's stage is 251, so something has to give and it
              is the spacing, not the sentence.
     alone    the stage cannot hold both at any size. The card takes the stage and
              the drawing waits behind it. This is the same call the component
              already makes when the answer slab retreats the moment keep opens:
              at the keep beat, ownership is the beat, and on a small stage one
              thing at a time is the only honest arrangement.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-keep { padding: 13px 15px 12px; }
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-keep .eyebrow { margin-bottom: 6px; }
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-keep h4 { font-size: 18px; margin-bottom: 6px; }
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-keep .body { font-size: 12px; line-height: 1.5; margin-bottom: 10px; }
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-receipt { padding: 7px 9px; margin-bottom: 10px; }
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-keep-go { padding: 10px 12px; }
.kg[data-surface="plinth"]:is([data-keep-fit="compact"],[data-keep-fit="alone"]) .kg-keep-act { margin-bottom: 7px; }

.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-keep {
  top: 50%;
  left: 50%;
  right: auto;
  width: min(420px, calc(100% - 40px));
  padding: 15px 17px 14px;
  /* Both states, not just the open one. The prototype paints still frames, so it
     only ever needed where the card ENDS; on the page the card travels, and a
     placement that sets transform without saying where it comes from outranks
     `.kg-keep[data-on="1"] { transform: none }` and the card stops sliding at
     all. Same journey as the other two placements, from below. */
  transform: translate(-50%, calc(50% + 40px));
}
.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-keep[data-on="1"] {
  transform: translate(-50%, -50%);
}
.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-keep h4 { font-size: 19px; }
/* The drawing waits rather than disappears: it is the thing being offered. */
.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-svg { opacity: .22; }

.kg[data-surface="plinth"] .kg-svg { transition: opacity .5s ease; }

.kg[data-surface="plinth"] .kg-receipt {
  border-color: var(--l-line);
  background: rgba(255, 255, 255, 0.7);
}
.kg[data-surface="plinth"] .kg-receipt .meta .a {
  color: #0f7a46;
}
.kg[data-surface="plinth"] .kg-receipt .meta .b,
.kg[data-surface="plinth"] .kg-receipt .meta .c {
  color: var(--l-soft);
}
.kg[data-surface="plinth"] .kg-receipt .meta .c b {
  color: var(--l-ink);
}


/* ---------------------------------------------------------------------------
   THE TRANSCRIPT, ON LIGHT.

   The prototype resized this text and never recoloured it, because a still frame
   of a light plinth with near-white words on it still reads as a light plinth.
   On the page it read as nothing at all: the guide's line is #dfe6f2 and the
   plinth is #eaeef4, which is 1.05:1. The conversation was invisible.

   Every colour here is the dark one's counterpart, and the ageing keeps its
   shape - each line recedes as the next arrives - but not its numbers. On dark,
   fading toward the ground means fading toward black and 16% still leaves a
   grey. On light it means fading toward white, which reaches nothing much
   sooner, so the two aged steps are lifted. They are still the quietest things
   in the component and the oldest is still what the beam's cap clips.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"] .kg-line .who { color: var(--l-dim); }
.kg[data-surface="plinth"] .kg-line[data-who="you"] .txt { color: var(--l-ink); }
/* The visitor's own turn keeps a mark of its own. Cyan is 1.5:1 here, so it is
   the same hue taken down to where it can be read. */
.kg[data-surface="plinth"] .kg-line[data-who="you"] .who { color: #0e7d72; }
.kg[data-surface="plinth"] .kg-line[data-who="guide"] .txt { color: var(--l-soft); }
.kg[data-surface="plinth"] .kg-line[data-invite-line="1"] .txt { color: var(--l-ink); }
.kg[data-surface="plinth"] .kg-em {
  color: var(--l-ink);
  box-shadow: inset 0 -2px 0 rgba(0, 72, 186, .38);
}
/* Also kept out of work mode, and for a stranger reason. `.kg[data-mode="work"]
   .kg-line` says nothing ages out of a conversation, and these two beat it for
   ages 1 and 2 while ages 3 and up fell through to it - so a conversation showed
   the newest line bright, the two before it at .52 and .26, and everything older
   than that back at full strength. A gradient that stops and then reverses.

   In the showcase, where lines really do age away, .30 rather than .26 for the
   oldest: it has to recede without becoming decoration, and it is the line a
   visitor is most likely to still be reading. */
.kg[data-surface="plinth"]:not([data-mode="work"]) .kg-line[data-age="1"] { opacity: .60; }
.kg[data-surface="plinth"]:not([data-mode="work"]) .kg-line[data-age="2"] { opacity: .30; }
.kg[data-surface="plinth"]:not([data-mode="work"]) .kg-line[data-age="3"] { opacity: .15; }

/* ---------------------------------------------------------------------------
   THE FIELD STAYS AWAKE.

   It used to drop to 62% with its caret hidden for the whole 30 seconds the
   story runs. Clicking it worked the entire time and took over; nothing said so.
   Somebody who arrives knowing what they want had no way in but to wait.

   So while the story plays the field is quieter, not asleep: it keeps the caret,
   it keeps a rim, and it keeps a fraction of the glow. The breathing stays where
   it was, on the resting state after the story, so the difference between "you
   may" and "your turn" survives - it is a difference of degree now rather than
   of presence.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"] .kg-composer[data-idle="0"] { opacity: .92; }
.kg[data-surface="plinth"] .kg-composer[data-idle="0"] .kg-cw { display: inline-block; }
.kg[data-surface="plinth"] .kg-composer[data-idle="0"] {
  border-color: rgba(0, 72, 186, .3);
  box-shadow: 0 0 22px -16px rgba(0, 72, 186, .5);
}
.kg[data-surface="plinth"] .kg-composer[data-idle="0"]:hover {
  border-color: var(--brand);
  cursor: text;
}

/* ---------------------------------------------------------------------------
   THE BUSY SWEEP, ON LIGHT.

   It travels a 2px band of --acc and --cyan along the field's bottom rim, and
   both of those were picked against a near-black composer: on white they are a
   pale smear. Same motion, same 1.15s, same travel - Absolute Zero instead,
   which is the one colour that reads on this surface.

   The send button's spinning rim is left alone deliberately. It sits BEHIND a
   button that is already Absolute Zero on white, so it reads as a ring around a
   dark object rather than a light shape on a light field, and it is judged by
   looking at it rather than by assuming.
   ------------------------------------------------------------------------ */
.kg[data-surface="plinth"] .kg-composer[data-busy="1"]::after {
  background-image: linear-gradient(90deg,
    rgba(0, 72, 186, 0), rgba(0, 72, 186, .85), rgba(14, 125, 114, .7), rgba(0, 72, 186, 0));
}

/* The field stops deferring to the chips at the moment it becomes the offer. The
   label points at two chips that lead straight back into the field, so at the
   invitation it is the field asking you to ask somewhere else. */
.kg[data-invite="1"] .kg-or { display: none; }

/* `alone` only ever happens on a stage that could not hold both, so the card is
   on a short stage by definition and takes the tightening above with it. Without
   it the card overflowed its own max-height and put "Build yours" below its own
   fold behind a hairline scrollbar - the one control the whole beat exists for,
   hidden by the arrangement meant to give it room. */
/* Still 58px too tall for a 262px stage after the tightening, and what was
   falling off the bottom was the button. So the card sheds the two lines that
   are reassurance rather than action: the fine print says the field below is
   theirs and nothing is asked for yet, which the field itself is saying six
   inches away on a light plinth, and the receipt's third line repeats the count
   above it. The heading, what they built, and the button stay. */
.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-keep .fine { display: none; }
.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-keep .body { margin-bottom: 8px; }

/* THE CARD DOES NOT SCROLL. A scrollbar on a card this size is the interface
   admitting it did not fit, and it puts the one control the beat exists for
   behind a gesture. So it sheds instead, in order of what the beat needs least,
   and the belt is `hidden` rather than `auto` so nothing can quietly grow a
   scrollbar again.

   The SAMPLE's receipt goes first: it counts the practice, which is not theirs,
   and the sentence above it has already said a practice became a model and a
   star. Once they have built their own, the receipt counts THEIR star and is the
   most load-bearing thing on the card, so it stays and the heading takes the
   trim instead. */
/* No card scrolls, in any placement. A scrollbar here is the interface admitting
   it did not fit, and it puts the one control the beat exists for behind a
   gesture. `hidden` is the belt; the ladder in fitKeep is what actually makes it
   fit, by stepping the card down until it stops wanting to be taller. */
.kg[data-surface="plinth"] .kg-keep { overflow: hidden; }
.kg[data-surface="plinth"][data-keep-fit="alone"]:not([data-keep-state="keep"]) .kg-receipt { display: none; }
.kg[data-surface="plinth"][data-keep-fit="alone"][data-keep-state="keep"] .kg-keep h4 { font-size: 17px; }
.kg[data-surface="plinth"][data-keep-fit="alone"][data-keep-state="keep"] .kg-receipt { padding: 6px 8px; margin-bottom: 8px; }
.kg[data-surface="plinth"][data-keep-fit="alone"] .kg-keep-act { margin-bottom: 0; }

/* ---------------------------------------------------------------------------
   THE READING ON THE LINE

   Every line in the graph beat is one sentence with its constraints attached,
   and until now the beat drew the constraints and not the sentence. The word
   rides the line's midpoint with a stroke of the ground painted behind it, so it
   sits ON the line rather than being crossed out by it.

   The chevron says which end is the subject without touching the words: the
   reading is drawn exactly as authored, in the direction it was authored in, and
   this points from its subject to its object.
   ------------------------------------------------------------------------ */
.kg .kg-read {
  fill: var(--ink-soft);
  font-family: var(--mono); font-size: 10px; letter-spacing: .04em;
  text-anchor: middle; dominant-baseline: middle;
  paint-order: stroke; stroke: var(--bg); stroke-width: 5; stroke-linejoin: round;
  transition: opacity .4s ease, fill .25s ease;
  pointer-events: none;
}
.kg .kg-way {
  fill: none; stroke: var(--ink-dim); stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  transition: opacity .3s ease, stroke .25s ease;
  pointer-events: none;
}
/* A 1.4-unit line is not a target. The hit line is, and it is invisible rather
   than absent so the sentence can be reached at any scale. */
.kg .kg-hit { stroke: transparent; stroke-width: 20; fill: none; cursor: pointer; }
/* The drawn line is 1.4 units wide and paints over the hit line, so without this
   a pointer exactly on the line would land on a stroke that is not a target and
   the sentence would fail to come, in the one place a visitor is most likely to
   aim. */
.kg .edge { pointer-events: none; }
.kg .kg-hit:hover + .kg-read,
.kg .kg-hit[data-open="1"] + .kg-read { fill: var(--acc); }
.kg .kg-hit:hover ~ .kg-way { stroke: var(--acc); }

/* ---------------------------------------------------------------------------
   THE DETAIL PANEL

   Chrome over the stage, because the boxes are the model: one that grows to hold
   its detail says this dimension is a bigger thing than the one beside it, and a
   grown box enlarges what the camera must fit, so the whole drawing shrinks the
   moment a cursor touches it. Measured that way first: x1.67 down to x1.34.

   The same material as the keep card, so the surface has two chrome objects
   rather than five.
   ------------------------------------------------------------------------ */
.kg-panel {
  position: absolute; z-index: 7; left: 0; top: 0;
  width: 216px; max-height: calc(100% - 16px); overflow: hidden;
  padding: 11px 13px 12px;
  border: 1px solid var(--l-line2, #c2cbd8);
  background: linear-gradient(160deg, #fff, var(--l-paper, #f4f6f9));
  color: var(--l-ink, #131822);
  box-shadow: 0 18px 44px -18px rgba(0,0,0,.9);
  opacity: 0; transform: translateY(4px);
  transition: opacity .18s ease, transform .18s cubic-bezier(.22,1,.28,1);
  pointer-events: none;
}
.kg-panel[data-on="1"] { opacity: 1; transform: none; }
.kg-panel .k {
  font-family: var(--mono); font-size: 9px; letter-spacing: .15em;
  text-transform: uppercase; color: var(--l-dim, #4c586b); margin-bottom: 5px;
}
.kg-panel .n { font-size: 15px; font-weight: 700; letter-spacing: -.01em; margin-bottom: 8px; }
.kg-panel ul { list-style: none; margin: 0; padding: 0; }
.kg-panel li {
  font-family: var(--mono); font-size: 11px; line-height: 1.7;
  color: var(--l-soft, #3c4759);
  border-top: 1px solid rgba(0,0,0,.07); padding-top: 3px; margin-top: 3px;
}
.kg-panel li:first-child { border-top: 0; margin-top: 0; padding-top: 0; }

/* The stacked edge: a box holding something it is not showing. Marked whenever
   there is something to reveal, and never when the box is already showing it
   all, so the mark is a promise the panel keeps. */
.kg .kg-more {
  stroke: #5a6879; stroke-width: 1.6; stroke-linecap: round;
  transition: opacity .3s ease, stroke .25s ease;
  pointer-events: none;
}
.kg [data-node]:hover .kg-more { stroke: var(--acc); }

@media (prefers-reduced-motion: reduce) {
  .kg-panel { transition: none; }
  .kg .kg-more { transition: none; }
}

/* ---------------------------------------------------------------------------
   THE STAGE, WAITING.

   Work mode used to keep the dental drawing on the stage, so the moment the page
   handed over was the moment it looked most like nothing had happened. The
   sample leaves now, and this stands in its place until the visitor's own first
   sentence has something to draw.

   Modelled on the rest panel and deliberately quieter than it: that one is the
   page introducing itself and this one is a page waiting, which is a smaller
   thing to say. pointer-events stay off - the field below is the affordance and
   nothing here should take a click away from it.
   ------------------------------------------------------------------------ */
.kg-wait {
  position: absolute; inset: 0; z-index: 4;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 0 30px;
  opacity: 0; transform: translateY(6px);
  transition: opacity .5s ease, transform .6s cubic-bezier(.22,1,.28,1);
  pointer-events: none;
}
.kg-wait[data-on="1"] { opacity: 1; transform: none; }
.kg-wait .kicker {
  font-family: var(--mono); font-size: 10px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--acc); margin-bottom: 14px;
}
.kg-wait .say {
  font-size: 27px; font-weight: 300; letter-spacing: -.015em;
  color: var(--ink); line-height: 1.24; max-width: 19ch;
}
.kg-wait .eg {
  margin-top: 13px; font-size: 13.5px; color: var(--ink-dim);
  max-width: 40ch; line-height: 1.5;
}
@container (max-width: 720px) {
  .kg-wait .say { font-size: 22px; }
  .kg-wait .eg { font-size: 12.5px; }
}
/* Mirrored for [data-reduced="1"] the way every other reduced rule in this file
   is, because the review rig forces the flag on the root rather than changing the
   OS setting, and a rule that only lives in the media query is a rule the rig
   cannot see. */
@media (prefers-reduced-motion: reduce) {
  .kg-wait { transition: none; }
}
.kg[data-reduced="1"] .kg-wait { transition: none; }

/* ---------------------------------------------------------------------------
   PLAY IT AGAIN

   The story runs once on first sight and there was no way back to it short of
   reloading. Quiet, at the end of the instrument bar, in the bar's own type: it
   is a way back, not an invitation, and the field below is still the only thing
   asking for a click.
   ------------------------------------------------------------------------ */
.kg-replay {
  flex-shrink: 0;
  display: flex; align-items: center; gap: 7px;
  padding: 5px 11px 5px 9px;
  border: 1px solid var(--line);
  background: rgba(14,17,24,.6);
  color: var(--ink-dim);
  font-family: var(--mono); font-size: 10px; letter-spacing: .13em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .25s ease, border-color .25s ease, background .25s ease;
}
.kg-replay .gl { font-size: 12px; line-height: 1; transition: transform .45s cubic-bezier(.22,1,.28,1); }
.kg-replay:hover, .kg-replay:focus-visible { color: var(--ink); border-color: var(--line-2); background: rgba(20,24,33,.8); }
.kg-replay:hover .gl { transform: rotate(-180deg); }
.kg-replay:active { transform: translateY(1px); }

/* From a conversation it is a replacement, not a repeat, and it says so. */
.kg[data-mode="work"] .kg-replay .tx::after { content: " the example"; }

@container (max-width: 720px) {
  /* The glyph alone. The bar is tight at this width and a turning arrow beside
     two named destinations is unambiguous. */
  .kg-replay .tx { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .kg-replay { padding: 5px 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .kg-replay .gl, .kg-replay:hover .gl { transition: none; transform: none; }
}
.kg[data-reduced="1"] .kg-replay .gl,
.kg[data-reduced="1"] .kg-replay:hover .gl { transition: none; transform: none; }
