/* =========================================================
   IOMA LABS — console component layer

   The operator-console components the marketing site never needed. This is
   the layer the design-system inventory enumerates but `components.css` does
   not carry: real controls, rows, fields, tables and interaction states.

   DELIBERATELY SEPARATE from components.css. That file is the marketing
   site's layer and sets page-level rules (an 88px `section` rhythm, among
   others) that would wreck a console shell. A console page links the token
   files plus THIS file, and nothing else.

   The interaction recipe is ported from the donor's live implementation
   (`src/gp/gp.css` — the `gpi-*` utilities), which is the same mechanism as
   `state.css`: one class carries hover, press and focus for any interactive
   surface; all geometry stays inline, read from tokens. Ioma's values, the
   donor's mechanics.
   ========================================================= */

/* ---------------------------------------------------------
   INTERACTION — one recipe, every interactive surface.
   4% hover / 8% press. Nothing moves. Focus is the 2px accent ring.
   --------------------------------------------------------- */
.io-act{
  cursor:pointer;
  transition:background var(--dur-fast) var(--ease-linear),
             color var(--dur-fast) var(--ease-linear),
             border-color var(--dur-fast) var(--ease-linear);
}
@media (hover:hover){ .io-act:hover{ background:var(--wash-hover); } }
.io-act:active{ background:var(--wash-press); }
.io-act:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:2px; }

/* on a row, the ring sits inside so it cannot be clipped by the row above */
.io-act-row:focus-visible{ outline-offset:-2px; }

/* on an ink or otherwise dark fill the wash inverts to paper */
.io-act-ink{ cursor:pointer; transition:background var(--dur-fast) var(--ease-linear); }
@media (hover:hover){ .io-act-ink:hover{ background:var(--accent); border-color:var(--accent); } }
.io-act-ink:active{ background:var(--accent-ink); border-color:var(--accent-ink); }
.io-act-ink:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:2px; }

.io-act[disabled],.io-act[aria-disabled="true"],
.io-act-ink[disabled],.io-act-ink[aria-disabled="true"]{ opacity:.4; pointer-events:none; }

/* ---------------------------------------------------------
   CONTROLS
   --------------------------------------------------------- */
.io-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--gap-sm);
  min-height:var(--control-sm); padding:0 var(--pad-pill-sm);
  font-family:var(--font-mono);
  font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  white-space:nowrap; text-align:center; text-decoration:none;
  border:var(--stroke-hairline) solid var(--rule-strong);
  background:transparent; color:var(--ink-soft);
  cursor:pointer;
  transition:background var(--dur-fast) var(--ease-linear),
             color var(--dur-fast) var(--ease-linear),
             border-color var(--dur-fast) var(--ease-linear);
}
@media (hover:hover){ .io-btn:hover{ background:var(--wash-hover); color:var(--ink); border-color:var(--ink); } }
.io-btn:active{ background:var(--wash-press); }
.io-btn:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:2px; }
.io-btn[disabled],.io-btn[aria-disabled="true"]{ opacity:.4; pointer-events:none; }
/* selection is a CSS concern, driven off the accessible state — never a
   computed inline colour. A pressed control reads as an ink fill. */
.io-btn[aria-pressed="true"]{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
@media (hover:hover){ .io-btn[aria-pressed="true"]:hover{ background:var(--accent); border-color:var(--accent); } }

/* one rung up — the control a surface is actually for */
.io-btn-md{
  min-height:var(--control-md); padding:0 var(--pad-pill-md);
  font-size:var(--ctl-type-md); line-height:var(--ctl-line-md);
}
/* two rungs up — a gate or a single full-screen decision */
.io-btn-lg{
  min-height:var(--control-lg); padding:0 var(--pad-pill-lg);
  font-size:var(--ctl-type-lg); line-height:var(--ctl-line-lg);
}
/* fills its row. For stacked decisions (sign-in, the mobile gate). */
.io-btn-block{ display:flex; width:100%; }
/* a control that is also the way to the surface you are on */
.io-btn[aria-current="page"]{ color:var(--accent); border-color:var(--accent); }

/* variants. One primary per surface. */
.io-btn-primary{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
@media (hover:hover){ .io-btn-primary:hover{ background:var(--accent); color:var(--paper); border-color:var(--accent); } }
.io-btn-primary:active{ background:var(--accent-ink); border-color:var(--accent-ink); }

/* tertiary — dismissive. No border: Cancel, Back, Dismiss. */
.io-btn-quiet{ border-color:transparent; }
@media (hover:hover){ .io-btn-quiet:hover{ border-color:var(--rule-strong); } }
/* secondary — a row's or a card's own action. The sunken fill says "this is a
   separate tap target" without competing with the surface's one primary. The
   plain outline .io-btn is for siblings inside a button GROUP only. */
.io-btn-secondary{ background:var(--paper-sunken); border-color:transparent; color:var(--ink); }
@media (hover:hover){ .io-btn-secondary:hover{ background:var(--wash-press); border-color:transparent; } }
.io-btn-secondary:active{ background:var(--rule); }
/* the group's selected member reads as the ink fill, whatever its variant */
.io-btn-secondary[aria-pressed="true"]{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
@media (hover:hover){ .io-btn-secondary[aria-pressed="true"]:hover{ background:var(--accent); border-color:var(--accent); } }
/* a glyph-only button at the control rung */
.io-btn-icon{ width:var(--control-sm); padding:0; }
.io-btn-icon svg{ width:var(--icon-sm); height:var(--icon-sm); }

/* the critical family. Outlined on a surface — it announces the confirm it
   opens. Filled ONLY as the committing control inside that confirm. */
.io-btn-danger{ color:var(--sev-critical-ink); border-color:var(--sev-critical); background:transparent; }
@media (hover:hover){ .io-btn-danger:hover{ background:var(--sev-critical); color:var(--paper); border-color:var(--sev-critical); } }
.io-btn-danger:active{ background:var(--sev-critical-ink); border-color:var(--sev-critical-ink); color:var(--paper); }
.io-btn-danger-fill{ color:var(--paper); background:var(--sev-critical); border-color:var(--sev-critical); }
@media (hover:hover){ .io-btn-danger-fill:hover{ background:var(--sev-critical-ink); color:var(--paper); border-color:var(--sev-critical-ink); } }
.io-btn-danger-fill:active{ background:var(--sev-critical-ink); border-color:var(--sev-critical-ink); }

/* a square control carrying one glyph. 44px target via the parent, per doctrine. */
.io-iconbtn{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:var(--control-sm); height:var(--control-sm);
  min-width:var(--touch-min); min-height:var(--touch-min);
  background:transparent; border:none; color:var(--ink-soft);
  cursor:pointer;
  transition:background var(--dur-fast) var(--ease-linear), color var(--dur-fast) var(--ease-linear);
}
@media (hover:hover){ .io-iconbtn:hover{ background:var(--wash-hover); color:var(--ink); } }
.io-iconbtn:active{ background:var(--wash-press); }
.io-iconbtn:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-2px; }
.io-iconbtn svg{ width:var(--icon-md); height:var(--icon-md); }

/* a pill: a small pressable label, or a static count */
.io-pill{
  display:inline-flex; align-items:center; gap:var(--gap-xs);
  min-height:var(--inline-lg); padding:0 var(--gap-md);
  font-family:var(--font-mono);
  font-size:var(--label-type-sm); line-height:var(--label-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  white-space:nowrap; flex:none;
  border:var(--stroke-hairline) solid var(--rule); background:transparent; color:var(--ink-soft);
}
button.io-pill{ cursor:pointer; transition:background var(--dur-fast) var(--ease-linear), color var(--dur-fast) var(--ease-linear), border-color var(--dur-fast) var(--ease-linear); }
@media (hover:hover){ button.io-pill:hover{ background:var(--wash-hover); color:var(--ink); } }
button.io-pill:active{ background:var(--wash-press); }
button.io-pill:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:2px; }
.io-pill[aria-pressed="true"]{ background:var(--ink); color:var(--paper); border-color:var(--ink); }

/* a segmented control: siblings sharing one track, exactly one selected */
.io-seg{ display:inline-flex; border:var(--stroke-hairline) solid var(--rule-strong); flex:none; }
.io-seg > button{
  min-height:var(--control-sm); padding:0 var(--pad-pill-sm);
  font-family:var(--font-mono);
  font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  background:transparent; color:var(--ink-soft); border:none; cursor:pointer;
  white-space:nowrap;
  transition:background var(--dur-fast) var(--ease-linear), color var(--dur-fast) var(--ease-linear);
}
.io-seg > button + button{ border-left:var(--stroke-hairline) solid var(--rule-strong); }
@media (hover:hover){ .io-seg > button:hover{ background:var(--wash-hover); color:var(--ink); } }
.io-seg > button:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-2px; }
.io-seg > button[aria-pressed="true"],
.io-seg > button[aria-selected="true"]{ background:var(--ink); color:var(--paper); }

/* ---------------------------------------------------------
   ROW-SHAPED PRESSABLES — the row skeleton, made a target.
   Selection lives on the accessible attribute (aria-current, aria-selected,
   aria-pressed), never on a computed inline colour, so state and paint
   cannot disagree. The edge carries selection; the wash carries hover.
   --------------------------------------------------------- */
.io-rowbtn,.io-navrow,.io-option,.io-reply{
  position:relative; width:100%; display:flex; align-items:center; gap:var(--gap-md);
  background:none; border:none; cursor:pointer; text-align:left;
  font:inherit; color:var(--ink);
  transition:background var(--dur-fast) var(--ease-linear), color var(--dur-fast) var(--ease-linear), border-color var(--dur-fast) var(--ease-linear);
}
@media (hover:hover){ .io-rowbtn:hover,.io-navrow:hover,.io-option:hover,.io-reply:hover{ background:var(--wash-hover); } }
.io-rowbtn:active,.io-navrow:active,.io-option:active,.io-reply:active{ background:var(--wash-press); }
.io-rowbtn:focus-visible,.io-navrow:focus-visible,.io-option:focus-visible,.io-reply:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-2px; }

/* a list row that is a target: chooser, record lists */
.io-rowbtn{ min-height:var(--row-md); padding:var(--gap-item) var(--pad-box-x-sm); border-bottom:var(--stroke-hairline) solid var(--rule); }

/* a surface in the shell nav. Current = the press wash + accent edge. One
   selected treatment: the fill. Hover is the lighter wash, so the two cannot
   be confused; there is no card. */
.io-navrow{
  min-height:var(--row-sm); padding:0 var(--shell-gutter); color:var(--ink-soft);
  /* the edge is drawn INSIDE the box, so the row's content starts at the same
     x as the wordmark, the picker and the section labels — one left line down
     the whole rail. A border would push the row 2px right of everything else. */
  box-shadow:inset var(--stroke-emphasis) 0 0 0 transparent;
}
.io-navrow[aria-current="page"]{ background:var(--wash-press); box-shadow:inset var(--stroke-emphasis) 0 0 0 var(--accent); color:var(--ink); }

/* an option in a listbox. A tray is not a nav — selection reads as a trailing
   mark plus ink weight, never as an edge bar (I15, canonical-instances.md).
   The edge bar is for docked chrome: rails and tabs. */
.io-option{ align-items:baseline; padding:var(--gap-md) var(--shell-gutter); color:var(--ink-soft); }
.io-option[aria-selected="true"]{ color:var(--ink); }
/* the trailing check — the same mark the Menu uses (.io-menuitem-mark), so a
   picker and a menu say "this one" the same way. No fill, no weight: a filled
   row in a tray reads as pressed. Present in the DOM on every row so selecting
   one does not shift the labels; hidden until the row is the current one. */
.io-option-mark{ flex:none; width:var(--icon-sm); height:var(--icon-sm); color:var(--accent); visibility:hidden; }
.io-option[aria-selected="true"] .io-option-mark{ visibility:visible; }

/* a block of prose that can be focused for its audit */
.io-reply{ display:block; padding:var(--gap-item) var(--gap-lg); border-left:var(--stroke-emphasis) solid transparent; }
.io-reply[aria-pressed="true"]{ background:var(--paper-raised); border-left-color:var(--accent); }

/* a tab — one of a set under a rule; the selected one carries the accent edge */
.io-tab{
  min-height:var(--control-md); padding:0 var(--pad-pill-md); margin-bottom:-1px;
  font-family:var(--font-mono);
  font-size:var(--ctl-type-md); line-height:var(--ctl-line-md);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-soft); background:none; border:none;
  border-bottom:var(--stroke-emphasis) solid transparent; cursor:pointer; white-space:nowrap;
  transition:color var(--dur-fast) var(--ease-linear), border-color var(--dur-fast) var(--ease-linear), background var(--dur-fast) var(--ease-linear);
}
@media (hover:hover){ .io-tab:hover{ color:var(--ink); background:var(--wash-hover); } }
.io-tab:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-2px; }
.io-tab[aria-pressed="true"],.io-tab[aria-selected="true"]{ color:var(--ink); border-bottom-color:var(--accent); }

/* a step in a wizard rail — the Tab's geometry with a number in front. Three
   states: to come, current, done. Sits under the same rule a tab row does. */
.io-step{
  flex:none; min-width:0; min-height:var(--control-md); margin-bottom:-1px;
  display:flex; align-items:center; justify-content:center; gap:var(--gap-sm);
  padding:0 var(--pad-pill-md);
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-faint); background:none; border:none;
  border-bottom:var(--stroke-emphasis) solid transparent; cursor:pointer;
  transition:color var(--dur-fast) var(--ease-linear), border-color var(--dur-fast) var(--ease-linear), background var(--dur-fast) var(--ease-linear);
}
.io-step > .io-step-n{ font-size:var(--meta-type-sm); letter-spacing:var(--tracking-data); text-transform:none; color:var(--ink-faint); }
.io-step > .io-step-label{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@media (hover:hover){ .io-step:hover{ background:var(--wash-hover); color:var(--ink); } }
.io-step:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-2px; }
.io-step[data-done="true"]{ color:var(--ink-soft); border-bottom-color:var(--rule-strong); }
.io-step[aria-current="step"]{ color:var(--ink); border-bottom-color:var(--accent); }

/* a text link inside prose or a note */
.io-link{ color:var(--accent); text-decoration:underline; text-underline-offset:2px; cursor:pointer; }
@media (hover:hover){ .io-link:hover{ color:var(--accent-ink); } }
.io-link:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:2px; }

/* ---------------------------------------------------------
   TAG — a short uppercase label on a soft ground. Names a state or a kind.
   Never pressable (a pressable label is .io-pill). Colour is meaning: the
   variant names the family; a computed hue is passed inline, never here.
   --------------------------------------------------------- */
.io-tag{
  display:inline-flex; align-items:center; gap:var(--gap-xs); flex:none;
  font-family:var(--font-mono);
  font-size:var(--label-type-sm); line-height:var(--label-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  padding:2px var(--gap-sm); white-space:nowrap;
  color:var(--ink-soft); background:var(--paper-sunken);
}
.io-tag-accent{ color:var(--accent-ink); background:var(--accent-soft); }
.io-tag-critical{ color:var(--sev-critical-ink); background:var(--sev-critical-soft); }
.io-tag-high{ color:var(--sev-high-ink); background:var(--sev-high-soft); }
.io-tag-ok{ color:var(--status-ok-ink); background:var(--status-ok-soft); }

/* ---------------------------------------------------------
   FORMS — the gap the inventory named and nothing had built
   --------------------------------------------------------- */
.io-field{ display:flex; flex-direction:column; gap:var(--gap-sm); min-width:0; }
.io-field > label{
  font-family:var(--font-mono);
  font-size:var(--label-type-sm); line-height:var(--label-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase; color:var(--ink-faint);
}
.io-field > .io-help{
  font-family:var(--font-mono);
  font-size:var(--meta-type-sm); line-height:var(--line-data); color:var(--ink-faint);
}
.io-input,.io-select,.io-textarea{
  min-height:var(--control-md); padding:0 var(--gap-lg);
  font-family:var(--font-body); font-size:var(--type-body-sm);
  background:var(--paper-raised); color:var(--ink);
  border:var(--stroke-hairline) solid var(--rule-strong);
  min-width:0; width:100%;
  transition:border-color var(--dur-fast) var(--ease-linear);
}
.io-textarea{ padding:var(--gap-md) var(--gap-lg); min-height:calc(var(--control-md) * 2); line-height:var(--line-body); resize:vertical; }
.io-input::placeholder,.io-textarea::placeholder{ color:var(--ink-faint); opacity:1; }
.io-input:hover,.io-select:hover,.io-textarea:hover{ border-color:var(--ink-faint); }
/* a field the form named as missing on submit. The border says which; the
   banner above says why. Never the only signal. */
.io-input[aria-invalid="true"],.io-select[aria-invalid="true"],.io-textarea[aria-invalid="true"],[data-invalid="true"]{ border-color:var(--sev-high); }
.io-input:focus-visible,.io-select:focus-visible,.io-textarea:focus-visible{
  outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-1px; border-color:var(--accent);
}
.io-field[data-invalid="true"] .io-input,
.io-field[data-invalid="true"] .io-textarea{ border-color:var(--sev-critical); }
/* a select is pinned with height, not min-height (see ramp.css). The compact
   rung is for a value set in a row of other things — mono, because it is data. */
.io-select{ height:var(--control-md); }
.io-select-sm,.io-input-sm{
  height:var(--control-sm); min-height:var(--control-sm); padding:0 var(--gap-md);
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); width:auto;
}
/* label + control on one line — a setting row's form */
.io-field-row{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--gap-md) var(--gap-lg); }
.io-field-row > label{ font-size:var(--body-type-sm); line-height:var(--body-line-sm); color:var(--ink); }

/* a toggle. Track and knob come off the inline axis; nothing moves but the knob. */
.io-toggle{
  position:relative; flex:none; display:inline-block;
  width:44px; height:var(--inline-md);
  border-radius:var(--radius-full);
  background:var(--rule-strong); border:none; cursor:pointer; padding:0;
  transition:background var(--dur-fast) var(--ease-linear);
}
.io-toggle::after{
  content:""; position:absolute; top:2px; left:2px;
  width:calc(var(--inline-md) - 4px); height:calc(var(--inline-md) - 4px);
  border-radius:var(--radius-full); background:var(--paper-raised);
  transition:transform var(--dur-inplace) var(--ease-move);
}
.io-toggle[aria-checked="true"]{ background:var(--status-ok); }
.io-toggle[aria-checked="true"]::after{ transform:translateX(20px); }
.io-toggle:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:2px; }

/* ---------------------------------------------------------
   TYPOGRAPHY — the console's written surfaces
   --------------------------------------------------------- */

/* THE section label. 130 hand-rolled copies of this collapsed into one class.
   Margin and line-height stay inline: the rhythm above a label, and its
   leading, are the caller's decision. font-weight is deliberately NOT set —
   28 of the instances are <p> that inherited 400, and forcing 500 here would
   have made the class change what it replaced. The <h2> instances carry their
   own weight inline. */
.io-sec-label{
  font-family:var(--font-mono);
  font-size:var(--label-type-md);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-faint);
  padding-bottom:var(--gap-md);
  border-bottom:var(--stroke-hairline) solid var(--rule);
}
/* the same label with no rule under it — inside a panel that already has one */
.io-sub-label{
  font-family:var(--font-mono);
  font-size:var(--label-type-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-faint);
}
/* a mono footnote: what this reading means, or what it cannot tell you */
.io-note{
  font-family:var(--font-mono);
  font-size:var(--meta-type-sm); line-height:var(--line-data);
  color:var(--ink-faint);
}
/* the paragraph that frames a surface */
.io-lede{
  font-size:var(--type-body); line-height:var(--line-body);
  color:var(--ink-soft); max-width:var(--measure-reading);
}
/* a value a reader might copy */
.io-val{ font-family:var(--font-mono); font-size:var(--type-data); color:var(--ink); }
/* the compact datum in a data row — mono at data-sm, ink or soft. Column width
   stays inline: that is the caller's alignment. */
.io-val-sm{ font-family:var(--font-mono); font-size:var(--type-data-sm); color:var(--ink); }
.io-val-sm-soft{ font-family:var(--font-mono); font-size:var(--type-data-sm); color:var(--ink-soft); }
/* a mono footnote at data-sm, faint — the line under a figure that qualifies it */
.io-note-data{ font-family:var(--font-mono); font-size:var(--type-data-sm); color:var(--ink-faint); }
/* the mono lede — a surface framed in the data voice (the sign-in gate) */
.io-lede-mono{ font-family:var(--font-mono); font-size:var(--type-lede); line-height:var(--line-lede); color:var(--ink); }
/* tabular figures are opt-in: a column of numbers wants them, a lone value in
   a mono face is already fixed-width. */
.io-tabular{ font-variant-numeric:tabular-nums; }
.io-figure{
  font-family:var(--font-display); font-weight:var(--weight-bold);
  font-size:var(--figure-type-md); line-height:var(--figure-line-md);
  letter-spacing:var(--tracking-display); font-variant-numeric:tabular-nums; color:var(--ink);
}

/* The heading inside a panel or a figure that already owns its boundary: the
   section label WITHOUT the rule. The platform hand-rolled it eight times
   because .io-sec-label draws a hairline it did not want. */
.io-panel-label{
  font-family:var(--font-mono);
  font-size:var(--label-type-md);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-faint);
}
/* typography/Eyebrow, console form — the accent kicker above a block */
.io-eyebrow{
  font-family:var(--font-mono);
  font-size:var(--label-type-md);
  letter-spacing:var(--tracking-eyebrow); text-transform:uppercase;
  color:var(--accent);
}
/* an inline datum — mono, data-tracked, faint. .io-note is its paragraph form:
   same face and size, footnote leading. */
.io-meta{
  font-family:var(--font-mono);
  font-size:var(--meta-type-sm);
  letter-spacing:var(--tracking-data);
  color:var(--ink-faint);
}
/* the footnote one rung up — a reading the surface wants at body scale */
.io-note-md{
  font-family:var(--font-mono);
  font-size:var(--meta-type-md); line-height:var(--line-data);
  color:var(--ink-faint);
}
/* the key half of a detail row, at the meta rung the console reads at. Its
   column width stays inline: that is the caller's alignment, not the type. */
.io-key{
  font-family:var(--font-mono);
  font-size:var(--meta-type-md);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-faint);
}
/* typography/Prose, console form — body copy at the console's dense rung */
.io-body{ font-size:var(--body-type-sm); line-height:var(--body-line-sm); color:var(--ink); }
.io-body-soft{ font-size:var(--body-type-sm); line-height:var(--body-line-sm); color:var(--ink-soft); }
/* the reading rung — a source's text, an answer under review, a framing paragraph */
.io-body-md{ font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink); }
.io-body-md-soft{ font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink-soft); }
/* the name of a thing — a source, a dataset, an instance. Display face at the
   heading rung, no tracking. Not a section title: that is .io-h2. */
.io-name{ font-family:var(--font-display); font-weight:var(--weight-bold); font-size:var(--heading-type-md); line-height:var(--heading-line-md); color:var(--ink); }
.io-name-sm{ font-family:var(--font-display); font-weight:var(--weight-bold); font-size:var(--heading-type-sm); line-height:var(--heading-line-sm); color:var(--ink); }

/* controls/Chip, console form: a hued mark and a word. The mark is the one
   round thing in the system — it is a status mark, not a box. The hue is
   passed inline because it is computed from the datum.

   The class owns LAYOUT ONLY. Its label child keeps its own type class, because
   the console marks two different kinds of thing this way: a tier or a state
   (.io-sub-label — mono, uppercase) and a name or an id (.io-body, .io-meta —
   body face, or lowercase mono). Setting the type here silently uppercased the
   capability names and the tutor ids the first time it shipped. */
.io-chip{ display:inline-flex; align-items:center; gap:var(--gap-sm); flex:none; }
.io-chip-mark{ flex:none; width:var(--mark-sm); height:var(--mark-sm); border-radius:var(--radius-full); }

/* ---------------------------------------------------------
   STRUCTURE
   --------------------------------------------------------- */
.io-measure{ max-width:1180px; }
.io-panel{ background:var(--paper-raised); padding:var(--pad-box-y-sm) var(--pad-box-x-sm); }
/* a bounded surface — raised, hairline, square. The console's card. */
.io-surface{ background:var(--paper-raised); border:var(--stroke-hairline) solid var(--rule); }
.io-hairgrid{
  display:grid; gap:var(--stroke-hairline);
  background:var(--rule); border:var(--stroke-hairline) solid var(--rule);
}

/* surface and section titles */
.io-h1{
  font-family:var(--font-display); font-weight:var(--weight-bold);
  font-size:var(--type-display-md); line-height:var(--line-display-md);
  letter-spacing:var(--tracking-display);
}
.io-h2{
  font-family:var(--font-display); font-weight:var(--weight-bold);
  font-size:var(--heading-type-lg); line-height:var(--heading-line-lg);
}
/* the supporting line under a value or a title */
.io-small{ font-size:var(--meta-type-lg); color:var(--ink-soft); }
.io-small-ink{ font-size:var(--meta-type-lg); line-height:var(--line-body-sm); color:var(--ink); }
.io-small-faint{ font-size:var(--meta-type-lg); line-height:var(--line-body-sm); color:var(--ink-faint); }
/* .io-lede's ink form — the paragraph is the content, not the frame */
.io-lede-ink{ font-size:var(--type-body); line-height:var(--line-body); color:var(--ink); }

/* a detail row — the row skeleton, horizontal, hairline-separated.
   The doctrine's "no divider under the last row" is opt-in via .io-drows on
   the container: applied from the cell up it would silently drop a rule the
   caller drew on purpose, and applied unevenly it segments the line. */
.io-drow{
  display:flex; flex-wrap:wrap; gap:var(--gap-md) var(--gap-lg);
  padding:var(--gap-md) 0;
  border-bottom:var(--stroke-hairline) solid var(--rule);
}
.io-drows > .io-drow:last-child{ border-bottom:none; }
.io-drow-key{
  flex:none; min-width:150px;
  font-family:var(--font-mono);
  font-size:var(--label-type-sm); letter-spacing:var(--tracking-label);
  text-transform:uppercase; color:var(--ink-faint);
}

/* ---------------------------------------------------------
   TABLES — mono, tabular, scrolled rather than crushed
   --------------------------------------------------------- */
.io-table{ width:100%; border-collapse:collapse; }
.io-scroll{ overflow-x:auto; border:var(--stroke-hairline) solid var(--rule); }
.io-th{
  text-align:left; padding:var(--gap-md) var(--gap-lg);
  background:var(--paper-sunken);
  border-bottom:var(--stroke-hairline) solid var(--rule);
  font-family:var(--font-mono);
  font-size:var(--label-type-md);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  color:var(--ink-faint); font-weight:var(--weight-medium);
}
.io-td{
  padding:var(--gap-md) var(--gap-lg);
  border-bottom:var(--stroke-hairline) solid var(--rule);
  font-family:var(--font-mono); font-size:var(--type-data-sm); color:var(--ink);
}
.io-td-soft{ color:var(--ink-soft); }
.io-td-num{ font-size:var(--type-data); font-variant-numeric:tabular-nums; }
.io-td-nowrap{ white-space:nowrap; }
/* Opt-in, and applied to the ROW so every cell agrees. Set on the table.
   As a cell rule it segmented the hairline wherever one column had been
   converted and its neighbour had not. */
.io-table-flush tbody tr:last-child > *{ border-bottom:none; }

/* ---------------------------------------------------------
   OVERLAYS — the things that genuinely float. Each declares its dismissal.
   Modal: Escape, the scrim, Cancel. Toast: a timeout and a close.
   --------------------------------------------------------- */
.io-scrim{
  position:fixed; inset:0; z-index:var(--z-modal);
  display:flex; align-items:center; justify-content:center; padding:var(--gap-item);
}
/* the scrim is itself the dismiss target, so it is a button */
.io-scrim-hit{ position:absolute; inset:0; background:var(--scrim); border:none; cursor:pointer; padding:0; }
.io-dialog{
  position:relative; width:100%; max-width:var(--overlay-w);
  background:var(--paper-raised); border:var(--stroke-hairline) solid var(--rule-strong);
  box-shadow:var(--shadow-3);
}
.io-dialog-head{ padding:var(--gap-item) var(--pad-box-x-sm); border-bottom:var(--stroke-hairline) solid var(--rule); background:var(--paper-sunken); }
.io-dialog-head > h2{
  font-family:var(--font-display); font-weight:var(--weight-bold);
  font-size:var(--heading-type-md); line-height:var(--heading-line-md);
  letter-spacing:var(--tracking-display); color:var(--ink);
}
.io-dialog-body{ padding:var(--pad-box-y-sm) var(--pad-box-x-sm); display:flex; flex-direction:column; gap:var(--gap-item); }
.io-dialog-body > p{ font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink-soft); }
.io-dialog-foot{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--gap-sm);
  padding:var(--gap-item) var(--pad-box-x-sm); border-top:var(--stroke-hairline) solid var(--rule);
}

/* navigation/Menu — the anchored tray: an instance switcher, an account menu,
   a role picker. TWO LAYERS, always: a fixed dismiss hit under a positioned
   tray, so an outside click closes without a scrim darkening the work behind
   it — a menu is not modal, and dimming the page would say it were.

   The anchor is the SMALLEST wrapper that contains the trigger — a label and
   its button, never the whole group — so the tray opens flush to the control
   rather than to the group's box. Dismissal is declared: Escape, the hit
   layer, or choosing a row. */
.io-menu-anchor{ position:relative; }
.io-menu-hit{
  position:fixed; inset:0; z-index:var(--z-dropdown);
  background:none; border:none; padding:0; cursor:default;
}
.io-menu{
  position:absolute; z-index:calc(var(--z-dropdown) + 1);
  top:calc(100% + var(--menu-gap)); left:0;
  min-width:var(--menu-w);
  background:var(--paper-raised);
  border:var(--stroke-hairline) solid var(--rule-strong);
  box-shadow:var(--shadow-2);
  animation:io-menu-in var(--dur-inplace) var(--ease-enter);
}
/* four placements, and no fifth. The trigger's position on screen picks it. */
.io-menu[data-side="top"]{ top:auto; bottom:calc(100% + var(--menu-gap)); }
.io-menu[data-align="end"]{ left:auto; right:0; }
.io-menu[data-align="stretch"]{ left:0; right:0; min-width:0; }
/* a tray taller than its cap scrolls, and the scroll stops at its own end */
.io-menu-scroll{ max-height:var(--menu-max-h); overflow-y:auto; overscroll-behavior:contain; }
/* a group heading inside the tray, and the rule that ends a group */
.io-menu-label,.io-group-label{
  padding:var(--gap-md) var(--gap-md) var(--gap-xs);
  font-family:var(--font-mono); font-size:var(--label-type-xs); line-height:var(--label-line-xs);
  letter-spacing:var(--tracking-eyebrow); text-transform:uppercase; color:var(--ink-faint);
}
.io-menu-sep{ height:var(--stroke-hairline); background:var(--rule); }
/* the same eyebrow, outside a tray: heads a group of rail rows */
.io-group-label{ padding:0; white-space:nowrap; }
/* a row in the tray. Geometry is the row skeleton: leading glyph, label,
   trailing mark. Selection is a trailing check, NOT a fill — a filled row in
   a menu reads as pressed. */
.io-menuitem{
  position:relative; width:100%; min-height:var(--row-sm);
  display:flex; align-items:center; gap:var(--gap-md);
  padding:0 var(--gap-md);
  background:none; border:none; cursor:pointer; text-align:left;
  font-family:inherit; font-size:var(--body-type-sm); line-height:var(--body-line-sm);
  color:var(--ink);
  transition:background var(--dur-fast) var(--ease-linear), color var(--dur-fast) var(--ease-linear);
}
@media (hover:hover){ .io-menuitem:hover{ background:var(--wash-hover); } }
.io-menuitem:active{ background:var(--wash-press); }
.io-menuitem:focus-visible{ outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-2px; }
.io-menuitem[aria-disabled="true"]{ opacity:.4; cursor:default; }
.io-menuitem[aria-disabled="true"]:hover{ background:none; }
/* the mark that says "this one". Accent, trailing, and the only hue in a tray. */
.io-menuitem-mark{ flex:none; width:var(--icon-sm); height:var(--icon-sm); color:var(--accent); }
/* the trailing meta a row may carry instead of a mark — a page, a count, a key */
.io-menuitem-meta{
  flex:none; font-family:var(--font-mono); font-size:var(--meta-type-sm);
  letter-spacing:var(--tracking-data); color:var(--ink-faint);
}
@keyframes io-menu-in{ from{ opacity:0; } to{ opacity:1; } }

/* ---------------------------------------------------------
   FEEDBACK — moved here from components.css so each class has one home.
   --------------------------------------------------------- */
/* feedback/Toast — one at a time; a second event replaces the first */
.io-toast{
  position:fixed; left:50%; bottom:var(--gap-block); transform:translateX(-50%);
  z-index:var(--z-toast);
  display:flex; align-items:center; gap:var(--gap-lg);
  min-height:var(--control-md); padding:var(--gap-md) var(--pad-pill-md);
  max-width:var(--overlay-w);
  background:var(--bg-surface); color:var(--text-primary);
  border:var(--stroke-hairline) solid var(--border-hairline);
  box-shadow:var(--shadow-3);
  font-size:var(--type-body-sm); line-height:var(--line-body-sm);
  animation:io-toast-in var(--dur-inplace) var(--ease-enter);
}
.io-toast[data-tone="fail"]{ border-color:var(--sev-critical); }
.io-toast[data-tone="fail"] .io-toast-mark{ background:var(--sev-critical); }
.io-toast-mark{
  width:var(--mark-sm); height:var(--mark-sm); border-radius:var(--radius-full);
  background:var(--status-ok); flex:none;
}
.io-toast-close{
  background:none; border:none; cursor:pointer; color:var(--text-tertiary);
  min-width:var(--touch-min); min-height:var(--touch-min);
  display:flex; align-items:center; justify-content:center; margin:calc(var(--gap-md) * -1) 0;
  transition:color var(--dur-fast) var(--ease-linear);
}
.io-toast-close:hover{ color:var(--text-primary); }
.io-toast[data-leaving="true"]{ animation:io-toast-out var(--dur-fast) var(--ease-exit) forwards; }
@keyframes io-toast-in{ from{ opacity:0; transform:translate(-50%, var(--gap-md)); } to{ opacity:1; transform:translate(-50%, 0); } }
@keyframes io-toast-out{ to{ opacity:0; } }

/* feedback/Banner — a standing notice; carries a mark AND a word.
   Full-width and pinned by default; .io-banner-inline is the same notice
   set inside a panel (the verifier line on Converse). */
.io-banner{
  display:flex; align-items:center; gap:var(--gap-md);
  padding:var(--pad-box-y-sm) var(--gutter);
  border-bottom:var(--stroke-hairline) solid var(--border-hairline);
  background:var(--sev-info-soft); color:var(--sev-info-ink);
  font-size:var(--type-body-sm); line-height:var(--line-body-sm);
}
.io-banner[data-severity="critical"]{ background:var(--sev-critical-soft); color:var(--sev-critical-ink); }
.io-banner[data-severity="high"]{ background:var(--sev-high-soft); color:var(--sev-high-ink); }
.io-banner[data-severity="medium"]{ background:var(--sev-medium-soft); color:var(--sev-medium-ink); }
.io-banner-label{
  font-family:var(--font-mono);
  font-size:var(--label-type-md); line-height:var(--label-line-md);
  letter-spacing:var(--tracking-label); text-transform:uppercase;
  flex:none;
}
.io-banner-msg{ flex:1; }
.io-banner-inline{
  display:block; padding:var(--gap-sm) var(--gap-md); border-bottom:none;
  font-family:var(--font-mono); font-size:var(--meta-type-md); line-height:var(--line-data);
}

/* feedback/EmptyState — three tones, deliberately different */
.io-empty{
  padding:var(--pad-box-y-lg) var(--pad-box-x-lg);
  border:var(--stroke-hairline) solid var(--border-hairline);
  background:var(--bg-surface);
  text-align:left; max-width:var(--measure-narrow);
}
.io-empty h4{
  font-size:var(--type-subhead); line-height:var(--line-subhead);
  color:var(--text-primary); margin-bottom:var(--gap-sm);
}
.io-empty p{ color:var(--text-secondary); font-size:var(--type-body-sm); line-height:var(--line-body-sm); }
.io-empty .btn{ margin-top:var(--gap-item); }
.io-empty[data-tone="nothing-left"] h4{ color:var(--text-secondary); }

/* feedback/Skeleton — STATIC. No pulse, no shimmer. */
.io-skeleton{
  display:block; background:var(--border-hairline);
  height:var(--body-line-md); margin-bottom:var(--gap-sm);
}
.io-skeleton:last-child{ width:60%; margin-bottom:0; }
.io-skeleton[data-shape="figure"]{ height:var(--figure-line-md); width:40%; }
.io-skeleton[data-shape="mark"]{
  width:var(--mark-sm); height:var(--mark-sm); border-radius:var(--radius-full); display:inline-block;
}


/* =========================================================
   ANCHORED, MEASURED AND CHOSEN — ten primitives

   Anatomy ported from the Resonance chat client (a polished consumer
   surface built on the same ladder), reskinned to Ioma's law: square
   corners, hairline rules, surface steps and spacing carry the polish
   that the donor got from radii, gradients and soft shadows.

   Selector order matches the inventory: picker, popover, search field,
   code input, file pick, choice card, meter, avatar, tab rail, drill pane.
   ========================================================= */

/* --- PICKER — a value chosen from a short list behind a trigger --- */
.io-picker{
  display:flex; align-items:center; gap:var(--gap-sm); width:100%;
  height:var(--control-md); padding:0 var(--pad-pill-sm);
  background:var(--paper-raised); border:var(--stroke-hairline) solid var(--rule);
  color:var(--ink); text-align:left;
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
}
.io-picker[aria-expanded="true"]{ border-color:var(--rule-strong); }
.io-picker-val{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.io-picker-glyph{ flex:none; display:flex; width:var(--icon-sm); height:var(--icon-sm); color:var(--ink-soft); }
.io-picker-caret{ flex:none; width:var(--icon-xs); height:var(--icon-xs); color:var(--ink-faint); }
.io-picker-list{
  position:absolute; left:0; right:0; top:calc(100% + var(--menu-gap)); z-index:var(--z-dropdown);
  display:flex; flex-direction:column; max-height:var(--menu-max-h); overflow-y:auto;
  background:var(--paper-raised);
  border:var(--stroke-hairline) solid var(--rule-strong); box-shadow:var(--shadow-2);
}
.io-picker-opt{
  display:flex; align-items:center; gap:var(--gap-md);
  min-height:var(--row-sm); padding:0 var(--pad-pill-sm);
  background:transparent; border:0; border-bottom:var(--stroke-hairline) solid var(--rule);
  color:var(--ink); text-align:left;
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
}
.io-picker-opt:last-child{ border-bottom:0; }
.io-picker-opt[aria-selected="true"]{ background:var(--paper-sunken); }
.io-picker-opt-name{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.io-picker-opt-sub{ font-size:var(--label-type-sm); line-height:var(--label-line-sm); color:var(--ink-faint); }
.io-picker-check{ flex:none; width:var(--icon-sm); height:var(--icon-sm); color:var(--accent); }

/* --- POPOVER — an anchored panel of content a row cannot hold --- */
.io-pop{
  position:absolute; top:calc(100% + var(--menu-gap)); z-index:var(--z-dropdown);
  width:var(--menu-w); max-width:calc(100vw - var(--gutter) * 2);
  background:var(--paper-raised);
  border:var(--stroke-hairline) solid var(--rule-strong); box-shadow:var(--shadow-2);
}
.io-pop[data-align="start"]{ left:0; }
.io-pop[data-align="end"]{ right:0; }
/* the same four placements the Menu has, and no fifth: a trigger low on the
   screen opens its panel upward rather than past the fold */
.io-pop[data-side="top"]{ top:auto; bottom:calc(100% + var(--menu-gap)); }
.io-pop-head{
  display:flex; align-items:center; justify-content:space-between; gap:var(--gap-md);
  min-height:var(--row-sm); padding:0 var(--pad-box-x-sm);
  border-bottom:var(--stroke-hairline) solid var(--rule);
  font-family:var(--font-mono); font-size:var(--label-type-sm); line-height:var(--label-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase; color:var(--ink-faint);
}
.io-pop-body{ padding:var(--pad-box-y-sm) var(--pad-box-x-sm); }
.io-pop-foot{
  display:flex; justify-content:flex-end; gap:var(--gap-sm);
  padding:var(--gap-md) var(--pad-box-x-sm);
  border-top:var(--stroke-hairline) solid var(--rule);
}

/* --- SEARCH FIELD — glyph, query, clear, and the count --- */
.io-search{
  display:flex; align-items:center; gap:var(--gap-sm);
  height:var(--control-md); padding:0 var(--pad-pill-sm);
  background:var(--paper-sunken); border:var(--stroke-hairline) solid var(--rule);
}
.io-search:focus-within{ border-color:var(--accent); }
.io-search > svg{ flex:none; width:var(--icon-sm); height:var(--icon-sm); color:var(--ink-faint); }
.io-search input{
  flex:1 1 auto; min-width:0; height:100%;
  border:0; background:transparent; outline:none; color:var(--ink);
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
}
.io-search input::placeholder{ color:var(--ink-faint); }
.io-search-clear{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:var(--icon-md); height:var(--icon-md);
  border:0; background:transparent; color:var(--ink-faint);
}
.io-search-results{
  position:absolute; left:0; right:0; top:calc(100% + var(--menu-gap)); z-index:var(--z-dropdown);
  background:var(--paper-raised);
  border:var(--stroke-hairline) solid var(--rule-strong); box-shadow:var(--shadow-2);
}
.io-search-count{
  padding:var(--gap-sm) var(--pad-pill-sm);
  border-bottom:var(--stroke-hairline) solid var(--rule);
  font-family:var(--font-mono); font-size:var(--label-type-sm); line-height:var(--label-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase; color:var(--ink-faint);
}
.io-search-scroll{ display:flex; flex-direction:column; max-height:var(--menu-max-h); overflow-y:auto; }

/* --- CODE INPUT — a fixed-length code, one character per cell --- */
.io-code{ display:flex; gap:var(--gap-sm); }
.io-code-cell{
  width:var(--control-sm); height:var(--control-md);
  background:var(--paper-raised); border:var(--stroke-hairline) solid var(--rule);
  color:var(--ink); text-align:center;
  font-family:var(--font-mono); font-size:var(--ctl-type-lg); line-height:var(--ctl-line-lg);
  font-variant-numeric:tabular-nums;
}
.io-code-cell[data-filled="true"]{ border-color:var(--rule-strong); }
.io-code-cell:focus-visible{ border-color:var(--accent); outline:var(--stroke-emphasis) solid var(--focus-ring); outline-offset:-1px; }
.io-code[data-invalid="true"] .io-code-cell{ border-color:var(--sev-critical); }

/* --- FILE PICK — open the dialog, name the file, drop it --- */
.io-filepick{ display:flex; align-items:center; gap:var(--gap-md); }
.io-filepick input[type="file"]{ position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.io-filepick-preview{
  flex:none; width:var(--control-lg); height:var(--control-lg);
  background:var(--paper-sunken); border:var(--stroke-hairline) solid var(--rule);
  object-fit:cover;
}
.io-filepick-name{
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-family:var(--font-mono); font-size:var(--label-type-md); line-height:var(--label-line-md);
  color:var(--ink-soft);
}

/* --- CHOICE CARD — the card is the choice --- */
.io-choice{
  display:flex; flex-direction:column; gap:var(--gap-md);
  padding:var(--pad-box-y-sm) var(--pad-box-x-sm);
  background:var(--paper-raised);
  border:var(--stroke-hairline) solid var(--rule);
  border-left:var(--stroke-emphasis) solid transparent;
  text-align:left;
}
.io-choice[aria-checked="true"]{ background:var(--paper-sunken); border-left-color:var(--accent); }
.io-choice-name{
  display:flex; align-items:center; justify-content:space-between; gap:var(--gap-md);
  font-family:var(--font-display); font-size:var(--heading-type-sm); line-height:var(--heading-line-sm);
  font-weight:600; color:var(--ink);
}
.io-choice-measure{
  font-family:var(--font-mono); font-size:var(--figure-type-sm); line-height:var(--figure-line-sm);
  color:var(--ink); font-variant-numeric:tabular-nums;
}
.io-choice-what{
  font-family:var(--font-body); font-size:var(--body-type-sm); line-height:var(--body-line-sm);
  color:var(--ink-soft);
}

/* --- METER — a fraction of a stated cap --- */
.io-meter-figure{ display:flex; align-items:baseline; gap:var(--gap-sm); }
.io-meter-figure b{
  font-family:var(--font-display); font-size:var(--figure-type-md); line-height:var(--figure-line-md);
  font-weight:500; color:var(--ink); font-variant-numeric:tabular-nums;
}
.io-meter-cap{
  font-family:var(--font-mono); font-size:var(--label-type-md); line-height:var(--label-line-md);
  color:var(--ink-faint);
}
.io-meter{
  height:var(--inline-sm); margin:var(--gap-md) 0 var(--gap-sm);
  background:var(--paper-sunken); border:var(--stroke-hairline) solid var(--rule);
}
.io-meter-fill{ height:100%; width:var(--fill,0%); background:var(--ink); }
.io-meter[data-tone="warn"] .io-meter-fill{ background:var(--amber); }
.io-meter[data-tone="full"] .io-meter-fill{ background:var(--sev-critical); }

/* --- WORDMARK — the lockup. No icon: the type carries it. Shared by the
   website nav and the console rail, so it has one home and one value. --- */
.brand{
  display:flex; align-items:center; gap:var(--gap-md);
  white-space:nowrap; flex:none;
  font-family:var(--font-display); font-weight:var(--weight-black);
  font-size:var(--type-subhead); letter-spacing:var(--tracking-wordmark);
  text-decoration:none; color:var(--text-primary);
}
.brand .mark{ width:var(--icon-lg); height:var(--icon-lg); }

/* --- AVATAR — one person, as a circle. The one round thing in the system:
   every other box is square-edged, so the circle can only ever mean a person. --- */
.io-avatar{
  flex:none; display:flex; align-items:center; justify-content:center; overflow:hidden;
  width:var(--control-sm); height:var(--control-sm); padding:0; border-radius:50%;
  background:var(--paper-sunken); border:var(--stroke-hairline) solid var(--rule);
  font-family:var(--font-display); font-size:var(--ctl-type-md); line-height:1;
  font-weight:600; color:var(--ink-soft); text-transform:uppercase;
}
.io-avatar img{ width:100%; height:100%; object-fit:cover; }
.io-avatar-md{ width:var(--control-md); height:var(--control-md); font-size:var(--heading-type-sm); }
.io-avatar-lg{ width:var(--control-lg); height:var(--control-lg); font-size:var(--heading-type-md); }

/* --- TAB RAIL — vertical destinations beside their pane --- */
.io-tabrail{
  display:flex; flex-direction:column;
  background:var(--paper-sunken);
  border-right:var(--stroke-hairline) solid var(--rule);
}
.io-tabrail-label{
  padding:var(--gap-md) var(--pad-pill-sm) var(--gap-sm);
  font-family:var(--font-mono); font-size:var(--label-type-sm); line-height:var(--label-line-sm);
  letter-spacing:var(--tracking-label); text-transform:uppercase; color:var(--ink-faint);
}
.io-tabrail-item{
  display:flex; align-items:center; gap:var(--gap-md);
  min-height:var(--row-sm); padding:0 var(--pad-pill-sm);
  background:transparent; border:0; border-left:var(--stroke-emphasis) solid transparent;
  color:var(--ink-soft); text-align:left;
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
}
.io-tabrail-item > svg{ flex:none; width:var(--icon-sm); height:var(--icon-sm); }
.io-tabrail-item[aria-selected="true"]{
  background:var(--paper); border-left-color:var(--accent); color:var(--ink);
}

/* --- DRILL PANE — one level at a time --- */
.io-drill{ display:flex; flex-direction:column; min-height:0; }
.io-drill-head{
  display:flex; align-items:center; gap:var(--gap-md);
  min-height:var(--row-sm); padding:0 var(--pad-box-x-sm);
  border-bottom:var(--stroke-hairline) solid var(--rule);
}
.io-drill-back{
  display:flex; align-items:center; gap:var(--gap-sm);
  height:var(--control-sm); padding:0 var(--gap-sm) 0 0;
  background:transparent; border:0; color:var(--ink-soft);
  font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm);
}
.io-drill-back > svg{ flex:none; width:var(--icon-sm); height:var(--icon-sm); }
.io-drill-title{
  flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-family:var(--font-display); font-size:var(--heading-type-sm); line-height:var(--heading-line-sm);
  font-weight:600; color:var(--ink);
}
.io-drill-body{ flex:1 1 auto; min-height:0; overflow-y:auto; }


/* =========================================================
   ABSORBED · 16 Sep 2026 — from the Resonance consumer app and the Game
   Plan library, reskinned. Square edges, hairlines, ink washes. Nothing
   here introduces a radius, a gradient or a hue the system did not have.
   ========================================================= */

/* --- MESSAGE — one turn in a conversation. The user's turn is a box on the
   accent-soft ground, flush right; the assistant's is plain prose on paper,
   because the assistant is the page. --- */
.io-msg{ display:flex; flex-direction:column; gap:var(--gap-xs); max-width:var(--measure-reading); }
.io-msg-user{ align-self:flex-end; align-items:flex-end; max-width:min(72%, var(--measure-reading)); }
.io-msg-body{
  font-family:var(--font-body); font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink);
  text-wrap:pretty;
}
.io-msg-user .io-msg-body{ background:var(--accent-soft); color:var(--accent-ink); padding:var(--gap-md) var(--pad-box-x-sm); }
.io-msg-actions{ display:flex; gap:var(--gap-xs); align-items:center; min-height:var(--target-click); color:var(--ink-faint); }
.io-msg-actions .io-act{ width:var(--target-click); height:var(--target-click); }
/* the assistant's actions carry a labelled verb where the user's carry only glyphs */
.io-msg-actions .io-act[data-labelled]{ width:auto; padding:0 var(--gap-sm); gap:var(--gap-xs); font-family:var(--font-mono); font-size:var(--meta-type-md); text-transform:uppercase; letter-spacing:var(--tracking-label); }

/* --- COMPOSER — the one place the user writes to the model. A hairline
   box that grows with the text, and a square send at the control rung. --- */
.io-composer{
  display:flex; align-items:flex-end; gap:var(--gap-sm);
  border:var(--stroke-hairline) solid var(--rule-strong); background:var(--paper-raised);
  padding:var(--gap-xs) var(--gap-xs) var(--gap-xs) var(--pad-box-x-sm);
}
.io-composer:focus-within{ outline:var(--stroke-emphasis) solid var(--accent); outline-offset:-1px; }
.io-composer-field{
  flex:1; min-width:0; min-height:var(--control-md); max-height:calc(var(--body-line-md) * 8);
  padding:calc((var(--control-md) - var(--body-line-md)) / 2) 0; margin:0; border:0; background:transparent; resize:none;
  font-family:var(--font-body); font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink);
}
.io-composer-field:focus{ outline:none; }
.io-composer-field::placeholder{ color:var(--ink-faint); }
.io-composer-send{ flex:none; width:var(--control-md); height:var(--control-md); background:var(--accent); color:var(--paper); }
.io-composer-send:disabled{ background:var(--paper-sunken); color:var(--ink-faint); opacity:1; }
.io-composer-cap{ font-family:var(--font-mono); font-size:var(--meta-type-sm); line-height:var(--meta-line-sm); color:var(--ink-faint); padding:0 0 var(--gap-xs); }

/* --- JUMP — appears only when the reader has scrolled away from the live
   end. One label, one glyph, one job. Dismisses itself on arrival. --- */
.io-jump{
  position:absolute; left:50%; transform:translateX(-50%); z-index:var(--z-sticky);
  display:inline-flex; align-items:center; gap:var(--gap-sm);
  height:var(--control-sm); padding:0 var(--pad-pill-sm);
  background:var(--paper-raised); color:var(--ink); border:var(--stroke-hairline) solid var(--rule-strong); box-shadow:var(--shadow-2);
  font-family:var(--font-mono); font-size:var(--meta-type-md); letter-spacing:var(--tracking-label); text-transform:uppercase;
}

/* --- DRAWER — the rail, when the viewport cannot afford a rail. Slides in
   from the leading edge over a scrim that is its dismiss target. --- */
.io-drawer-scrim{ position:fixed; inset:0; z-index:var(--z-scrim); background:var(--scrim); border:0; padding:0; cursor:pointer; }
.io-drawer{
  position:fixed; top:0; bottom:0; left:0; z-index:calc(var(--z-scrim) + 1);
  width:min(var(--shell-nav-w), 86vw); display:flex; flex-direction:column;
  background:var(--paper-sunken); border-right:var(--stroke-hairline) solid var(--rule); box-shadow:var(--shadow-3);
  transform:translateX(-100%); transition:transform var(--dur-part) var(--ease-enter);
}
.io-drawer[data-open="true"]{ transform:none; }

/* --- PERSON ROW — one person: avatar, name, and the address that makes the
   name unambiguous. Never a status dot, never a role chip here. --- */
.io-person{ display:flex; align-items:center; gap:var(--gap-md); min-height:var(--row-md); min-width:0; }
.io-person-text{ display:flex; flex-direction:column; min-width:0; }
.io-person-name{ font-family:var(--font-display); font-weight:var(--weight-bold); font-size:var(--heading-type-sm); line-height:var(--heading-line-sm); color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.io-person-mail{ font-family:var(--font-mono); font-size:var(--meta-type-sm); line-height:var(--meta-line-sm); color:var(--ink-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* --- CHECKBOX — admitted by I18. Legal ONLY inside a form that has a Save
   button; a setting that applies at once is still a Toggle. --- */
.io-check{ display:inline-flex; align-items:center; gap:var(--gap-sm); min-height:var(--touch-min); cursor:pointer; font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink); }
.io-check input{ position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.io-check-box{
  flex:none; width:var(--inline-sm); height:var(--inline-sm); display:grid; place-items:center;
  border:var(--stroke-mark) solid var(--rule-strong); background:var(--paper-raised); color:transparent;
  transition:background var(--dur-fast) var(--ease-linear), border-color var(--dur-fast) var(--ease-linear);
}
.io-check-box svg{ width:var(--icon-xs); height:var(--icon-xs); }
.io-check input:checked + .io-check-box{ background:var(--ink); border-color:var(--ink); color:var(--paper); }
.io-check input:focus-visible + .io-check-box{ outline:var(--stroke-emphasis) solid var(--accent); outline-offset:2px; }
.io-check input:disabled + .io-check-box{ opacity:.4; }
.io-check[data-indeterminate="true"] .io-check-box{ background:var(--ink); border-color:var(--ink); color:var(--paper); }

/* --- GRIP — a drag affordance. Six dots, ink-faint, and the cursor says
   the rest. Appears only where order is the user's to change. --- */
.io-grip{
  flex:none; width:var(--inline-sm); height:var(--inline-md); cursor:grab; touch-action:none; border:0; padding:0;
  background:radial-gradient(circle, var(--ink-faint) 1px, transparent 1.5px) 0 0 / 6px 6px;
  background-clip:content-box; padding:2px 4px;
}
.io-grip:active{ cursor:grabbing; }
[data-dragging="true"]{ background:var(--wash-drag); }

/* --- ROW ACTIONS — the trailing controls of a row, hidden until the row is
   under the pointer or holds focus. Touch input sees them always. --- */
.io-rowactions{ display:flex; align-items:center; gap:var(--gap-xs); flex:none; }
@media (hover:hover){
  .io-rowactions{ opacity:0; transition:opacity var(--dur-fast) var(--ease-linear); }
  tr:hover .io-rowactions, tr:focus-within .io-rowactions,
  .io-row:hover .io-rowactions, .io-row:focus-within .io-rowactions,
  [data-row]:hover .io-rowactions, [data-row]:focus-within .io-rowactions{ opacity:1; }
}

/* --- MATCH — a search result's snippet with the hit marked. The mark is a
   ground, not a weight: weight is for hierarchy. --- */
.io-match{ font-size:var(--body-type-sm); line-height:var(--body-line-sm); color:var(--ink-soft); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.io-match mark,.io-match-mark{ background:var(--accent-soft); color:var(--accent-ink); padding:0 1px; }

/* --- PROPOSAL — something the machine wrote that a person has not yet
   ratified. The band carries the role in ratified-machine; approving it
   re-renders as ordinary content, never as a green card. --- */
.io-proposal{ display:flex; flex-direction:column; background:var(--paper-raised); border:var(--stroke-hairline) solid var(--rule-strong); }
.io-proposal-band{
  display:flex; align-items:center; justify-content:space-between; gap:var(--gap-md);
  padding:var(--gap-sm) var(--pad-box-x-sm); background:var(--ratified-machine-soft); color:var(--ratified-machine-ink);
  font-family:var(--font-mono); font-size:var(--label-type-sm); line-height:var(--label-line-sm); letter-spacing:var(--tracking-label); text-transform:uppercase;
}
.io-proposal-body{ padding:var(--pad-box-y-sm) var(--pad-box-x-sm); font-family:var(--font-body); font-size:var(--body-type-md); line-height:var(--body-line-md); color:var(--ink); }
.io-proposal-actions{ display:flex; gap:var(--gap-sm); padding:var(--gap-sm) var(--pad-box-x-sm) var(--pad-box-y-sm); }

/* --- CONFIRM — a Dialog narrowed to one question. The heading IS the
   action as a question; the primary names the verb; never "Are you sure?" --- */
.io-dialog-confirm{ max-width:min(var(--overlay-w), 420px); }
.io-dialog-confirm .io-dialog-foot{ justify-content:flex-end; }


/* --- STEPPER — a whole number inside a range. Minus · value · plus in one
   hairline track; the ends disable at the bounds rather than vanishing.
   Applies at once and says so with a toast, or sits inside a form with Save. --- */
.io-stepper{ display:inline-flex; align-items:stretch; border:var(--stroke-hairline) solid var(--rule-strong); background:var(--paper-raised); flex:none; height:var(--control-sm); }
.io-stepper > button{ width:var(--control-sm); display:grid; place-items:center; background:transparent; border:0; color:var(--ink-soft); cursor:pointer; padding:0; }
.io-stepper > button svg{ width:var(--icon-xs); height:var(--icon-xs); }
.io-stepper > button:disabled{ opacity:.4; cursor:default; }
.io-stepper > button:disabled:hover{ background:none; }
.io-stepper-val{ min-width:var(--control-sm); display:grid; place-items:center; padding:0 var(--gap-sm); border-left:var(--stroke-hairline) solid var(--rule-strong); border-right:var(--stroke-hairline) solid var(--rule-strong); font-family:var(--font-mono); font-size:var(--ctl-type-sm); line-height:var(--ctl-line-sm); font-variant-numeric:tabular-nums; color:var(--ink); }
