/* ==========================================================================
   aiRA — Component library and visual language.

   THE SIGNATURE
   Three motifs carry aiRA's identity, all derived from the product itself
   (a question, a reasoning trace, a move awaiting approval) and from the
   logo's four-point sparkle:

     1. .rule    a hairline whose left terminus is a sparkle. Sections are
                 separated by a mark, not a border. Asymmetric and directional,
                 so the page reads left to right, top to bottom.
     2. .thread  a vertical hairline with sparkle nodes. This is the reasoning
                 trace made visual: a sequence of steps, each one anchored.
     3. .qstring a real question set in mono, quoted, with a brand left mark.
                 Questions are the product's surface, so they are a first-class
                 typographic object rather than body copy.

   Nothing here uses the dashed double-rail system from the base export.
   That is another product's signature.
   ========================================================================== */

/* ---- 1. Sparkle rule --------------------------------------------------- */

.rule {
  position: relative;
  height: 1px;
  background: var(--border);
  border: 0;
  margin: 0;
}
.rule::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 9px;
  height: 9px;
  transform: translate(-1px, -50%);
  background: var(--brand);
  /* the sparkle, as a mask so it inherits the brand token */
  -webkit-mask: var(--sparkle-mask) center / contain no-repeat;
  mask: var(--sparkle-mask) center / contain no-repeat;
}
.rule--center::before { left: 50%; transform: translate(-50%, -50%); }
.rule--muted::before { background: var(--slate-300); }
.section--inv .rule { background: rgba(255, 255, 255, 0.12); }
.section--inv .rule::before { background: var(--primary-300); }

:root {
  --sparkle-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12,0Q14.6,9.4 24,12Q14.6,14.6 12,24Q9.4,14.6 0,12Q9.4,9.4 12,0Z'/%3E%3C/svg%3E");
}

/* Section top rule: sits flush on the section's leading edge. */
.section > .rule:first-child,
.section-rule {
  position: absolute;
  top: 0;
  left: var(--pad-x);
  right: var(--pad-x);
  width: auto;
}

/* ---- 2. Thread --------------------------------------------------------- */

.thread {
  display: grid;
  gap: var(--sp-8);
  position: relative;
  padding-left: var(--sp-8);
}
.thread::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: linear-gradient(
    to bottom,
    var(--primary-200) 0%,
    var(--border-strong) 58%,
    transparent 100%
  );
}
.thread__step { position: relative; }
.thread__step::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--sp-8) + 0px);
  top: 5px;
  width: 11px;
  height: 11px;
  background: var(--brand);
  -webkit-mask: var(--sparkle-mask) center / contain no-repeat;
  mask: var(--sparkle-mask) center / contain no-repeat;
}
.thread__step:not(:first-child)::before { background: var(--slate-400); }

.thread__label {
  font-family: var(--font-display);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}
.thread__title {
  font-family: var(--font-display);
  font-size: var(--t-title-sm);
  font-weight: 500;
  letter-spacing: var(--tr-title);
  color: var(--ink);
  margin-bottom: 5px;
}
.thread__body {
  font-size: var(--t-body-sm);
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 32rem;
}

.section--inv .thread__title { color: var(--ink-inv); }
.section--inv .thread__body { color: var(--ink-inv-2); }
.section--inv .thread__label { color: var(--ink-inv-3); }
.section--inv .thread::before {
  background: linear-gradient(to bottom, rgba(139,157,249,0.5), rgba(255,255,255,0.10) 55%, transparent);
}
.section--inv .thread__step:not(:first-child)::before { background: rgba(255, 255, 255, 0.30); }

/* The thread tells its story in sequence: the hairline draws downward and
   each step lights up as the line reaches it, node first, text after. The
   hidden state is gated on a .reveal ancestor that has not yet gained
   .is-in (js/reveal.js), so a thread outside any reveal wrapper, or with
   reduced motion or no JS, simply sits complete. */
.thread::before {
  transform-origin: top;
  transition: transform 1.1s var(--ease) 0.1s;
}
.thread__step {
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.thread__step::before {
  transition: transform 0.4s var(--ease);
  transition-delay: inherit;
}
.thread__step:nth-child(1) { transition-delay: 0.15s; }
.thread__step:nth-child(2) { transition-delay: 0.5s; }
.thread__step:nth-child(3) { transition-delay: 0.85s; }
.thread__step:nth-child(4) { transition-delay: 1.2s; }

.reveal:not(.is-in) .thread::before { transform: scaleY(0); }
.reveal:not(.is-in) .thread__step { opacity: 0; transform: translateY(8px); }
.reveal:not(.is-in) .thread__step::before { transform: scale(0) rotate(-90deg); }

@media (prefers-reduced-motion: reduce) {
  .thread::before,
  .thread__step,
  .thread__step::before { transition: none; }
  .reveal:not(.is-in) .thread::before { transform: none; }
  .reveal:not(.is-in) .thread__step { opacity: 1; transform: none; }
  .reveal:not(.is-in) .thread__step::before { transform: none; }
}

/* ---- Annotation mark ----------------------------------------------------
   A short label with a drawn arrow, pointed at one control inside a figure.
   The sendr.ai reference the user sent on 22 Sep sets its version in a
   HANDWRITING face; this one is Geist Mono, because Caveat was retired from
   the project on 17 Sep ("basically we are not using caveat typeface in this
   project") and mono is already permitted inside drawn illustrations, which
   is exactly what this is. It reads as a margin note either way.

   Absolutely positioned, so the figure it sits on needs position: relative.
   Hidden below 992: there is no room beside a figure that has gone full
   width, and an annotation overlapping the thing it annotates is worse than
   no annotation.
   ------------------------------------------------------------------------- */
.mark {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  pointer-events: none;
}
.mark__t {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  line-height: 1.35;
  color: var(--ink-3);
  max-width: 14ch;
  text-align: right;
}
.mark__arrow {
  flex: none;
  width: 46px;
  height: 34px;
  color: var(--ink-faint);
}
/* Mirrored for a mark that sits to the LEFT of its subject. */
.mark--flip { flex-direction: row-reverse; }
.mark--flip .mark__arrow { transform: scaleX(-1); }

@media (max-width: 991px) {
  .mark { display: none; }
}

/* ---- 3. Question string ------------------------------------------------ */

.qstring {
  position: relative;
  padding-left: var(--sp-4);
  /* Inter since 15 Sep: mono is confined to eyebrows and the drawn
     illustrations. The brand bar stays; it is the mark that makes a
     question a question. */
  font-family: var(--font-sans);
  font-size: var(--t-body-sm);
  font-weight: 500;
  line-height: 1.62;
  color: var(--ink);
}
.qstring::before {
  content: '';
  position: absolute;
  left: 0;
  top: 3px;
  bottom: 3px;
  width: 2px;
  border-radius: 2px;
  background: var(--brand);
}
.qstring--muted { color: var(--ink-2); }
.qstring--muted::before { background: var(--slate-300); }
.section--inv .qstring { color: var(--ink-inv); }

/* ---- Micro-label ---------------------------------------------------------
   The class name is historical: these were Geist Mono until 15 Sep, when
   mono was confined to eyebrows and illustration. Caps labels are now
   the display face at 600. */

.mono {
  font-family: var(--font-display);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.mono--brand { color: var(--brand-ink); }
.mono--faint { color: var(--ink-faint); }
.mono--num { text-transform: none; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; }
/* For labels that contain the product name: aiRA must never render AIRA. */
.mono--case { text-transform: none; }

/* ---- Cards -------------------------------------------------------------
   One card shell, three intents. Cards do not lift on hover; they settle.
   ---------------------------------------------------------------------- */

.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-6);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              transform var(--dur-slow) var(--ease);
}
.card__title {
  font-size: var(--t-title);
  font-weight: 700;
  letter-spacing: var(--tr-title);
  color: var(--ink);
  margin-bottom: var(--sp-2);
  text-wrap: balance;
}
.card__body {
  font-size: var(--t-body-sm);
  line-height: 1.68;
  color: var(--ink-2);
  text-wrap: pretty;
}
.card__body strong { color: var(--ink); font-weight: 600; }
.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

/* Interactive card: an anchor wrapping the shell. */
a.card, .card--link { display: block; }
a.card:hover, .card--link:hover {
  border-color: var(--brand-line);
  box-shadow: var(--shadow-md);
}

/* Numbered argument card. The index is a bare figure, not a circle badge. */
.card--argued { padding-top: var(--sp-5); }
.card__index {
  font-family: var(--font-sans);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--brand);
  margin-bottom: var(--sp-4);
  display: block;
}

/* Quiet card: no shadow, for dense grids. */
.card--quiet { box-shadow: none; background: var(--bg-subtle); }
.card--quiet:hover { box-shadow: none; }

/* Card on a dark section. */
.section--inv .card {
  background: rgba(255, 255, 255, 0.035);
  border-color: rgba(255, 255, 255, 0.10);
  box-shadow: none;
}
.section--inv .card__title { color: var(--ink-inv); }
.section--inv .card__body { color: var(--ink-inv-2); }
.section--inv .card__body strong { color: var(--ink-inv); }
.section--inv a.card:hover, .section--inv .card--link:hover {
  border-color: rgba(139, 157, 249, 0.45);
  background: rgba(255, 255, 255, 0.06);
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-5);
}
.card-grid--2 { grid-template-columns: repeat(2, 1fr); }
.card-grid--4 { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 991px) {
  .card-grid, .card-grid--4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
  .card-grid, .card-grid--2, .card-grid--4 { grid-template-columns: 1fr; }
}

/* ---- Chips and pills --------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: var(--t-body-sm);
  font-weight: 550;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  padding: 8px 15px;
  transition: border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.chip .sparkle { width: 10px; height: 10px; color: var(--brand); flex: none; }
button.chip, a.chip { cursor: pointer; }
button.chip:hover, a.chip:hover { border-color: var(--brand-line); color: var(--ink); }
.chip[aria-pressed="true"], .chip.is-active {
  background: var(--brand); border-color: transparent; color: var(--on-brand);
}
.chip[aria-pressed="true"] .sparkle, .chip.is-active .sparkle { color: var(--on-brand); }

.chip-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Eyebrow badge: the pill that sits above a hero headline. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-ink);
  background: var(--surface);
  border: 1px solid var(--brand-line);
  border-radius: var(--r-pill);
  padding: 7px 14px 7px 11px;
  box-shadow: var(--shadow-xs);
}
.badge .sparkle { width: 11px; height: 11px; flex: none; }

/* ---- Approval grammar --------------------------------------------------
   aiRA proposes, a person decides. Every proposed move carries its state.
   ---------------------------------------------------------------------- */

.state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.075em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
  padding: 4px 9px;
  white-space: nowrap;
}
.state::before {
  content: '';
  width: 5px; height: 5px; border-radius: 50%;
  background: currentColor;
  flex: none;
}
.state--pending { color: var(--pending); background: var(--pending-bg); }
.state--approve { color: var(--approve); background: var(--approve-bg); }
.state--hold    { color: var(--hold);    background: var(--hold-bg); }
.state--reject  { color: var(--reject);  background: var(--reject-bg); }

/* ---- Panel: CSS-drawn product surface ---------------------------------
   The house illustration style. Product UI is drawn in CSS, never
   screenshotted, so it stays crisp, themeable and editable.
   ---------------------------------------------------------------------- */

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.panel__bar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 11px 14px;
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
  position: relative;
}
/* Panel identity: the sparkle and the product's name, left-aligned.
   Not macOS traffic lights; this is aiRA's surface, not a browser mock. */
.panel__app {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: var(--tr-mono);
  /* no text-transform: the product name is always aiRA, never AIRA */
  color: var(--ink);
}
.panel__app .sparkle { width: 11px; height: 11px; color: var(--brand); flex: none; }
.panel__meta {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-faint);
}
.panel__name {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-faint);
}
.panel__body { padding: var(--sp-5); }
.panel__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--bg-subtle);
}

/* Decision buttons: flat tinted pills, no border. */
.decide { display: flex; gap: 6px; }
.decide button {
  font-size: var(--t-caption);
  font-weight: 600;
  border-radius: var(--r-pill);
  padding: 6px 13px;
  transition: filter var(--dur-fast) var(--ease);
}
.decide button:hover { filter: brightness(0.96); }
.decide__approve { color: var(--approve); background: var(--approve-bg); }
.decide__hold    { color: var(--hold);    background: var(--hold-bg); }
.decide__reject  { color: var(--reject);  background: var(--reject-bg); }

/* ---- Dot field: a quiet stage behind illustrations --------------------- */

/* NEVER PUT .dot-field ON A .section. It paints into ::before, and on a
   section that pseudo is already the page frame's LEFT RAIL: the texture
   lands inside a 1px strip and the inset:0 here drags the rail from the
   container edge to x=0. Invisible texture plus a broken frame, from one
   class, with no error anywhere (28 Sep, /about). For a whole section, give
   it a ground ELEMENT instead, the way .abhero and .whatis do. This
   component is for a well or a card, where ::before is free. */
.dot-field { position: relative; }
.dot-field::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle, var(--frame-grid) 1px, transparent 1px);
  /* 20, not the 18 this shipped with. A decorative layer inside the page
     frame has to use a pitch that DIVIDES --cell (80px, which is
     --section-pad), or it meets the frame's band lines at a different place
     on every column and the whole frame reads as broken. 18 does not divide
     80; 20 does. Safe to change on 28 Sep because nothing had ever placed
     this component: /about's "What is aiRA" section is its first user.
     The ink moved to --frame-grid in the same pass, since a hard-coded rgba
     does not follow the dark theme and this ground is now reachable on a
     page that could go dark. */
  background-size: 20px 20px;
  -webkit-mask-image: radial-gradient(ellipse 78% 78% at 50% 42%, #000 28%, transparent 76%);
  mask-image: radial-gradient(ellipse 78% 78% at 50% 42%, #000 28%, transparent 76%);
}
.section--inv .dot-field::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

/* ---- Comparison table -------------------------------------------------- */

.cmp-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.cmp {
  min-width: 44rem;
  font-size: var(--t-body-sm);
}
.cmp th, .cmp td {
  text-align: left;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.cmp thead th {
  font-family: var(--font-display);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom-color: var(--border-strong);
  white-space: nowrap;
}
.cmp thead th.is-aira { color: var(--brand-ink); }
.cmp tbody th {
  font-weight: 600;
  color: var(--ink);
  width: 12rem;
}
.cmp td { color: var(--ink-2); line-height: 1.6; }
.cmp td.is-aira { color: var(--ink); background: var(--primary-50); }
.cmp tbody tr:last-child th, .cmp tbody tr:last-child td { border-bottom: 0; }

/* ---- Stat ------------------------------------------------------------- */

.stat__n {
  /* Sans, not mono: Geist Mono gives the comma a full character cell,
     so "41,208" reads as "41 , 208". Tabular numerals keep it aligned. */
  font-family: var(--font-sans);
  font-size: 1.625rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.stat__l {
  font-size: var(--t-caption);
  color: var(--ink-3);
  margin-top: 5px;
}
.section--inv .stat__n { color: var(--ink-inv); }
.section--inv .stat__l { color: var(--ink-inv-2); }

/* ---- Logo wall --------------------------------------------------------- */

/* Until real logo files are approved, brand names are set as a mono
   index. Deliberate typography, not sans-serif text pretending to be
   logos. Swap for artwork when the files arrive. */
.logo-wall {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-10);
}
.logo-wall span {
  font-family: var(--font-display);
  font-size: var(--t-mono-lg);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}

/* ---- Question ledger ----------------------------------------------------
   Settled 20 Sep: the FIVE core capability shapes, all on the page at once
   (the pager came off the same day it went on; user call). Each prompt is
   the product's chat bubble in a framed well with the capability captioned
   below, in loop order: ad hoc and advanced (Analyse), predictive and
   audience (Decide), then journey (Act) closing on the full-width frame.
   The sixth-question strip below carries the /book route.
   ---------------------------------------------------------------------- */

/* ---- The question bar (20 Sep, user reference) ---------------------------
   The five prompts collapse into one. A bar that looks like the product's
   own composer carries whichever question is chosen, and the capability
   labels become a row of tabs under it. js/qbar.js builds this from the
   .qledger__grid markup below and stows the grid; with the script off the
   grid is the page, exactly as before.
   ------------------------------------------------------------------------- */

.qledger__grid.is-stowed { display: none; }

.qbar {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  width: 100%;
}

/* ---- the drifting brand row ---------------------------------------------
   One row of the SUB-INDUSTRY's own customer marks, each at its natural
   width, sliding behind the bar and masked away at the sides and at the
   bottom so the bar emerges out of it. Texture, not content: aria-hidden,
   and the marks are listed properly on the pages that credit them.
   ------------------------------------------------------------------------- */
.qbar__field {
  position: relative;
  /* 720 and centred, so the row sits over the bar rather than running the
     container end to end (user, 20 Sep). The cap goes HERE, on the window the
     marks travel through, not on .qbar__run: the run has to stay wider than
     the window or there is nothing for it to scroll, so capping the run would
     stop the belt rather than shorten it. */
  width: 100%;
  max-width: 720px;
  margin-inline: auto;
  /* Pulled DOWN under the bar so the fade ends behind the pill rather than
     above it, and the row's own height is what the padding restores. Less pull
     than before: the marks were sitting on the bar's shoulder. */
  margin-bottom: -38px;
  padding-bottom: 62px;
  overflow: hidden;
  /* Two masks multiplied: the sides so marks dissolve before the rails, the
     bottom so the row is gone by the time the bar starts. */
  -webkit-mask-image:
    linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent),
    linear-gradient(180deg, #000 30%, transparent 86%);
  mask-image:
    linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent),
    linear-gradient(180deg, #000 30%, transparent 86%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
.qbar__row { overflow: hidden; }
.qbar__run {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  width: max-content;
  animation: qbar-drift 72s linear infinite;
}
@keyframes qbar-drift {
  to { transform: translateX(-50%); }
}
/* Natural size (user, 20 Sep): no tile, no square. The marks are wordmarks of
   every aspect, so they are held to a common HEIGHT and keep their own width,
   which is what lets each read as itself. Full colour, always. */
.qbar__mark {
  flex: none;
  display: inline-flex;
  align-items: center;
}
.qbar__mark img {
  height: 26px;
  width: auto;
  /* A guard for the widest wordmarks, so one of them cannot own the row. */
  max-width: 148px;
  object-fit: contain;
}
/* SQUARE BADGES GET MORE HEIGHT. A common height is the right rule for
   wordmarks, which are all roughly the same proportion, and the wrong one for
   a badge: Shell's pecten and Petron's shield are as tall as they are wide, so
   at 26px they occupy a sixth of the area of the wordmark beside them and read
   as half-loaded. Same optical correction the homepage logo grid makes for the
   Rack Room Shoes badge. Matched on the filename because the markup is
   generated from a name list and carries nothing else to hook on. */
.qbar__mark img[src*="shell"],
.qbar__mark img[src*="petron"],
.qbar__mark img[src*="tata"],
.qbar__mark img[src*="rack-room-shoes"],
.qbar__mark img[src*="1-india-family-mart"] {
  height: 40px;
}

/* The bar keeps the narrow measure; the field and the tabs do not. An input
   the width of the container reads as a banner (user call), but a tab row
   capped at the same width wraps to two lines for no reason. */
.qbar__box {
  width: 100%;
  max-width: 720px;
  margin-inline: auto;
}

/* The bar is the product's own ask bar, the same anatomy as the homepage
   difference loop's (20 Sep, user call): a white pill with plus, the
   question, mic, and the round P200 send. 24px, not --r-pill: the bar grows
   when the question wraps, and a half-height radius on a tall box stops
   reading as the product's bar. */
.qbar__box {
  position: relative;   /* above the field it emerges from */
  z-index: 1;
  display: flex;
  /* Three lines tall and FIXED (user, 20 Sep). The questions run from one
     line to three, and a box that grew with each one made the tabs below it
     jump every time a tab was pressed. 3 x 1.5 x 14px of text plus the
     padding; the text block centres itself inside whatever it does not use. */
  min-height: 79px;
  align-items: center;
  gap: var(--sp-3);
  padding: 8px 8px 8px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 24px;
  box-shadow: var(--shadow-md);
}
.qbar__text {
  flex: 1;
  min-width: 0;
  margin: 0;
  /* Three lines' worth reserved, so the pill is the same height on the
     one-line questions as on the three-line ones. */
  min-height: calc(3 * 1.5em);
  display: flex;
  align-items: center;
  /* The bar is product UI, so it takes the product's face, same call as
     the bubble in the well below it. */
  font-family: var(--font-product);
  font-size: var(--t-body);
  line-height: 1.5;
  color: var(--ink);
  /* Wraps rather than truncates: the whole question stays readable and the
     pill grows taller instead, same rule as the difference loop's bar. */
  overflow-wrap: break-word;
}
/* One flex item, so the caret below can stay inline inside it. */
.qbar__line { display: block; width: 100%; }

/* A caret, so the line reads as being typed rather than as a heading that
   happens to change. It rides the end of the text because it is a sibling
   span, not a pseudo on the box. */
.qbar__caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: var(--brand);
  animation: qbar-blink 1.05s step-end infinite;
}
@keyframes qbar-blink { 50% { opacity: 0; } }

.qbar__plus,
.qbar__mic {
  display: inline-flex;
  flex: none;
  color: var(--ink-3);
}
.qbar__plus svg,
.qbar__mic svg { width: 20px; height: 20px; }
/* The product's own send: the round P200 circle with the regular up arrow.
   Decorative, so the low-contrast white-on-periwinkle arrow is fine. */
.qbar__send {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-pill);
  background: var(--primary-200);
  color: var(--on-brand);
}
.qbar__send svg { width: 20px; height: 20px; }

/* ---- the capability tabs ---- */
.qbar__tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  /* No max-width: all five fit on one line at the container's full width,
     and capping them at the bar's 720 pushed the fifth onto its own row. */
  width: 100%;
  /* On top of .qbar's own 16px gap (user, 20 Sep): the tabs sat tight under
     the pill and read as part of it rather than as the thing that drives it. */
  margin-top: var(--sp-4);
}
.qbar__tab {
  position: relative;
  /* The fill is a pseudo sweeping the whole pill, so it has to be clipped by
     the pill's own corners. This costs the pill its automatic minimum size:
     a flex item whose overflow is not visible floors at 0 instead of
     min-content, so in the mobile row (nowrap) every pill collapsed onto its
     icon and clipped its own label. flex: none is what holds them out. */
  overflow: hidden;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  font-family: var(--font-sans);
  font-size: var(--t-body);
  font-weight: 500;
  line-height: 1;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.qbar__tab svg { width: 18px; height: 18px; flex: none; color: var(--ink-3); }
@media (hover: hover) {
  .qbar__tab:hover { color: var(--ink); border-color: var(--border-strong); }
  .qbar__tab:hover svg { color: var(--ink-2); }
}
.qbar__tab.is-on {
  color: var(--ink);
  background: var(--brand-subtle);
  border-color: var(--brand-line);
}
.qbar__tab.is-on svg { color: var(--brand); }

/* ---- the six-second fill ------------------------------------------------
   The open tab fills left to right over the time it has left. The bar and the
   advance are ONE clock: this keys off .is-running, which js/qbar.js adds in
   the same frame it sets the six-second timeout, so the fill can never be
   ahead of or behind the change. 6s here and HOLD there are the same number
   and must move together.
   ------------------------------------------------------------------------- */
.qbar__tab::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  transform: scaleX(0);
  transform-origin: left center;
}
.qbar.is-running .qbar__tab.is-on::before {
  animation: qbar-fill 6s linear forwards;
}
@keyframes qbar-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
/* The label rides above the fill. Without this the pseudo paints over the
   icon and the text, because a positioned pseudo beats static content. */
.qbar__tab > * { position: relative; z-index: 1; }
.qbar__tab:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

@media (max-width: 640px) {
  /* One scrolling row rather than four stacked lines of pills, which took
     more height than the bar they belong to. Back to flex-start: a centred
     row that overflows clips its own left edge out of reach. */
  .qbar__tabs {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scrollbar-width: none;
    /* width:auto, not the base rule's 100%: with an explicit 100% the box
       stays as wide as the container while the negative margins shift it, so
       the row sat 20px left of the content and stopped 20px short of the
       right edge. Letting it stretch is what makes the bleed symmetrical. */
    width: auto;
    margin-inline: calc(var(--sp-5) * -1);
    padding-inline: var(--sp-5);
    /* The bleed IS the affordance: the last pill is cut by the screen edge
       rather than tidily contained, which is what says the row scrolls. The
       scroll padding keeps an auto-advanced tab off that edge. */
    scroll-padding-inline: var(--sp-5);
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  .qbar__tabs::-webkit-scrollbar { display: none; }
  /* 44px, the touch minimum. At the desktop padding these are 34px tall and
     sit under a thumb. The icon is what sets the content height, so the
     padding is solved against 18px, not the 14px label. */
  .qbar__tab { padding: 12px 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .qbar__caret { animation: none; }
  .qbar__tab { transition: none; }
  /* The field holds still. It keeps its full width, so the row reads as a
     static wall of marks rather than a belt frozen mid-slide. */
  .qbar__run { animation: none; }
  /* No fill either: the script does not advance the tabs under reduced
     motion, so a bar counting down to nothing would be a lie. */
  .qbar.is-running .qbar__tab.is-on::before { animation: none; }
}

.qledger__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-6);
  align-items: stretch;
}

.qledger__item {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}
/* The odd fifth: the journey frame spans the row, the move closing the loop. */
.qledger__item:last-child { grid-column: 1 / -1; }
.qledger__item:last-child .chat__q { max-width: 34rem; }

/* The frame space: a quiet well with the product bubble riding its right
   edge, the way an ask sits in the real product. Equal heights so each pair
   reads as one exhibit whatever the questions' lengths. */
.qledger__frame {
  flex: 1;
  display: flex;
  align-items: center;
  min-height: 140px;
  padding: var(--sp-6);
  background: var(--bg-subtle);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
}
.qledger__frame .chat__q {
  margin-left: auto;
  max-width: 92%;
  /* The bubble is drawn product UI, so it wears the product's face. */
  font-family: var(--font-product);
}

/* The caption: the capability, quiet and second to the prompt. */
.qledger__cap { color: var(--ink-2); }

@media (max-width: 767px) {
  .qledger__grid { grid-template-columns: minmax(0, 1fr); }
  .qledger__frame { min-height: 0; padding: var(--sp-5); }
}


/* ---- Footnote ---------------------------------------------------------- */

.footnote {
  font-size: var(--t-caption);
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 44rem;
}
.footnote a { color: var(--brand-ink); text-decoration: underline; text-underline-offset: 2px; }

/* ---- Hover grammar: things settle into place --------------------------
   One idea, applied consistently. Elements at rest are slightly out of
   true and straighten on hover. Rest-state transforms MUST live in CSS,
   never in an inline style attribute, or the hover rule loses.
   ---------------------------------------------------------------------- */

.settle { transition: transform var(--dur-slow) var(--ease); }
.settle--tilt-l { transform: rotate(-1.4deg); }
.settle--tilt-r { transform: rotate(1.4deg); }
.settle--drop   { transform: translateY(4px); }
.settle-group:hover .settle,
.settle:hover { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .settle, .card, .btn, .chip, .nav, .nav__panel { transition: none !important; }
  .settle--tilt-l, .settle--tilt-r, .settle--drop { transform: none; }
}

/* ---- Reveal on scroll -------------------------------------------------
   Implemented for real in js/reveal.js. If JS fails or is disabled the
   .no-js / no-observer path adds .is-in immediately, so content is never
   trapped invisible.
   ---------------------------------------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }
.reveal-stagger > * { transition-delay: calc(var(--i, 0) * 60ms); }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

/* ---- Video lightbox -------------------------------------------------------
   Opened by js/video.js from a [data-video] poster: dimmed overlay, a 16:9
   box for the embed, a close button above it. Escape, the button, or a
   click on the backdrop closes it.
   ---------------------------------------------------------------------- */

body.modal-open { overflow: hidden; }

/* ---- Video lightbox (rebuilt 20 Sep, user reference) --------------------
   Nearly full viewport, the film rounded and clipped, and aiRA's own control
   bar riding on the picture over a gradient scrim rather than a solid strip.
   Driven by js/video.js, which drives either a <video> or a YouTube iframe
   through the same bar.
   ------------------------------------------------------------------------- */

.vmodal {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Was 64px of top padding to clear a close button that hung ABOVE the box.
     The button sits inside the picture now, so the popup can use the room. */
  padding: var(--sp-5);
  background: rgba(11, 16, 32, 0.86);
  animation: vmodal-in var(--dur) var(--ease);
}
@keyframes vmodal-in {
  from { opacity: 0; }
}

/* Height-first, not width-first. A 16:9 box sized off the width overflows a
   short laptop window, which is why the old one capped at 1080 and still sat
   small on a large screen. Sizing off BOTH and letting min() pick means the
   film is as large as the window can actually hold, either way round. */
.vmodal__box {
  position: relative;
  width: min(1680px, 100%, (100vh - 2 * var(--sp-5)) * 16 / 9);
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.vp__stage,
.vp__stage > * {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  object-fit: contain;
}

/* The two scrims. Controls read on any frame, and the picture reads through
   them, which a solid bar does not allow. */
.vp__scrim {
  position: absolute;
  left: 0; right: 0;
  pointer-events: none;
}
.vp__scrim--top {
  top: 0; height: 96px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), transparent);
}
.vp__scrim--bot {
  bottom: 0; height: 132px;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.62), transparent);
}

.vmodal__close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 0;
  background: transparent;
  color: #fff;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.vmodal__close:hover { background: rgba(255, 255, 255, 0.16); }
.vmodal__close svg { width: 20px; height: 20px; }

/* ---- the control bar ---- */
.vp__bar {
  position: absolute;
  z-index: 2;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  color: #fff;
}
.vp__btn {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: inherit;
  cursor: pointer;
  opacity: 0.86;
  transition: opacity var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.vp__btn:hover { opacity: 1; background: rgba(255, 255, 255, 0.14); }
.vp__btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.vp__btn svg { width: 20px; height: 20px; }

/* Tabular figures, or the bar shuffles sideways every time the seconds tick
   over from 1 to 2 digits. */
.vp__time {
  flex: none;
  font-size: var(--t-caption);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.82);
  white-space: nowrap;
}

/* The scrubber is a real range input, so it arrives with keyboard support
   and a correct a11y role for free. --p is the played fraction, written by
   the script; the track paints it as a two-stop gradient. */
.vp__seek {
  flex: 1;
  min-width: 0;
  height: 16px;
  margin: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.vp__seek::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(to right,
    #fff 0 var(--p, 0%), rgba(255, 255, 255, 0.32) var(--p, 0%) 100%);
}
.vp__seek::-moz-range-track {
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(to right,
    #fff 0 var(--p, 0%), rgba(255, 255, 255, 0.32) var(--p, 0%) 100%);
}
/* The handle appears on hover and focus only: at rest the reference shows a
   bare line, and a permanent dot on a one-minute film reads as clutter. */
.vp__seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  margin-top: -4.5px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.vp__seek::-moz-range-thumb {
  width: 12px; height: 12px;
  border: 0; border-radius: 50%;
  background: #fff;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.vp__seek:hover::-webkit-slider-thumb,
.vp__seek:focus-visible::-webkit-slider-thumb { opacity: 1; }
.vp__seek:hover::-moz-range-thumb,
.vp__seek:focus-visible::-moz-range-thumb { opacity: 1; }
.vp__seek:focus-visible { outline: 2px solid #fff; outline-offset: 4px; border-radius: 2px; }

/* ---- the speed menu ---- */
.vp__menu-wrap { position: relative; flex: none; display: flex; }
.vp__menu {
  position: absolute;
  bottom: calc(100% + 10px);
  right: 0;
  z-index: 3;
  min-width: 128px;
  padding: 4px;
  background: rgba(20, 24, 38, 0.96);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}
.vp__menu[hidden] { display: none; }
.vp__rate {
  display: block;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: rgba(255, 255, 255, 0.82);
  font: inherit;
  font-size: var(--t-caption);
  text-align: left;
  cursor: pointer;
}
.vp__rate:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }
.vp__rate.is-on { color: #fff; font-weight: 600; }

@media (max-width: 640px) {
  .vmodal { padding: var(--sp-3); }
  .vp__bar { gap: var(--sp-3); padding: var(--sp-3); }
  /* The time reading is the first thing to go: the scrubber carries the same
     information and needs the room more. */
  .vp__time { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vmodal { animation: none; }
  .vp__btn, .vp__seek::-webkit-slider-thumb, .vp__seek::-moz-range-thumb,
  .vmodal__close { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vmodal { animation: none; }
  .vmodal__close { transition: none; }
}

/* ============ Accordion ============
   One-open-at-a-time disclosure list. Hairline-separated rows, mono index,
   Phosphor caret. Collapse animates via the 0fr -> 1fr grid-row trick so no
   heights are hardcoded. Used with js/accordion.js ([data-accordion]). */

.acc { display: flex; flex-direction: column; }

.acc__item { border-top: 1px solid var(--border); }
.acc__item:last-child { border-bottom: 1px solid var(--border); }

/* .acc__btn takes `font: inherit`, so this h3 sets the type for the whole
   disclosure row. A row is not a section heading, so it opts out of the
   universal h3 size deliberately and says so. It declared nothing at all
   before, which is why it was rendering at the UA default's 16.38px. */
.acc__head {
  margin: 0;
  font-size: var(--t-title-sm);
  font-weight: 500;
  line-height: var(--lh-title);
}

.acc__btn {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  /* The row is padded and pulled back out by the same amount, so the hover
     wash reaches past the text to the accordion's own edges instead of
     stopping at the first and last glyph. */
  width: calc(100% + var(--sp-4) * 2);
  padding: var(--sp-5) var(--sp-4);
  margin-inline: calc(-1 * var(--sp-4));
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--ink);
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
/* Hover recolours, it does not fill (user call, 22 Sep, REVERSING the 16 Sep
   split where hover was a Primary/50 wash and nothing else). The colour goes
   on .acc__btn rather than on .acc__title, because only /product's rows wrap
   their label in a .acc__title: the homepage FAQ puts its label in as a bare
   text node, and a rule on the span would leave those rows with no hover
   affordance at all. The title inherits, so both get it from here.

   The open row still refuses the hover state: a pointer hint on the thing
   already chosen says nothing. Hover and open landing on the same colour is
   fine now that neither is a fill, because the open row is also carrying a
   rotated chevron, an expanded panel and the advance bar. */
.acc__item:not(.is-open) .acc__btn:hover,
.acc__item:not(.is-open) .acc__btn:focus-visible {
  color: var(--brand-ink);
}
.acc__item.is-open .acc__title { color: var(--brand-ink); }
.acc__item.is-open .acc__chev { color: var(--brand); }

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

/* Grey until the row is open (user call, 22 Sep). It was --brand on every
   row, which lit all four at once and left the open one with nothing of its
   own. --ink-3 is the chevron's grey, so the two marks on a closed row agree.
   It stays grey on hover too: hover moves the LABEL, and an icon that moved
   with it would make a pointer hint look like a selection. */
.acc__icon {
  flex: none;
  display: flex;
  margin-top: 2px;
  color: var(--ink-3);
}
.acc__item.is-open .acc__icon { color: var(--brand); }
.acc__icon svg { width: 20px; height: 20px; }

.acc__n {
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

.acc__title {
  font-family: var(--font-display);
  font-size: var(--t-title-sm);
  font-weight: 500;
  letter-spacing: var(--tr-title);
  line-height: 1.32;
}

.acc__chev {
  flex: none;
  width: 16px;
  height: 16px;
  margin-left: auto;
  align-self: center;
  color: var(--ink-3);
  transition: transform var(--dur-fast) var(--ease);
}
.acc__item.is-open .acc__chev { transform: rotate(180deg); }

.acc__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s var(--ease);
}
.acc__item.is-open .acc__panel { grid-template-rows: 1fr; }
.acc__panel > .acc__body { overflow: hidden; min-height: 0; }

.acc__body p {
  font-size: var(--t-body);
  color: var(--ink-2);
  /* Indent under the title: index width plus the button gap. */
  padding: 0 var(--sp-8) var(--sp-5) calc(20px + var(--sp-4));
  margin: 0;
  max-width: 34rem;
}
.acc__body strong { color: var(--ink); font-weight: 600; }

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


/* ==========================================================================
   Proof marquee
   MOVED here from pages/home.css on 17 Sep. index.html and /solutions/retail
   both carry the band, so it is a component rather than a page rule. Until
   this moved, the retail page rendered the three belt tracks as three
   stacked rows, because it does not load home.css.
   ========================================================================== */

/* ---- Proof: customer logo cloud ------------------------------------------
   A marquee strip (Magic UI Marquee, logo-cloud-three) sitting between two
   frame joints. Three identical tracks form the belt so it stays covered at
   container width; each slides one track-width per loop, and an edge mask
   dissolves the names before they reach the rails. Mono-caps names hold the
   seats until the customer brand marks arrive.
   ---------------------------------------------------------------------- */

.proof__marquee {
  display: flex;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right,
    transparent, #000 16%, #000 84%, transparent);
  mask-image: linear-gradient(to right,
    transparent, #000 16%, #000 84%, transparent);
}

/* Each mark is the full-colour original at rest: the grey-at-rest,
   colour-on-hover pair was retired site-wide on 19 Sep. The belt pauses
   while hovered so a moving logo can still be read. */
.proof__logo {
  position: relative;
  display: block;
}
.proof__logo img { display: block; }
/* No-JS fallback only: with js/marquee.js driving, hover slows instead. */
.proof__marquee:hover .proof__track { animation-play-state: paused; }
.proof__marquee--js .proof__track { animation: none; }

/* Light-mode tuning for the belt (16 Sep, marquee restored to V1 from V2).
   The edge mask dissolves each mark into the page ground, so the band reads
   contained without a box: it needs a FLAT ground to fade into, which is
   why this section takes no wash. */
.proof__track {
  flex: none;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--sp-20);
  /* Trailing pad keeps the loop seam the same width as the item gap. */
  padding-right: var(--sp-20);
  animation: proof-marquee 36s linear infinite;
}
@keyframes proof-marquee {
  to { transform: translateX(-100%); }
}

@media (max-width: 640px) {
  .proof__logo img { height: 24px; width: auto; }
  .proof__logo img[alt="Rack Room Shoes"] { height: 36px; }
  .proof__track { gap: var(--sp-12); padding-right: var(--sp-12); }
}


/* Reduced motion: one static, centred index; no mask, no duplicates. */
@media (prefers-reduced-motion: reduce) {
  .proof__track {
    animation: none;
    flex-wrap: wrap;
    justify-content: center;
    padding-right: 0;
  }
  .proof__track[aria-hidden="true"] { display: none; }
  .proof__marquee {
    /* The lone track is max-content wide, so the wrapper centres it. */
    justify-content: center;
    -webkit-mask-image: none;
    mask-image: none;
  }
}


/* ==========================================================================
   The business grid
   --------------------------------------------------------------------------
   The eight retail businesses, rendered as an open ruled list rather than a
   wall of cards (17 Sep, user reference): rules BETWEEN entries only, never a
   box around each one. Icon, title, two-line description, link.

   Shared, not page furniture. The same eight appear on the finder as filter
   buttons and on the retail hub as links to their category pages, and the
   eight category pages inherit the same grid, so the rules live here and each
   page supplies only the element type and the link label. "Business" is the
   site's own word for these, from the 15 Sep call (IA §19), which is why the
   component is not named after either page.

   The divider arithmetic is keyed to the column count: every cell draws its
   top and left edge, then the grid's outer ring is erased along with the
   outer padding so the set sits flush on the page rails. Each breakpoint
   re-keys that arithmetic to its own count.
   ========================================================================== */


/* Eight then four: at four columns both groups fill their rows exactly, so
   neither ends on an orphan card. */
.biz-grid {
  display: grid;
  /* Three across (17 Sep, Amplitude-style reference): eight cards run 3+3+2,
     and the open treatment below reads better with the wider measure. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin-top: var(--sp-10);
}

/* Open, unboxed (17 Sep, Amplitude-style reference): icon, title, body and a
   link, no card chrome. Eleven bordered boxes read as a wall; eleven entries
   read as a list you can scan. The dark industry band below is the contrast:
   sub-industries are entries, industries are destinations. */
.biz {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  padding: var(--sp-8);
  text-align: left;
  cursor: pointer;
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
}
/* Rule lines only BETWEEN entries (user call, 17 Sep: "border lines but only
   in between, i dont want boxy layout"): every cell draws its top and left
   edge, then the grid's outer ring is erased, and the outer paddings go with
   it so the set stays flush on the page rails. Keyed to the 3-column count;
   each breakpoint below re-keys the arithmetic to its own count. */
.biz:nth-child(-n+3) { border-top: 0; padding-top: 0; }
.biz:nth-child(3n+1) { border-left: 0; padding-left: 0; }
.biz:nth-child(3n) { padding-right: 0; }
/* The bottom edge of the outer ring, same idea as the other three: the last
   row's padding is trailing space with no row beneath it to separate from,
   so the grid's content hugs its own edge. Items 7 and 8 are the last row at
   both three and two columns. */
.biz:nth-last-child(-n+2) { padding-bottom: 0; }

/* The eight run 3 + 3 + 2, so the last cell of the bottom row is empty and the
   rules stopped short of the right-hand rail. This pseudo-element is a real
   grid item filling that cell, drawing the same two edges and nothing else, so
   the set closes square. It is keyed to a set of eight in three columns: at
   two and at one column eight divides evenly, there is no orphan cell, and the
   rule below switches it off. */
.biz-grid::after {
  content: "";
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
}
/* Static variant (18 Sep): the homepage runs this grid for all four
   industries, but only retail has pages behind its categories. The other
   three render as entries rather than links, so they must not offer a
   pointer, a hover or a trailing "go" line that leads nowhere. */
.biz--static { cursor: default; }
.biz--static:hover .biz__ic { color: var(--ink-3); }

.biz:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 4px;
  border-radius: var(--r-xs);
}

/* The same graph-paper tile the nav drawer glyphs sit on (user call, 17 Sep:
   "biz__ic should look like nav__item-icon"): 40px of ruled paper dissolving
   at its own edges, glyph ink-3 at rest and brand on the card's hover.
   .nav__item-icon came off on 28 Sep and went back on the same day, so the
   reference is live again. */
.biz__ic {
  position: relative;
  display: flex;
  align-items: center;
  /* The ROW is the card's full width, the TILE is the 40px square at its left
     end (drawn by ::before). They were the same box until 19 Sep, when the
     hover arrow moved to the far end of this row: the arrow needs a right
     edge to sit against, and the card's own edge is the only one that lines
     up with the text below. */
  width: 100%;
  height: 40px;
  margin-bottom: var(--sp-4);
  color: var(--ink-3);
  transition: color var(--dur-fast) var(--ease);
}
.biz__ic::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 40px;
  height: 40px;
  background-image:
    repeating-linear-gradient(to right, var(--nav-graph) 0 1px, transparent 1px 10px),
    repeating-linear-gradient(to bottom, var(--nav-graph) 0 1px, transparent 1px 10px);
  background-position: 5px 5px;
  -webkit-mask-image: radial-gradient(closest-side, #000 52%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 52%, transparent 100%);
}
.biz:hover .biz__ic { color: var(--brand); }
/* (40 - 24) / 2: centres the glyph in the tile, which the row no longer
   does for it. */
.biz__ic svg { position: relative; width: 20px; height: 20px; margin-left: 8px; }

.biz__t {
  font-family: var(--font-display);
  font-size: var(--t-title-lg);
  font-weight: 600;
  letter-spacing: var(--tr-title);
  color: var(--ink);
  margin-bottom: 4px;
}
.biz__d {
  font-size: var(--t-body-sm);
  line-height: 1.5;
  color: var(--ink-3);
  /* Two allowances for the trailing "See the questions" link came out with it
     on 19 Sep: the grow that held one baseline for the links across a row, and
     the 20px gap above them. The description is the last child of every card
     now, so both were reserving space for nothing and every card carried the
     20px. The grow was a no-op on its own (a last child that grows still sets
     its text from the top), the margin was the height. */
  margin-bottom: 0;
}
/* The whole card is the link, and the affordance arrives on demand: on hover
   or keyboard focus an arrow slides in at the far end of the icon row,
   opposite the category glyph, which stays exactly where it is. Same 24px as
   the glyph, so the row reads as one line with a start and an end. Scoped to
   a.biz, so a card that is not a link never offers a route it does not have.
   The %23000 in the mask is alpha, not a colour: the arrow paints in the
   row's own currentColor, which the card's hover has already taken to
   brand. */
a.biz .biz__ic::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  margin-top: -12px;
  width: 24px;
  height: 24px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
a.biz:hover .biz__ic::after,
a.biz:focus-visible .biz__ic::after {
  opacity: 1;
  transform: none;
}
/* On a phone the arrow is there from the start (user, 20 Sep). "On demand"
   needs a pointer to demand it: with no hover the affordance never appeared
   at all, and in the one-column layout a card is a full-width row that gives
   no other sign it is a route. Two conditions, either one: the component's
   own phone layout, and any device that reports no hover, which covers the
   tablet widths in between. Still scoped to a.biz, so a static card is
   unaffected. */
@media (max-width: 560px), (hover: none) {
  a.biz .biz__ic::after {
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 1100px) {
  .biz-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .biz-grid::after { display: none; }
  /* Re-key the between-lines to two columns: restore everything the 3-column
     arithmetic erased, then erase the new outer ring. */
  .biz:nth-child(n) {
    border-top: 1px solid var(--border);
    border-left: 1px solid var(--border);
    padding: var(--sp-8);
  }
  .biz:nth-child(-n+2) { border-top: 0; padding-top: 0; }
  .biz:nth-child(2n+1) { border-left: 0; padding-left: 0; }
  .biz:nth-child(2n) { padding-right: 0; }
  .biz:nth-last-child(-n+2) { padding-bottom: 0; }
}
@media (max-width: 560px) {
  .biz-grid { grid-template-columns: 1fr; }
  /* One column: horizontal rules between entries only. */
  .biz:nth-child(n) {
    border-left: 0;
    border-top: 1px solid var(--border);
    padding: var(--sp-6) 0;
  }
  .biz:first-child { border-top: 0; padding-top: 0; }
  .biz:last-child { padding-bottom: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .biz, .biz__ic, .biz__ic::after { transition: none; }
}

/* ---- Analyse / Decide / Act ----------------------------------------------
   The client's own three-step frame, lifted from the capabilities-per-vertical
   deck (17 Sep, see IA §29): see what happened, pick the move, launch it with
   a person approving.

   Laid out on the Clerk reference the user supplied (17 Sep): a large column
   headline, a muted line under it, then a list where a 20px glyph sits left of
   the item name and the description hangs under the NAME, not under the glyph.
   That second indent is what makes the list read as two levels instead of one,
   and it is the whole trick in the reference. Three columns rather than the
   reference's two, divided by hairlines rather than boxed into cards: the open
   treatment is the same call already made on .biz-grid.

   Icons are official Phosphor DUOTONE, one per item, in --brand: the same
   treatment as .seat__cap-ic and .qledger__icon, which sit on these same pages,
   because these are capability glyphs and that is the site's weight for them.
   They were regular and ink for one revision, on the reasoning that brand had
   to be reserved for the "supported today" mark. That reasoning expired when
   the mark became the word LIVE: a word does not need colour to be found, so
   the glyphs are free to follow the house treatment. Duotone also needs a hue:
   its 0.2 layer is a tint of the same colour, which reads as mud in grey.

   The 01/02/03 markers earn their place here, where numbered markers usually
   do not: this is a real sequence and each column consumes the one before it.

   Items carry data-live. If the roadmap items should come off the page
   entirely, hide [data-live="false"] rather than editing four pages.
   -------------------------------------------------------------------------- */

.runs-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin-top: var(--sp-12);
  /* Stretch, not start: the divider is the column's own left border, so an
     align-items:start grid gives each rule a different length. */
}

.runs__col {
  padding-inline: var(--sp-10);
  border-left: 1px solid var(--border);
}
.runs__col:first-child { padding-left: 0; border-left: 0; }
.runs__col:last-child { padding-right: 0; }

.runs__n {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  color: var(--ink-faint);
  margin-bottom: var(--sp-4);
}
.runs__t {
  font-size: var(--t-title-xl);
  font-weight: 500;
  line-height: 1.22;
  letter-spacing: var(--tr-title);
  color: var(--ink);
}
.runs__d {
  margin-top: var(--sp-3);
  font-size: var(--t-body-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
  text-wrap: pretty;
}

.runs__list {
  list-style: none;
  margin: var(--sp-10) 0 0;
  padding: 0;
  display: grid;
  /* The scale has no --sp-7: it steps 24 -> 32. An invalid value here makes the
     whole gap declaration invalid, which collapses the list. */
  gap: var(--sp-8);
}

/* Two columns: the glyph, then everything that hangs off the name. */
.runs__item {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  column-gap: var(--sp-3);
  row-gap: var(--sp-2);
  align-items: start;
}
.runs__ic {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  width: 20px;
  height: 20px;
  flex: none;
  color: var(--brand);
}
.runs__name {
  grid-column: 2;
  grid-row: 1;
  font-size: var(--t-title-sm);
  font-weight: 600;
  /* 16px x 1.25 = 20px, exactly the glyph's box. The icon is top-aligned in the
     same row, so its box and the name's FIRST line box coincide: the two line up
     without a nudge, and they stay lined up when a long name wraps, which a
     centred icon would not. */
  line-height: 1.25;
  letter-spacing: var(--tr-title);
  color: var(--ink);
}
.runs__desc {
  grid-column: 2;
  grid-row: 2;
  font-size: var(--t-body-sm);
  line-height: var(--lh-body);
  color: var(--ink-3);
  text-wrap: pretty;
}

/* Two columns then one. The sequence still reads in order when stacked, and
   the divider moves to the top edge so it never sits at the end of a column. */
@media (max-width: 1100px) {
  .runs__col { padding-inline: var(--sp-8); }
}
@media (max-width: 991px) {
  .runs-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .runs__col:nth-child(2n+1) { padding-left: 0; border-left: 0; }
  .runs__col:nth-child(n+3) {
    margin-top: var(--sp-10);
    padding-top: var(--sp-10);
    border-top: 1px solid var(--border);
  }
}
@media (max-width: 640px) {
  .runs-grid { grid-template-columns: minmax(0, 1fr); }
  .runs__col { padding-inline: 0; border-left: 0; }
  .runs__col + .runs__col {
    margin-top: var(--sp-8);
    padding-top: var(--sp-8);
    border-top: 1px solid var(--border);
  }
}

/* Dark variant (18 Sep, user call): the section wears .section--inv and
   every register retunes. Icons and flags ride Primary/200, the dark-surface
   accent, because full brand Primary/400 sinks on near-black. Placed after the
   responsive blocks so the longhand border colours win over the media
   rules' shorthands at every width. */
.section--inv .runs__col {
  border-left-color: rgba(255, 255, 255, 0.14);
  border-top-color: rgba(255, 255, 255, 0.14);
}
.section--inv .runs__n { color: var(--ink-inv-3); }
.section--inv .runs__t { color: var(--ink-inv); }
.section--inv .runs__d { color: var(--ink-inv-2); }
/* Light, not brand (user call, 18 Sep): the icons sit in the same register
   as the names beside them rather than adding a violet accent per row. */
.section--inv .runs__ic { color: var(--ink-inv-2); }
.section--inv .runs__name { color: var(--ink-inv); }
.section--inv .runs__desc { color: var(--ink-inv-3); }


/* ---- Proof logo grid (19 Sep; moved here from home.css 20 Sep) --------------
   Shared by index.html and /book.
   The promptwatch book-a-demo grid in the token system, replacing the
   marquee belt: the grid's own ground is the hairline colour and the tiles
   sit on it with a 1px gap and small radii, so the gaps draw the grid the
   way the reference's half-pixel padding does. Tiles are slightly
   translucent over the section ground; logos sit in full colour at rest
   (the grey-at-rest, colour-on-hover pair retired 19 Sep). js/logo-grid.js
   resettles one mark every few seconds; without it the grid is simply
   still. */

/* This container drops its 20px inline gutter so the tiles run right up to
   the page frame's rails, promptwatch-style. */
.lgrid .container {
  padding-inline: 0;
}

/* The reference's quiet centred line: plain body sans in the muted ink, no
   eyebrow, sitting in its own row above the grid. */
.lgrid__label {
  text-align: center;
  font-size: var(--t-body-lg);
  color: var(--ink-2);
  margin-bottom: var(--sp-6);
}
/* Left on phones (user, 20 Sep). The line runs to two or three lines at this
   width and a centred rag over the grid's hard left edge reads as a
   misalignment rather than a caption. */
@media (max-width: 640px) {
  .lgrid__label { text-align: left; }
}

/* Square, promptwatch-style: white tiles split by 1px hairlines (the grid's
   gap over the border colour), closed by hairlines above and below. The
   sides stay open; the page frame's rails draw those lines. */
.lgrid__grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border-block: 1px solid var(--border);
}

.lgrid__tile {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 104px;
  background: var(--surface);
}

.lgrid__logo {
  position: relative;
  display: inline-flex;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
/* One height per SHAPE, not one height for all: at a single 26px a wide
   wordmark reads huge while a squarish mark reads tiny, so each logo's span
   carries the class of its aspect-ratio band (from the SVG viewBox):
   over 4:1 wide, 2.4-4 default, 1.7-2.4 mid, under 1.7 badge. */
.lgrid__logo img {
  display: block;
  height: 26px;
  width: auto;
}
.lgrid__logo--wide img { height: 22px; }
.lgrid__logo--mid img { height: 32px; }
.lgrid__logo--badge img { height: 40px; }
.lgrid__logo--sq img { height: 48px; }

/* The resettle: js/logo-grid.js fades a mark out of one seat and into the
   next. */
.lgrid__logo.is-out { opacity: 0; transform: translateY(6px) scale(0.96); }
.lgrid__logo.is-in { animation: lgrid-in 260ms var(--ease); }
@keyframes lgrid-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.96); }
}

@media (max-width: 767px) {
  .lgrid__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lgrid__tile { min-height: 88px; }
}

/* Below the rails' breakpoint the frame is gone, so the grid pulls itself
   out of .padding-global's gutter and runs to the screen edges; the label
   above keeps its inset. */
@media (max-width: 991px) {
  .lgrid__grid { margin-inline: calc(-1 * var(--pad-x)); }
}

@media (prefers-reduced-motion: reduce) {
  .lgrid__logo { transition: none; }
  .lgrid__logo.is-in { animation: none; }
}


/* ==== Sections shared by index.html and /product (moved from home.css 20 Sep) ==== */

/* ---- Mechanism: data -> judgement -> channels --------------------------
   Dark architecture diagram (Steep-style): source pills feed the aiRA core
   over dashed wires, and the core fans out to the channels. Neutral glyphs
   hold the seats in the pills and rows until the real brand logos arrive.
   ---------------------------------------------------------------------- */

.mech { overflow: hidden; }
.mech > .padding-global { position: relative; }

/* Violet glow rising from the bottom edge, mixed from the brand tokens. */
.mech__glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(80% 64% at 50% 112%,
      color-mix(in srgb, var(--brand) 14%, transparent), transparent 72%),
    radial-gradient(46% 40% at 16% 116%,
      color-mix(in srgb, var(--primary-300) 8%, transparent), transparent 75%);
}

.mech__diagram {
  position: relative;   /* the measured wires overlay anchors to this */
  display: grid;
  grid-template-columns:
    minmax(180px, 0.9fr) minmax(60px, 1fr) auto
    minmax(60px, 1fr) minmax(210px, 0.9fr);
  align-items: center;
  /* Full container width: no cap, no side margins (user call, 12 Sep). */
}
.mech__sources { grid-column: 1; }
.mech__core { grid-column: 3; }
.mech__dest { grid-column: 5; }

.mech__col-label {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-inv-3);
  margin-bottom: var(--sp-4);
}

/* Bare brand lockups (dark-surface variants), no pill chrome. Heights are
   set per logo in the markup: the stacked lockups (BigQuery, Redshift)
   need more height than the single-line ones to read optically equal. */
.mech__sources {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-6);
}
.mech__sources .mech__col-label { margin-bottom: 0; }
.mech__sources img { display: block; height: 30px; width: auto; }
/* The Databricks lockup runs optically large: one step down. */
.mech__sources img[alt="Databricks"] { height: 22px; }

.mech__col-sub {
  font-size: var(--t-caption);
  color: var(--ink-inv-3);
  margin: 0;
}
.mech__dest .mech__col-sub { text-align: right; }

/* Dashed wires, generated by js/diagram.js from measured logo and node
   positions so every line terminates on the thing it connects. */
.mech__wires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}
.mech__wires path {
  fill: none;
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 1.5;
  stroke-dasharray: 1 6;
  stroke-linecap: round;
}

/* Animated beams (Magic UI AnimatedBeam): each wire carries a duplicate
   solid path stroked with a travelling blue-to-violet gradient that
   js/diagram.js builds and SMIL-animates along the wire, with randomised
   pace, phase, window and colour order per wire. The gradient stroke
   itself rides an inline style; only the geometry is styled here. */
.mech__wires .mech__beam {
  stroke-width: 2;
  stroke-dasharray: none;
}
.mech__wires .mech__beamstop-a { stop-color: var(--primary-300); }
.mech__wires .mech__beamstop-b { stop-color: var(--violet-400); }
@media (prefers-reduced-motion: reduce) {
  .mech__wires .mech__beam { display: none; }
}

/* The core as a compact node (Tailark illustration style): the aiRA mark
   in a circle with a dashed halo ring, a soft indigo glow, an orbiting
   border beam, and a mono label hung beneath. */
.mech__core {
  position: relative;
  display: flex;
  justify-content: center;
}

.mech__core-node {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 0 80px color-mix(in srgb, var(--brand) 28%, transparent);
}
.mech__core-node img { width: 40px; height: 40px; }

/* Dashed halo ring around the node. */
.mech__core-node::after {
  content: '';
  position: absolute;
  inset: -26px;
  border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, 0.16);
}

/* Hung above the node so the circle stays on the wires' centre line. */
.mech__core-label {
  position: absolute;
  bottom: calc(100% + var(--sp-10));
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-inv-3);
}

/* Hung under the node: the LP's four intelligence-layer claims, cycling
   one at a time (diagram.js swaps .is-on; the first is on in the markup
   so no JS still shows a line). */
.mech__core-sub {
  position: absolute;
  top: calc(100% + var(--sp-10));
  left: 50%;
  transform: translateX(-50%);
  width: 280px;
  font-size: var(--t-caption);
  line-height: 1.6;
  color: var(--ink-inv-3);
}
.mech__core-sub-text {
  position: relative;
  display: block;
  height: 20px;
}
/* Each line (spinner + words) centres as one max-content unit on the node. */
.mech__core-sub-text > span {
  position: absolute;
  top: 0;
  left: 50%;
  width: max-content;
  white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, 5px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.mech__core-sub-text > span.is-on {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Dot-matrix working loader (hex-3 dot sweep, translated from the efferd
   Svelte loader): 19 dots on a hex lattice, opacities driven by diagram.js. */
.dotspin {
  position: relative;
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-right: 8px;
  vertical-align: -2px;
}
.dotspin i {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: var(--primary-200);
  opacity: 0.08;
}
@media (prefers-reduced-motion: reduce) {
  .mech__core-sub-text { height: auto; text-align: center; }
  .mech__core-sub-text > span {
    position: static;
    display: block;
    width: auto;
    opacity: 1;
    transform: none;
    transition: none;
  }
  .dotspin { display: none; }
}

/* Border beam (Magic UI BorderBeam, in CSS): a lit segment of a conic
   gradient orbits the node's border, masked to a 1px ring. */
@property --mech-orbit {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.mech__core-node::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: conic-gradient(from var(--mech-orbit),
    transparent 0deg 300deg,
    color-mix(in srgb, var(--primary-200) 90%, transparent) 330deg,
    transparent 360deg);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  animation: mech-orbit 8s linear infinite;
}
@keyframes mech-orbit {
  to { --mech-orbit: 360deg; }
}
@media (prefers-reduced-motion: reduce) {
  .mech__core-node::before { display: none; }
}

/* Channel logos mirror the sources: bare lockups, right-aligned. */
.mech__dest {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-6);
}
.mech__dest .mech__col-label { margin-bottom: 0; }
.mech__dest img { display: block; height: 30px; width: auto; }
/* The Engage+ pill lockup runs optically large: one step down. */
.mech__dest img[alt="Capillary Engage+"] { height: 22px; }
/* The Salesforce lockup stacks its cloud over the text, so at the uniform
   height it reads tiny: one step up. */
.mech__dest img[alt="Salesforce Marketing Cloud"] { height: 42px; }

.mech__foot { margin-top: var(--sp-10); color: var(--ink-inv-3); }

/* The zero-copy promise: three cells sharing the questions grid's anatomy
   (.feat__mini icon/h4/para), retuned for the dark section. No top border. */
.mech__points.feat__mini {
  list-style: none;
  /* The core caption hangs below the diagram's flow box, so the gap here
     has to clear it before it reads as space. */
  margin: var(--sp-24) 0 0;
  border-top: 0;
  padding-top: 0;
}
.mech__points.feat__mini h4 { color: var(--ink-inv); }
.mech__points.feat__mini p { color: var(--ink-inv-3); }

@media (max-width: 991px) {
  /* Wider gaps give the vertical beam runs room to breathe. */
  .mech__diagram { grid-template-columns: 1fr; gap: var(--sp-10); }
  .mech__sources, .mech__core, .mech__dest { grid-column: auto; }
  /* Everything centres in the stack so the flow is symmetric around the
     node: labels, logo rows, captions, and therefore the wire spread. */
  .mech__sources, .mech__dest {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--sp-5) var(--sp-8);
  }
  .mech__sources .mech__col-label, .mech__dest .mech__col-label {
    width: 100%;
    text-align: center;
  }
  .mech__col-sub,
  .mech__dest .mech__col-sub {
    width: 100%;
    text-align: center;
  }
  /* The hung label needs cleared space when the diagram stacks. */
  .mech__core {
    margin-block: var(--sp-6);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
  }
  .mech__core-label,
  .mech__core-sub {
    position: static;
    transform: none;
    left: auto;
    top: auto;
    bottom: auto;
  }
}


/* ---- Category comparison -------------------------------------------------
   Three-column board: feature labels with icons, the aiRA column raised on
   a brand panel that overhangs the rows (Rally-style), the horizontal
   platforms column muted. The panel is a grid item spanning every row
   behind column 2, so no heights are hardcoded.
   ---------------------------------------------------------------------- */

/* ---- Breadcrumb -----------------------------------------------------------
   Shared since 22 Sep: the retail category pages had it first (in
   category.css) and /privacy-policy took the same control, so it lives here rather
   than being restated. The first item is a .crumb__back carrying a
   regular-weight arrow-left; the separators are unstyled spans in the markup.
   Page stylesheets position the strip, they do not restyle it.
   ------------------------------------------------------------------------- */
.crumb {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--t-body-sm);
  color: var(--frame-plus);
}
.crumb a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 500;
  color: var(--ink-3);
  transition: color var(--dur-fast) var(--ease);
}
.crumb a:hover { color: var(--brand-ink); }
/* The page you are on. It is a <span> and not an <a> because a link to the
   current page is a link that does nothing, and it takes full --ink against
   the trail's --ink-3 so the end of the trail is the part that reads. */
.crumb__current { font-weight: 500; color: var(--ink); }
.crumb__back svg { width: 20px; height: 20px; flex: none; }
@media (prefers-reduced-motion: reduce) {
  .crumb a { transition: none; }
}

.compare__wrap {
  margin-inline: auto;
  overflow-x: auto;
}

/* The sourcing note under the board (21 Sep, /product). Centred on the
   board's own axis and held narrow: it is a footnote, so it must not read as
   another row of the argument. Muted on the dark ground the board wears. */
.compare__note {
  max-width: 44rem;
  margin: var(--sp-6) auto 0;
  text-align: center;
  font-size: var(--t-caption);
  line-height: var(--lh-body);
  color: var(--ink-3);
}
.section--inv .compare__note { color: var(--ink-inv-3); }

.compare__board {
  display: grid;
  /* Feature labels are short; the two argument columns take the width. */
  grid-template-columns: minmax(0, 0.7fr) minmax(0, 1.15fr) minmax(0, 1.15fr);
  grid-template-rows: repeat(6, auto);
  min-width: 52rem;
  /* Room for the brand panel's overhang inside the scroll wrapper. */
  padding-block: var(--sp-6);
}

.compare__brandbg {
  grid-column: 2;
  grid-row: 1 / -1;
  margin-block: calc(-1 * var(--sp-6));
  /* A BRAND-TINTED DARK PANEL, not the flat brand (user, 28 Sep: "this color
     goes a bit different than dark page other colors"). It was var(--brand),
     Primary/600 at full saturation, which made it the only fully lit block on
     a section where everything else is near-black with white-alpha layers: it
     read as a sticker on the board rather than a column of it. The dark
     sections' accent language is the LIGHT end of the ramp, which is what the
     heading's <em> uses (--primary-200), not the saturated middle.
     30% is the level where the column still reads as the marked one: below
     about 20 it is indistinguishable from the ground, and the panel stops
     doing its job of saying which side of the board is aiRA.
     The edge is P200 at 30%, the same light accent the heading spends, so the
     panel is outlined in the section's own accent rather than in more of
     itself.
     NOTE it mixes against --surface-inv, which is correct while this component
     only ever appears on a LIGHT page inside a .section--inv band, as it does
     on index.html. On a whole dark page --surface-inv is the LIGHT surface and
     this would come out a pale lilac; that is the same inversion trap the nav
     drawer hit, and it would need a :root[data-theme="dark"] guard. */
  background: color-mix(in srgb, var(--brand) 30%, var(--surface-inv));
  border: 1px solid color-mix(in srgb, var(--primary-200) 30%, transparent);
  border-radius: var(--r-lg);
  /* The site's one floating-surface shadow, same as the header card: this
     panel overhangs the board top and bottom, so it should read as lifted
     off it rather than printed on it. */
  box-shadow: var(--shadow-float);
  z-index: 0;
}

.compare__cell {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  padding-block: var(--sp-4);
  font-size: var(--t-body);
  line-height: 1.5;
  text-wrap: pretty;
}

/* Explicit placement: the brand panel occupies column 2 in the placement
   grid, so auto-flow would route cells around it. Pin every cell. */
.compare__cell--feat  { grid-column: 1; }
.compare__cell--aira  { grid-column: 2; }
.compare__cell--other { grid-column: 3; }
.compare__board > .compare__cell:nth-child(-n+3)               { grid-row: 1; }
.compare__board > .compare__cell:nth-child(n+4):nth-child(-n+6)  { grid-row: 2; }
.compare__board > .compare__cell:nth-child(n+7):nth-child(-n+9)  { grid-row: 3; }
.compare__board > .compare__cell:nth-child(n+10):nth-child(-n+12) { grid-row: 4; }
.compare__board > .compare__cell:nth-child(n+13):nth-child(-n+15) { grid-row: 5; }
.compare__board > .compare__cell:nth-child(n+16):nth-child(-n+18) { grid-row: 6; }

.compare__cell--feat {
  gap: var(--sp-3);
  padding-right: var(--sp-8);
  font-weight: 500;
  color: var(--ink);
  border-bottom: 1px solid var(--border);
}
.compare__icon { flex: none; display: flex; color: var(--brand-ink); }
/* 20px, matching every other Phosphor glyph on the page (13 Sep). */
.compare__icon svg { width: 20px; height: 20px; }

.compare__cell--aira {
  padding-inline: var(--sp-6);
  font-weight: 500;
  color: #fff;
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
}

.compare__cell--other {
  padding-left: var(--sp-8);
  color: var(--ink-2);
  border-bottom: 1px solid var(--border);
}

.compare__cell--head {
  font-family: var(--font-display);
  font-size: var(--t-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.compare__cell--aira.compare__cell--head img { display: block; }

/* The board on the dark ground (19 Sep, when the section went section--inv
   to run into pricing as one passage): inks flip, hairlines go to white
   alphas, and the icons take the P200 dark-surface accent. The brand panel
   itself needs nothing: true indigo on ink was V2's call and it holds. */
.section--inv .compare__cell--feat {
  color: var(--ink-inv);
  border-bottom-color: color-mix(in srgb, var(--ink-inv) 12%, transparent);
}
.section--inv .compare__cell--other {
  color: var(--ink-inv-2);
  border-bottom-color: color-mix(in srgb, var(--ink-inv) 12%, transparent);
}
.section--inv .compare__cell--head { color: var(--ink-inv-3); }
/* White, not the P200 accent (user call, 19 Sep): the feature icons read as
   part of the label, so they take the label's ink. */
.section--inv .compare__icon { color: var(--ink-inv); }


/* Phones: the board keeps all three columns and scrolls sideways inside
   .compare__wrap (the user's call over the earlier restacked layout: the
   full grid reads better). Slightly narrower minimum and tighter gutters
   shorten the scroll distance. */
@media (max-width: 767px) {
  .compare__board { min-width: 44rem; }
  .compare__cell--feat { padding-right: var(--sp-5); }
  .compare__cell--aira { padding-inline: var(--sp-4); }
  .compare__cell--other { padding-left: var(--sp-5); }
}

/* Last row: the board's edge, not a divider. */
.compare__board > .compare__cell:nth-last-child(-n+4):not(.compare__brandbg) {
  border-bottom: 0;
}


/* ---- Book CTA ------------------------------------------------------------
   Last section: one brand-filled box between dotted rails (20 Sep,
   frigade.com reference), replacing the split card with the inline form.
   Heading, one line, one route to /book, where the form will live; four
   duotone glyphs float in the box's corners as quiet product furniture.
   ---------------------------------------------------------------------- */

/* Purple rather than the --wash-primary that .section--wash hands it, so the
   last light section on the page is not a repeat of the difference section. */
.book { background: var(--wash-secondary); }

/* The dotted side rails the box sits between. */
/* The section wears .section--band, so the page's own dot-grid side bands
   frame the box; the earlier dashed stand-ins came off with it. */
.book__frame {
  padding: var(--sp-4) 0;
}

.book__card {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-5);
  padding: var(--sp-20) var(--sp-10);
  background: var(--brand);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
/* A quiet radial lift at the top centre, so the fill is not one flat wall. */
.book__card::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(60% 70% at 50% -20%,
    rgba(255, 255, 255, 0.18), transparent 70%);
}

.book__title {
  position: relative;
  font-family: var(--font-display);
  font-size: var(--t-display-2);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: var(--tr-display);
  color: var(--on-brand);
  max-width: 24ch;
  text-wrap: balance;
}
.book__sub {
  position: relative;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: rgba(255, 255, 255, 0.82);
  max-width: 36rem;
}
/* White button on the brand fill, the inverse-section flip. */
.book__cta {
  position: relative;
  margin-top: var(--sp-3);
  background: var(--on-brand);
  color: var(--black);
  box-shadow: var(--shadow-md);
}
.book__cta:hover { background: var(--primary-50); }
.book__cta:active { background: var(--primary-100); }

/* ---- The SPLIT variant (23 Sep, promptwatch.com reference) --------------
   Copy left on a diagonal brand gradient, the real product right, cut by the
   card's own edge, and the whole thing filling the section RAIL TO RAIL
   ("real image or right, some gradient and end to end section fill") rather
   than sitting inside it as a box.

   WORN BY /product ONLY. index.html and the two experiments keep the centred
   brand box, so this is a modifier and not a rewrite of .book__card: all four
   pages load components.css, and index-v2 and index-v3 have to keep working
   against their frozen home-legacy.css.

   The radius and the drop shadow both go with it. A fill that meets the
   frame's own verticals is not a card any more, and a card shadow on
   something with no visible left or right edge only darkens the rails.
   -------------------------------------------------------------------------- */
/* The section gives up its 80px block padding and the frame its 16, so the
   fill runs the section's FULL height as well as its full width (user,
   23 Sep). Both are scoped with :has() rather than set on .book and
   .book__frame outright, because index.html and the two experiments share
   those two classes and keep the inset box.

   It changes where the dissolve lands, which the user called: the ramp used
   to finish inside 80px of wash and now finishes on the section's own bottom
   edge, so the fill resolves straight into the footer's boundary instead of
   into a strip of ground. That is the better end for it, but it means
   --book-fade is now the ONLY thing standing between the copy and that edge. */
.book:has(.book__card--split) {
  --section-pad: 0px;
  /* And the ground the dissolve lands ON becomes the FOOTER's, not this
     section's usual purple wash. The card fills the section outright now, so
     the only place that ground is ever seen is the last 120px of the fade,
     which sits directly against the footer: two different light greys meeting
     at that seam put a step exactly where the gradient is trying to resolve.
     .book's --wash-secondary exists so the last LIGHT section is not a repeat
     of the difference section's wash, and here there is no light section left
     to tell apart. */
  background: var(--surface-2);
}
.book__frame:has(.book__card--split) { padding: 0; }

.book__card--split {
  display: grid;
  /* 45 / 55 (user, 23 Sep). The capture is the wider half: it is the thing
     with detail in it, and the copy is four short lines. */
  grid-template-columns: minmax(0, 45fr) minmax(0, 55fr);
  align-items: center;
  gap: var(--sp-10);
  text-align: left;
  padding: var(--sp-20) 0 var(--sp-20) var(--sp-16);
  border-radius: 0;
  box-shadow: none;
  /* The base card's own --brand fill has to go, or it paints an unmasked
     solid layer under ::before and the dissolve shows only on the capture. */
  background: none;
  /* Out to the rails: .container pads 20px purely to clear them, so handing
     that back puts the fill's edges on the frame's own verticals. Unwound
     below 992, where the rails come off and .container drops the gutter with
     them. */
  margin-inline: calc(-1 * var(--sp-5));
  /* The diagonal. It stays DARK across the whole left third and only opens up
     under the capture, because the copy sits on that third: a gradient that
     reached --primary-400 where the sub is would put white text on 3:1. */
  /* How far up from the fill's bottom edge the dissolve reaches, and how far
     the capture hangs below that edge. Both are shared with .book__shot so
     the two masks cannot drift apart. */
  /* 120 and not 150: the CTA's bottom sits 86px above the fill's edge at a
     1200 viewport, so a longer ramp puts the conversion button on a fill that
     is already half gone. */
  --book-fade: 120px;
  --shot-past: var(--sp-10);
}
/* THE FILL IS A LAYER, not the card's own background, so that the card can
   DISSOLVE DOWNWARD (user, 23 Sep: "fade out whole card towards bottom")
   without taking the copy with it. Masking the card itself was the obvious
   way and it is wrong: the CTA sits at about 78% of the card's height, inside
   the ramp, and a washed-out conversion button is a worse outcome than a hard
   bottom edge. The fill and the capture fade; .book__body is left alone.

   A mask rather than a gradient overlay, because an overlay would have to
   know the section's ground colour to fade INTO it. This simply stops being
   there, so it is right on any ground.

   It also replaces the radial lift the base card carries: the diagonal is
   already doing that job and the two together read as a smudge. */
.book__card--split::before {
  display: block;
  background: linear-gradient(115deg,
    var(--primary-800) 0%,
    var(--primary-700) 26%,
    var(--brand) 56%,
    var(--primary-300) 100%);
  -webkit-mask-image: linear-gradient(to top, transparent 0, #000 var(--book-fade));
  mask-image: linear-gradient(to top, transparent 0, #000 var(--book-fade));
}

.book__body {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-5);
}
.book__card--split .book__title { max-width: 16ch; }
.book__card--split .book__sub { max-width: 32rem; }
.book__card--split .book__cta { margin-top: var(--sp-4); }

/* The capture. Flush to the card's right edge with no border or radius on
   that side, which is what makes the window read as CONTINUING past the fill
   rather than as a picture placed inside it, and bled past the bottom padding
   for the same reason. */
.book__shot {
  position: relative;
  align-self: end;
  /* Taller than the space it is given, and pulled further than the card's
     bottom padding, so the window is CUT by the fill's edge instead of
     stopping neatly on it. That cut is the whole reference: a product that
     carries on past the frame reads as open, one that fits inside reads as a
     picture of a product. */
  height: 400px;
  margin-bottom: calc(-1 * var(--sp-20) - var(--shot-past));
  /* The SAME dissolve as the fill, measured from the fill's bottom edge and
     not its own: the capture hangs --shot-past below that edge, so its ramp
     has to start that much higher to finish level with it. Both are px off
     the bottom for exactly this reason; two percentage masks over boxes of
     different heights would never line up. */
  -webkit-mask-image: linear-gradient(to top,
    transparent 0, #000 calc(var(--book-fade) + var(--shot-past)));
  mask-image: linear-gradient(to top,
    transparent 0, #000 calc(var(--book-fade) + var(--shot-past)));
  overflow: hidden;
  border-top-left-radius: var(--r-md);
  border-top: 1px solid rgba(255, 255, 255, 0.28);
  border-left: 1px solid rgba(255, 255, 255, 0.28);
}
.book__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: left top;
  /* Framed from the TOP-LEFT and zoomed, so what shows is the question and
     the reasoning under it, with the dashboard pane running off the right.
     workspace.png is a 2560px two-pane window: shown whole in a 640px column
     its type renders at about 7px, which is a grey rectangle rather than a
     picture of a product. */
  transform: scale(1.9);
  transform-origin: 0 0;
}

/* The floating glyphs: white duotone marks drifting at the corners, tilted
   a few degrees so they read placed rather than templated. */
.book__float {
  position: absolute;
  color: rgba(255, 255, 255, 0.38);
  pointer-events: none;
}
.book__float svg { width: 40px; height: 40px; }
.book__float--1 { top: 18%; left: 9%; transform: rotate(-8deg); }
.book__float--2 { top: 24%; right: 10%; transform: rotate(7deg); }
.book__float--3 { bottom: 20%; left: 16%; transform: rotate(6deg); }
.book__float--4 { bottom: 26%; right: 17%; transform: rotate(-7deg); }

@media (max-width: 991px) {
  .book__frame { padding: var(--sp-6) var(--sp-4); }
  .book__card { padding: var(--sp-16) var(--sp-8); }
  /* One column, and the rail bleed comes off with the rails themselves. The
     capture stays: it moves under the copy and keeps bleeding off the bottom
     and the right, which is the same idea at a different aspect. */
  .book__card--split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-12);
    margin-inline: 0;
    padding: var(--sp-16) 0 var(--sp-16) var(--sp-8);
    --book-fade: 110px;
    --shot-past: var(--sp-6);
  }
  .book__shot {
    height: 300px;
    margin-bottom: calc(-1 * var(--sp-16) - var(--shot-past));
  }
  /* Scaled harder as the box narrows: the capture has to stay READABLE, and
     what it has to show is the question and the work under it. More of the
     window falls off the right and the bottom as the box shrinks, which is
     the intended cut rather than a loss. */
  .book__shot img { transform: scale(2.2); }
  .book__float svg { width: 28px; height: 28px; }
  .book__float--1 { top: 8%; left: 6%; }
  .book__float--2 { top: 10%; right: 6%; }
  .book__float--3 { bottom: 8%; left: 10%; }
  .book__float--4 { bottom: 10%; right: 10%; }
}
@media (max-width: 640px) {
  .book__float { display: none; }
  /* Doubled class on purpose: the base .book__card rule further down this
     same block resets the padding, and at equal specificity the later one
     wins, which put 24px back on the right and stopped the capture reaching
     the edge. */
  .book__card.book__card--split {
    padding: var(--sp-8) 0 var(--sp-8) var(--sp-6);
    --book-fade: 80px;
    --shot-past: var(--sp-4);
  }
  .book__shot {
    height: 220px;
    margin-bottom: calc(-1 * var(--sp-8) - var(--shot-past));
  }
  .book__shot img { transform: scale(3.1); }
  /* No frame padding (user, 20 Sep). The frame exists to hold the card off
     the dot-grid side bands, and those bands only exist from 1200px up; at
     phone width it was 24 and 16 of nothing, insetting the card from a page
     edge that .padding-global already holds it off. The card keeps its own
     padding, which is the space the copy actually needs. */
  .book__frame { padding: 0; }
  /* Left from here down. Centred text needs width to hold: on a phone the
     sub breaks to four or five short lines and a ragged centre reads worse
     than a clean left edge. Same line the decorative glyphs drop on, so the
     card becomes a plain panel and the alignment follows it. */
  .book__card {
    align-items: flex-start;
    text-align: left;
    /* 20 and 24 (user, 20 Sep). The card fills the page width now that the
       frame's inset is gone, so the generous inset it carried from the
       desktop layout was only making a tall box out of four short lines. */
    padding: var(--sp-5) var(--sp-6);
  }
}


/* ==== Chat vocabulary (moved from industry.css 20 Sep; shared by the hubs'
   askstory stages and index.html's difference loop). The product-font pinning
   is NOT here: each page scopes its own surface to --font-product. ==== */

/* The person's side: a white bubble off the right edge. */
.chat__q {
  align-self: flex-end;
  max-width: 90%;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--t-body);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
}

/* aiRA's side: plain text, the way the product itself answers. */
.chat__a {
  align-self: flex-start;
  max-width: 94%;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* Since late 19 Sep the three panels are three DIFFERENT slices of the
   product (Attio platform-page reference): the ask ends in tool-step rows
   and KPI tiles, the drill-down in a saved-audience card, the move in a
   proposal card wearing the approval grammar. Everything below the chat
   lines is drawn product UI, so Geist Mono may carry the metas; figures
   stay tabular in the display face. */

/* Tool-step rows, straight from the product: label left, meta right. */
.chat__tools {
  display: grid;
  gap: var(--sp-2);
}
.chat__tool {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  min-width: 0;
}
.chat__tool-c {
  flex: none;
  font-size: var(--t-body);
  line-height: 1;
  color: var(--ink-faint);
}
/* Illustration micro-glyph, so it keeps the product's own 16px and the
   purple the real tool rows use. */
.chat__tool-ic {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--secondary-400);
}
.chat__tool-t {
  flex: 1;
  font-size: var(--t-caption);
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat__tool-m {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--ink-3);
}

/* KPI tiles: the artifact the answer hands back. */
.chat__kpis {
  margin-top: auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-2);
}
.chat__kpis-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}
.chat__ver {
  padding: 2px 8px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--ink-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
}
.chat__kpi {
  display: grid;
  gap: 4px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
/* The tile's movement line: amber, because every one of these tracks a
   cohort growing the wrong way. */
.chat__kpi-s {
  font-size: var(--t-caption);
  font-weight: 500;
  color: var(--hold);
}
.chat__kpi-l {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-3);
}
.chat__kpi-n {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: var(--tr-title);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* The saved-audience card. */
.chat__aud {
  margin-top: auto;
  display: grid;
  gap: var(--sp-2);
  justify-items: start;
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.chat__aud-t {
  font-family: var(--font-display);
  font-size: var(--t-title-sm);
  font-weight: 600;
  letter-spacing: var(--tr-title);
  color: var(--ink);
}
.chat__aud-n {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.chat__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chat__chips span {
  padding: 3px 10px;
  font-size: var(--t-caption);
  font-weight: 500;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
}

/* The move: a proposal card pinned to the panel's foot, approval grammar
   visible down to the decision row. The row is drawn, not interactive:
   spans styled like the product's decide pills, inside the aria-hidden
   artifact. */
.chat__move {
  margin-top: auto;
  display: grid;
  gap: var(--sp-2);
  justify-items: start;
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.chat__move-t {
  font-family: var(--font-display);
  font-size: var(--t-title-sm);
  font-weight: 600;
  letter-spacing: var(--tr-title);
  color: var(--ink);
}
.chat__move-d {
  font-size: var(--t-caption);
  line-height: 1.5;
  color: var(--ink-2);
}
.chat__act {
  display: flex;
  gap: 6px;
  margin-top: var(--sp-1, 4px);
}
/* The real product's approval surface (20 Sep, user screenshot of the
   product): a header row with the move quoted in the title and a risk-class
   tag, one explainer row, then the footer with the Draft pill left and the
   two real buttons right: Save draft, its white secondary, and Submit for
   approval, its PRIMARY COLOUR (the deep indigo, --primary-500 here). Rows
   divide on full-bleed hairlines inside the card. */
.chat__move-h {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
}
.chat__move-tag {
  flex: none;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--hold);
  background: var(--hold-bg);
  border-radius: var(--r-pill);
  padding: 3px 10px;
}
.chat__move-b {
  font-size: var(--t-caption);
  line-height: 1.5;
  color: var(--ink-2);
  width: 100%;
  margin-inline: calc(-1 * var(--sp-5));
  padding: var(--sp-3) var(--sp-5) 0;
  border-top: 1px solid var(--border);
  box-sizing: content-box;
}
.chat__move-f {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3);
  width: 100%;
  margin-inline: calc(-1 * var(--sp-5));
  padding: var(--sp-3) var(--sp-5) 0;
  border-top: 1px solid var(--border);
  box-sizing: content-box;
}
.chat__draft {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-caption);
  font-weight: 500;
  color: var(--ink-2);
  background: var(--bg-subtle);
  border-radius: var(--r-pill);
  padding: 4px 12px;
}
.chat__draft-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink-3);
}
.chat__act span {
  font-size: var(--t-caption);
  font-weight: 600;
  /* 8px (--r-sm), user 20 Sep. Set on the shared rule because both -a and -h
     take it; a pill read as two status chips rather than as the decision pair
     the card is asking for. */
  border-radius: var(--r-sm);
  padding: 5px 13px;
  border: 1px solid transparent;
}
.chat__act-a { color: var(--on-brand); background: var(--primary-500); }
.chat__act-h { color: var(--ink); background: var(--surface); border-color: var(--border-strong); }

.chat__chips span { background: var(--bg-subtle); }

/* ==========================================================================
   Glyph matrix
   --------------------------------------------------------------------------
   A canvas field of small mono glyphs that shift a few at a time, injected by
   js/glyph-matrix.js into any empty [data-glyph-matrix] host. Ported by hand
   on 22 Sep from a Svelte component the user supplied (sv-animations
   GlyphMatrix); none of its code was usable here, only its idea and its four
   knobs.

   THIS IS THE GROUND RULE ONLY: the canvas fills its host and takes no
   pointer. Where the field sits, how big its pitch is, what colour it takes
   and how it is masked all belong to the page that mounts it, because a
   field's whole job is to be shaped around the copy it sits behind.

   The host must be sized by the page and must set `color`: the script reads
   its computed colour and never carries one of its own, so this stays inside
   the rule that only tokens.css holds colour values.
   ========================================================================== */

.glyphs__canvas {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The script draws nothing under reduced motion beyond one static frame, so
   the field stays as texture rather than disappearing. Nothing here animates:
   the guard lives in the script, where the timer is. */
