/* ===========================================================================
   Commonplace House Style — web component layer (HOUSE_STYLE §16)

   The CSS half of design-system/web. It styles the React components in this
   directory and is equally usable from plain HTML: every class here is a
   noun (`.house-button`, `.house-input`, `.house-menu`) that any consumer can
   put on a native element.

   Depends on the tokens in ../tokens/commonplace.css — import that first.
   Nothing here declares a colour, a radius, or a shadow of its own: every
   value resolves from the five primitives, `--danger`, and the rule / spacing
   tokens, so the whole layer inverts with the theme. Motifs and panel
   primitives already shipped in commonplace.css (.arrow-link, .btn-arrow,
   .label-chrome, .divider-asterisk, .house-panel*, .house-control*,
   .house-frame*, .house-cell*) are NOT redefined here — the components reuse
   them by name.

   Two hairline weights, on purpose:
     - passive furniture (panels, rules, inputs at rest) draws in
       --border-color (gray-20);
     - controls that DO things (buttons, segmented, checkbox box, menu edge)
       draw in --border-strong (gray-50), matching the Swift HouseButtonStyle
       stroke, so a control boundary clears WCAG 1.4.11's 3:1 non-text
       contrast floor at rest (§17).
   =========================================================================== */

/* ---- Buttons --------------------------------------------------------------
   `.house-button` mirrors Swift `HouseButtonStyle`: uppercase tracked chrome,
   a --border-strong hairline, transparent. `--prominent` fills ink (the one primary
   action on a surface). Press/hover recede to 0.5 — never lift.            */
.house-button {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-xs);
  min-height: 32px; padding: var(--space-xs) var(--space-md);
  background: transparent; color: var(--text-color);
  border: var(--rule-hair) solid var(--border-strong);
  font: inherit; font-size: 0.78rem; font-weight: 400; line-height: 1.2;
  text-transform: uppercase; letter-spacing: var(--tracking-chrome);
  cursor: pointer; transition: var(--transition);
}
.house-button:hover { opacity: 0.5; }
.house-button:active { opacity: 0.5; }
.house-button:disabled { opacity: 0.35; cursor: default; }
/* An icon beside a label, sized like the one inside .house-icon-button. Without
   this a 24px-default glyph towers over 0.78rem chrome and every call site
   passes its own size (DR-021). */
.house-button > svg { width: 14px; height: 14px; flex-shrink: 0; }
.house-button--prominent { background: var(--text-color); color: var(--bg-color); border-color: var(--text-color); font-weight: 700; }
.house-button--danger { color: var(--danger); border-color: var(--danger); }
/* The label is the GROUND, as on the plain prominent button — never a literal.
   `--danger` is a different value in each theme (dark lightens it so it reads
   as text on the dark ground), and a literal cannot follow it: `#ffffff` was
   5.52:1 on the light fill and 3.77:1 on the dark one, under §17's 4.5. The
   ground is white in light — the same pixels as before — and 5.16:1 in dark. */
.house-button--danger.house-button--prominent { background: var(--danger); color: var(--bg-color); }
.house-button--arrow::after { content: '→'; color: var(--text-light); }
.house-button--prominent.house-button--arrow::after { color: inherit; opacity: 0.55; }
.house-button--block { display: flex; width: 100%; }

/* The compact size — the second point on the density scale (§17, DR-021).
   The house default (32px) was built for a reading column; a drafting app's
   24px command footer and 28px status bar cannot seat it, and ~190 controls
   across two apps had to be re-sized locally. 24px is the floor, not a
   preference: WCAG 2.5.8 puts the minimum target at 24x24, so `compact` sits
   exactly on it and there is no third, smaller step. Chrome text drops to
   0.65rem; value-bearing text (a field, a checkbox caption) stays 12px, which
   is as small as a string of digits stays readable. */
.house-button--compact { min-height: 24px; padding: var(--space-xxs) var(--space-sm); font-size: 0.65rem; }
.house-button--compact > svg { width: 12px; height: 12px; }

/* Pressed — a toggle at rest, not a hover response. Same face as
   `.house-control[aria-selected='true']`, from the same two tokens: a 10% ink
   wash and the gray-50 edge, plus the bold weight the segmented control's
   selected cell already uses. `aria-pressed` is the correct attribute on a
   button; `aria-selected` is not valid there. */
.house-button[aria-pressed='true'] { background: var(--active-bg); border-color: var(--active-border); font-weight: 700; }

/* ---- Icon button — the one primitive for row / card actions (§8) -------- */
.house-icon-button {
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box; width: 32px; height: 32px; flex-shrink: 0; padding: 0;
  background: transparent; border: var(--rule-hair) solid transparent; color: var(--text-light);
  font: inherit; line-height: 1; cursor: pointer; transition: var(--transition);
}
.house-icon-button:hover { opacity: 0.5; }
.house-icon-button:disabled { opacity: 0.3; cursor: default; pointer-events: none; }
.house-icon-button > svg { width: 16px; height: 16px; }
.house-icon-button--compact { width: 24px; height: 24px; }
.house-icon-button--compact > svg { width: 12px; height: 12px; }
/* Pressed is §11's selected face in full: BOTH --active-bg and --active-border,
   plus the mark going from --text-light to full ink. The square carries a
   hairline in `transparent` at rest so the border can be recoloured without
   the box moving, and `border-box` keeps the 32px (and the compact 24px) the
   target the law counts. Drafter drew this edge locally on its rail before
   §18b existed and lost it on adoption, because the library's pressed face
   was the wash alone: on a tool rail the wash is a 10% tint on a small square
   with air around it, which is the weakest place in the system for a state to
   read. §11's pair is not two ways to say the same thing — the fill says
   "this one", the edge says "and it is a thing you pressed". */
.house-icon-button[aria-pressed='true'] {
  background: var(--active-bg); border-color: var(--active-border); color: var(--text-color);
}

/* ---- Actions rail + menu (§8: always visible, docked, collapse past two) - */
.house-actions { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--space-xxs); flex-shrink: 0; }
.house-actions-menu { position: relative; display: inline-flex; flex-shrink: 0; }
.house-menu {
  position: absolute; top: 100%; margin-top: var(--space-xxs); z-index: var(--layer-overlay);
  min-width: 180px; padding: var(--space-xs) 0;
  background: var(--bg-color); color: var(--text-color);
  border: var(--rule-hair) solid var(--border-strong);
}
.house-menu[data-align='right'] { right: 0; }
.house-menu[data-align='left']  { left: 0; }
.house-menu-item {
  display: flex; width: 100%; align-items: center; gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  background: transparent; border: 0; color: inherit;
  font: inherit; font-size: 14px; text-align: left; cursor: pointer;
  transition: var(--transition);
}
.house-menu-item:hover, .house-menu-item:focus-visible { opacity: 0.5; }
.house-menu-item:disabled { opacity: 0.3; cursor: default; pointer-events: none; }
.house-menu-item--danger { color: var(--danger); }
.house-menu-item > .house-menu-icon { display: inline-flex; flex-shrink: 0; }

/* ---- Text fields ---------------------------------------------------------- */
.house-input {
  display: block; box-sizing: border-box; width: 100%; min-height: 32px; padding: var(--space-xs) var(--space-sm);
  background: transparent; color: var(--text-color);
  border: var(--rule-hair) solid var(--border-color);
  font: inherit; font-size: 14px; line-height: 1.4;
  transition: var(--transition);
}
.house-input::placeholder { color: var(--text-light); }
.house-input:hover { border-color: var(--border-strong); }  /* darken, never lighten */
.house-input:focus-visible { outline: none; border-color: var(--text-color); box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--ring); }
.house-input:disabled { opacity: 0.5; cursor: default; }
.house-input[aria-invalid='true'] { border-color: var(--danger); }
textarea.house-input { min-height: 96px; resize: vertical; }
.house-input--compact { min-height: 24px; padding: 0 var(--space-xs); font-size: 12px; }
textarea.house-input--compact { min-height: 48px; }

/* Native select, house-dressed. The ▾ is a control glyph, not a motif. */
.house-select { position: relative; display: block; }
.house-select > select {
  appearance: none; -webkit-appearance: none;
  padding-right: calc(var(--space-sm) + 1.4em);
}
.house-select::after {
  content: '▾'; position: absolute; right: var(--space-sm); top: 50%; transform: translateY(-50%);
  color: var(--text-light); pointer-events: none; font-size: 12px;
}
.house-select--compact > select { padding-right: calc(var(--space-xs) + 1.2em); }
.house-select--compact::after { right: var(--space-xs); font-size: 10px; }

/* ---- Checkbox — mirrors Swift HouseCheckboxToggleStyle ------------------ */
/* `position: relative` anchors the visually-hidden input to its own label.
   Unpositioned, the input's containing block is the nearest positioned
   ancestor — in an app shell, typically the frame OUTSIDE the panel's own
   scroller — so the input escapes the panel's scroll clip, inflates the
   frame's scrollHeight, and a click (which focuses it) makes the browser
   scroll the frame to reveal it. (Modeler #1807, GAP D9.) */
.house-checkbox { position: relative; display: inline-flex; align-items: center; gap: var(--space-sm); cursor: pointer; font-size: 14px; }
.house-checkbox > input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
.house-checkbox-box {
  position: relative; width: 14px; height: 14px; flex-shrink: 0;
  border: var(--rule-hair) solid var(--border-strong); background: transparent;
}
.house-checkbox-box::after { content: ''; position: absolute; inset: 2px; background: var(--text-color); opacity: 0; transition: var(--transition); }
.house-checkbox > input:checked + .house-checkbox-box::after { opacity: 1; }
.house-checkbox > input:focus-visible + .house-checkbox-box { box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--ring); }
.house-checkbox > input:disabled ~ * { opacity: 0.5; }
.house-checkbox:has(> input:disabled) { cursor: default; }
/* The box shrinks but the LABEL keeps a 24px min-height: the hit target is the
   whole label, not the 12px mark inside it (§17 / WCAG 2.5.8). */
.house-checkbox--compact { min-height: 24px; gap: var(--space-xs); font-size: 12px; }
.house-checkbox--compact > .house-checkbox-box { width: 12px; height: 12px; }

/* ---- Segmented — mirrors Swift HouseSegmented --------------------------- */
.house-segmented { display: inline-flex; border: var(--rule-hair) solid var(--border-strong); }
.house-segmented-item {
  flex: 1 1 0; min-width: 0; padding: var(--space-xs) var(--space-md);
  background: transparent; color: var(--text-light); border: 0;
  border-left: var(--rule-hair) solid var(--border-strong);
  font: inherit; font-size: 0.68rem; font-weight: 400; line-height: 1.4;
  text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
  cursor: pointer; transition: var(--transition);
}
.house-segmented-item:first-child { border-left: 0; }
.house-segmented-item:hover { color: var(--text-color); }
/* Both selection attributes: `aria-checked` is the radiogroup Segmented renders,
   `aria-selected` is a tablist. house.css promises every class works on a native
   element, and a tab strip is the same mark doing the other job — keyed on
   `aria-checked` alone the selected cell silently never paints (DR-021). */
.house-segmented-item[aria-checked='true'],
.house-segmented-item[aria-selected='true'] { background: var(--text-color); color: var(--bg-color); font-weight: 700; }
.house-segmented-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--ring); }
.house-segmented-item:disabled { opacity: 0.35; cursor: default; }
.house-segmented--compact > .house-segmented-item { min-height: 24px; padding: 0 var(--space-sm); font-size: 0.62rem; }

/* ---- Slider — ink track and thumb; the thumb is the lone permitted circle (§3) */
.house-slider { width: 100%; margin: 0; accent-color: var(--text-color); background: transparent; }
.house-slider:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--ring); }
/* The live value beside the track (v6.0.0, gap G1): a fixed minimum width so a
   column of slider rows aligns, tabular figures so its digits do too (§11). */
.house-slider-row { display: flex; align-items: center; gap: var(--space-sm); width: 100%; }
.house-slider-row > .house-slider { flex: 1 1 auto; min-width: 0; }
.house-slider-readout { flex-shrink: 0; min-width: 3.6em; text-align: right; font-size: 0.68rem; font-variant-numeric: tabular-nums; }

/* ---- Colour input — the swatch IS the value (§9), on the control hairline (v6.0.0, gap G4) */
.house-color-input { display: inline-flex; align-items: center; gap: var(--space-sm); }
.house-color-swatch {
  box-sizing: border-box; flex-shrink: 0; width: 32px; height: 32px; padding: 2px;
  background: transparent; cursor: pointer; transition: var(--transition);
}
.house-color-input--compact > .house-color-swatch { width: 24px; height: 24px; padding: 1px; }
.house-color-swatch:hover { border-color: var(--border-strong); }  /* darken, never lighten */
.house-color-swatch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px var(--ring); }
.house-color-swatch:disabled { opacity: 0.35; cursor: default; }
/* The engine's own frame around the colour is a second, grey hairline; the
   control's is the one frame. */
.house-color-swatch::-webkit-color-swatch-wrapper { padding: 0; }
.house-color-swatch::-webkit-color-swatch { border: 0; }
.house-color-swatch::-moz-color-swatch { border: 0; }
.house-color-readout { font-size: 0.68rem; font-variant-numeric: tabular-nums; }

/* ---- Panel section — ruled + collapsible (the Modeler pattern, §11/§12) - */
.house-panel-section { display: flex; flex-direction: column; gap: var(--space-sm); border-top: var(--rule-hair) solid var(--border-color); padding-top: var(--space-sm); }
.house-panel-section-header {
  display: flex; align-items: center; gap: var(--space-xs); width: 100%;
  padding: 0; background: transparent; border: 0; color: var(--text-color);
  font: inherit; text-align: left; cursor: default;
}
button.house-panel-section-header { cursor: pointer; transition: var(--transition); }
button.house-panel-section-header:hover { opacity: 0.5; }
.house-panel-section-header > .house-panel-title { color: var(--text-color); }
.house-panel-section-caret { margin-left: auto; color: var(--text-light); font-size: 10px; }
.house-panel-section[data-collapsed='true'] > .house-panel-section-body { display: none; }

/* ---- Labeled row + readout — mirror Swift LabeledRow / Readout ---------- */
.house-labeled-row { display: flex; align-items: center; gap: var(--space-sm); }
.house-labeled-row > .house-control-label { flex-shrink: 0; }
.house-labeled-row > .house-labeled-row-control { flex: 1 1 auto; min-width: 0; }
.house-readout { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-sm); }
.house-readout > .house-readout-value { font-size: 13px; }

/* ---- Feedback: notice, empty state, loading ------------------------------
   Advisory notices stay black-and-white: a glyph plus weight (the §2
   decision test). `--interrupt` is the must-interrupt case and the only
   consumer of --danger in this file.                                       */
.house-notice {
  display: flex; gap: var(--space-md); align-items: flex-start;
  padding: var(--space-md) var(--space-lg);
  border: var(--rule-hair) solid var(--border-color);
  font-size: 14px; line-height: 1.45;
}
.house-notice-glyph { font-weight: 700; flex-shrink: 0; width: 1em; text-align: center; }
.house-notice-title { font-weight: 700; }
.house-notice-body { flex: 1 1 auto; min-width: 0; }
.house-notice-actions { display: flex; gap: var(--space-md); margin-top: var(--space-sm); }
.house-notice--interrupt { border-color: var(--danger); }
.house-notice--interrupt > .house-notice-glyph, .house-notice--interrupt .house-notice-title { color: var(--danger); }

.house-empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-sm); padding: var(--space-3xl) var(--space-xl); }
.house-empty-title { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: var(--tracking-tight); line-height: 1.3; }
.house-empty-body { max-width: var(--measure); color: var(--text-light); font-size: 14px; line-height: 1.5; }
.house-empty-action { margin-top: var(--space-sm); }
/* Two sizes, because --space-3xl (96px) of padding is right for a whole surface
   and absurd under a section heading or in a 300px inspector rail (DR-021). */
.house-empty--panel { padding: var(--space-lg) var(--space-md); gap: var(--space-xs); }
.house-empty--panel > .house-empty-title { font-size: 14px; }
.house-empty--panel > .house-empty-body { font-size: 12px; }

.house-loading { display: flex; align-items: center; justify-content: center; gap: var(--space-sm); padding: var(--space-2xl); color: var(--text-light); font-size: 14px; }
.house-loading-mark { animation: house-loading-pulse 1.2s linear infinite; }  /* opacity only — §4 */
@keyframes house-loading-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }

/* ---- Tooltip — inverted ink on paper, square, no shadow ----------------- */
.house-tooltip { position: relative; display: inline-flex; }
.house-tooltip::after {
  content: attr(data-tip); position: absolute; left: 50%; bottom: 100%;
  transform: translateX(-50%); margin-bottom: var(--space-xs);
  padding: var(--space-xs) var(--space-sm); white-space: nowrap; z-index: var(--layer-tooltip);
  background: var(--text-color); color: var(--bg-color);
  font-size: 12px; line-height: 1.3;
  opacity: 0; pointer-events: none; transition: var(--transition);
}
.house-tooltip:hover::after, .house-tooltip:focus-within::after { opacity: 1; }

/* ---- Field — the stacked twin of .house-labeled-row ----------------------
   `.house-labeled-row` is an inspector row: label in a fixed 6em column, control
   beside it. A form field is the other shape — label above, control full width,
   and the two slots a form actually needs under it. 115+ sites across three apps
   hand-rolled this, and four dialogs hand-rolled the hint as a 10px grey div
   (DR-021).

   The label is `.house-control-label` and stays MIXED CASE (§5's one exception);
   nothing here transforms it.

   The error message is deliberately monochrome — bold ink, not red. The hue is
   already spent on the field's own edge, which `[aria-invalid='true']` turns
   --danger; §2's decision test says the word carries the meaning and the red is
   a floor, so spending it twice on one field buys nothing and would put --danger
   on an advisory surface. */
.house-field { display: flex; flex-direction: column; gap: var(--space-xs); }
.house-field > .house-control-label { display: block; }
.house-field-hint { color: var(--text-light); font-size: 0.68rem; line-height: 1.35; }
/* The standalone hint (v6.0.0, gap G2) is a <p>: the field's voice, no margin. */
.house-hint { margin: 0; }
/* A number field holds a few digits, not a column (v6.0.0, gap G6). */
.house-input.house-number-input { max-width: 8em; font-variant-numeric: tabular-nums; }
.house-field-error { color: var(--text-color); font-weight: 700; font-size: 0.68rem; line-height: 1.35; }

/* ---- List row — ruled, never boxed (§11) ---------------------------------
   A selectable row in a list of files, versions, layers or invitations. Hairline
   under each row, no card and no box; selection is carried by WEIGHT and a left
   rule in --active-border, the same two tokens `.house-control[aria-selected]`
   already spends, not by a tint.

   `aria-current='true'` is what a row rendered as a <button> can honestly say —
   `aria-selected` is only valid on an option / row / tab. The class paints both
   so it also works inside a consumer's real listbox, the way the segmented cell
   does for a tablist. */
.house-list-row {
  display: flex; align-items: center; gap: var(--space-sm); width: 100%; box-sizing: border-box;
  min-height: 32px; padding: var(--space-xs) var(--space-sm);
  background: transparent; color: var(--text-color);
  border: 0; border-bottom: var(--rule-hair) solid var(--border-color);
  border-left: var(--rule-object) solid transparent;
  font: inherit; font-size: 14px; line-height: 1.4; text-align: left;
  transition: var(--transition);
}
button.house-list-row { cursor: pointer; }
button.house-list-row:not(.house-list-row--pick):hover { opacity: 0.5; }
button.house-list-row:disabled { opacity: 0.35; cursor: default; }
.house-list-row[aria-current='true'],
.house-list-row[aria-selected='true'] { border-left-color: var(--active-border); font-weight: 700; }
.house-list-row--compact { min-height: 24px; padding: 0 var(--space-xs); font-size: 12px; }
/* The selectable half when the row also carries actions: a button cannot contain
   a button, so the row becomes the container and this is the pressable part. */
.house-list-row-main {
  display: flex; align-items: center; gap: var(--space-sm); flex: 1 1 auto; min-width: 0;
  padding: 0; background: transparent; border: 0; color: inherit;
  font: inherit; text-align: left; cursor: pointer; transition: var(--transition);
}
.house-list-row:not(.house-list-row--pick) > .house-list-row-main:hover { opacity: 0.5; }
.house-list-row-main:disabled { opacity: 0.35; cursor: default; }
.house-list-row-body { flex: 1 1 auto; min-width: 0; }
/* The pick row (v6.0.0, gap G7): a row of a menu you are answering. A name and
   its description on one baseline, spread apart; the whole row fills on hover
   with the 7% ink wash, because a paragraph-sized target at half opacity reads
   as disabled. It has no selected face — it dismisses, it does not persist. */
.house-list-row--pick > .house-list-row-body,
.house-list-row--pick > .house-list-row-main {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-sm);
}
button.house-list-row--pick:not(:disabled):hover,
.house-list-row--pick > .house-list-row-main:not(:disabled):hover { background: var(--accent); }
.house-list-row > .house-actions,
.house-list-row-main + .house-actions { margin-left: auto; }

/* ---- Dialog — the modal surface (§8 docked, §17 focus) -------------------
   Built on the native <dialog>: `showModal()` puts it in the top layer, makes
   the rest of the document inert, traps Tab, and turns Escape into a close
   request. §16 says reimplementing a focus trap by hand is an accessibility bug
   with better branding — so this does not; it asks the platform. Three apps hand-
   rolled 20 dialogs between them and every one got that half wrong (DR-021).

   The <dialog> ITSELF is the scrim, filling the viewport, with the panel centred
   inside it. Two reasons: `::backdrop` did not inherit custom properties until
   recently (so `var(--scrim)` silently resolved to nothing on older engines),
   and a scrim that is a real element gives `event.target === dialog` as an exact
   test for "the pointer went down on the backdrop". */
.house-dialog {
  position: fixed; inset: 0; z-index: var(--layer-dialog); box-sizing: border-box;
  width: 100%; height: 100%; max-width: none; max-height: none;
  margin: 0; padding: var(--space-lg);
  background: var(--scrim); color: var(--text-color); border: 0;
  overflow: auto;
}
.house-dialog[open] { display: flex; align-items: center; justify-content: center; }
.house-dialog::backdrop { background: transparent; }
.house-dialog-panel {
  display: flex; flex-direction: column; box-sizing: border-box;
  width: 100%; max-height: 100%; overflow: hidden;
  background: var(--bg-color); color: var(--text-color);
  border: var(--rule-frame) solid var(--border-strong);
}
.house-dialog[data-size='form']  > .house-dialog-panel { max-width: 420px; }
.house-dialog[data-size='prose'] > .house-dialog-panel { max-width: var(--measure); }
.house-dialog[data-size='page']  > .house-dialog-panel { max-width: var(--max-width); height: 100%; }
/* `full` (v6.0.0, gap G8): the sheet IS the viewport — no scrim showing, no
   frame, no gutter. A document read, not a question asked. */
.house-dialog[data-size='full'] { padding: 0; background: var(--bg-color); }
.house-dialog[data-size='full'] > .house-dialog-panel { max-width: none; height: 100%; border: 0; }
.house-dialog-header {
  display: flex; flex-direction: column; gap: var(--space-xxs); flex-shrink: 0;
  padding: var(--space-lg) var(--space-lg) var(--space-md);
  border-bottom: var(--rule-hair) solid var(--border-color);
}
.house-dialog-title {
  margin: 0; font-size: 0.78rem; font-weight: 700; line-height: 1.3;
  text-transform: uppercase; letter-spacing: var(--tracking-chrome);
}
.house-dialog-subtitle { color: var(--text-light); font-size: 12px; line-height: 1.4; }
.house-dialog-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--space-lg); font-size: 14px; line-height: 1.45; }
/* Docked, not clipped (§8): the body scrolls, the action row never does. A child
   with `margin-right: auto` floats left of the rest — an auto margin eats the
   free space before justify-content sees it — which is where a "Download file"
   sits beside Cancel / Save. */
.house-dialog-footer {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--space-sm); flex-shrink: 0;
  padding: var(--space-md) var(--space-lg);
  border-top: var(--rule-hair) solid var(--border-color);
}

/* ---- Reader — a document you READ (v6.0.0, gap G8) ----------------------
   A `Dialog` at size `full` for its behaviour; these are the sheet's chrome
   and the reading column. Every value is a token or a step of the type scale:
   the two hand-built copies this replaces carried a 790px measure and 0.08em
   tracking because, in one of them, the tokens "didn't exist here". */
.house-dialog.house-reader .house-dialog-body { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.house-reader-bar {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
  padding: var(--space-sm) var(--space-xl);
  border-bottom: var(--rule-frame) solid var(--text-color);
}
.house-reader-title { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--tracking-chrome); }
.house-reader-scroll { flex: 1 1 auto; overflow-x: hidden; overflow-y: auto; }
.house-reader-article { max-width: var(--measure); margin-inline: auto; padding: var(--space-2xl) var(--space-xl); font-size: 16px; line-height: 1.6; }
.house-reader-article h1 { margin: 0 0 var(--space-lg); font-size: 34px; font-weight: 700; line-height: 1.15; letter-spacing: var(--tracking-tight); }
.house-reader-article p { margin: 0 0 var(--space-lg); }
.house-reader-lede { font-size: 20px; line-height: 1.45; }
.house-reader-toc { margin: var(--space-2xl) 0; }
.house-reader-toc-head {
  margin: var(--space-xl) 0 var(--space-xs); color: var(--text-light);
  font-size: 12px; font-weight: 400; text-transform: uppercase; letter-spacing: var(--tracking-chrome);
}
.house-reader-toc ul { margin: 0; padding: 0; list-style: none; }
.house-reader-toc li { border-bottom: var(--rule-hair) solid var(--border-color); }
.house-reader-toc a { display: flex; gap: var(--space-sm); padding: var(--space-xs) 0; color: var(--text-color); font-size: 15px; text-decoration: none; }
.house-reader-toc a:hover { text-decoration: underline; }
.house-reader-toc-marker { flex: 0 0 4.5rem; color: var(--text-light); font-variant-numeric: tabular-nums; }
.house-reader-section { margin-top: var(--space-2xl); scroll-margin-top: var(--space-lg); }
.house-reader-section-head {
  display: flex; align-items: baseline; gap: var(--space-sm);
  margin: 0 0 var(--space-sm); padding-top: var(--space-sm);
  border-top: var(--rule-frame) solid var(--text-color);
  font-size: 26px; font-weight: 700; letter-spacing: var(--tracking-tight);
}
.house-reader-section-marker { color: var(--text-light); }
.house-reader-section-blurb { margin-bottom: var(--space-xl); color: var(--text-light); white-space: pre-line; }
/* Prose lists inside the article, by class, so the contents list (a ul) is untouched. */
.house-reader-list { margin: 0 0 var(--space-lg); padding-left: var(--space-xl); }
.house-reader-list > li { margin-bottom: var(--space-xs); }
.house-reader-list--plain { padding-left: 0; list-style: none; }
.house-reader-aside { color: var(--text-light); }

/* ---- Table ---------------------------------------------------------------
   Hairline rows; the actions cell docks right and never clips (§8).        */
.house-table-wrap { overflow-x: auto; }
.house-table { width: 100%; border-collapse: collapse; font-size: 14px; line-height: 1.45; }
.house-table th {
  text-align: left; padding: var(--space-sm) var(--space-md) var(--space-sm) 0;
  border-bottom: var(--rule-hair) solid var(--border-color);
  font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: var(--tracking-chrome); color: var(--text-light);
}
.house-table td { padding: var(--space-sm) var(--space-md) var(--space-sm) 0; border-bottom: var(--rule-hair) solid var(--border-color); vertical-align: top; }
.house-table .house-cell-actions { position: sticky; right: 0; background: var(--bg-color); text-align: right; padding-right: 0; }
.house-table .house-num { font-variant-numeric: tabular-nums; }

/* ---- The application frame — editors (§18, DR-022) ----------------------
   Three of the four surfaces an editor's canvas sits inside; the fourth, the
   settings panel, is a stack of `.house-panel-section`s at --frame-panel and
   needs no class of its own. Drafter and Modeler built all of this locally and
   arrived at the same shape; these are the shared nouns, so their App.css /
   house-gaps.css frame blocks can be deleted rather than ported.

   The frame is chrome, so every rule is monochrome and every size comes from
   --frame-* or the two control tiers. Nothing here invents a third density:
   the nav and the footer are `compact` (24px), the rail's squares `default`
   (32px). */

/* Top nav: mark + address │ menubar │ status + account. 48px including its
   hairline, so the nav's bottom rule and the rail's right rule meet in a
   48px square at the top-left and the frame reads as one object. */
.house-app-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-lg);
  box-sizing: border-box; height: var(--frame-bar); flex-shrink: 0; padding: 0 var(--space-lg);
  background: var(--bg-color); color: var(--text-color);
  border-bottom: var(--rule-hair) solid var(--border-color);
}
.house-app-header-start { display: flex; align-items: baseline; gap: var(--space-md); min-width: 0; }
/* The wordmark — chrome, one step up, in ink. Byte-for-byte the block both
   editors already shared, so every editor's top-left corner is the same object. */
.house-app-header-start > .house-app-wordmark { flex-shrink: 0; font-size: 0.82rem; color: var(--text-color); }
.house-app-address {
  min-width: 0; font-size: 11px; color: var(--text-light);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.house-app-address[data-editable='true'] { cursor: text; }
/* Docked, not clipped (§8): a menubar too wide for the window scrolls, it
   never runs a menu off the edge. */
.house-app-menubar { display: flex; align-items: center; gap: var(--space-xxs); min-width: 0; overflow-x: auto; }
.house-app-header-end { display: flex; align-items: center; gap: var(--space-md); flex-shrink: 0; }
/* A menu trigger is chrome: uppercase, tracked, transparent, a `compact` row.
   Open is the pressed face — `aria-expanded` is what a trigger honestly says,
   and it is what a headless kit's trigger sets. Never a colour change. */
.house-app-menu-trigger {
  display: inline-flex; align-items: center; flex-shrink: 0;
  min-height: 24px; padding: 0 var(--space-sm);
  background: transparent; color: var(--text-color); border: 0;
  font: inherit; font-size: 0.68rem; font-weight: 400; line-height: 1.2;
  text-transform: uppercase; letter-spacing: var(--tracking-chrome); white-space: nowrap;
  cursor: pointer; transition: var(--transition);
}
.house-app-menu-trigger:hover { opacity: 0.5; }
/* The wash alone, deliberately — no --active-border. An open trigger has the
   menu's own --border-strong edge hanging directly off its bottom, and a
   hairline on the trigger would be a second rule drawn against it (§3: one
   hairline per boundary). The icon square takes the pair because it stands in
   air; this one does not. */
.house-app-menu-trigger[aria-expanded='true'] { background: var(--active-bg); }
.house-app-menu-trigger:disabled { opacity: 0.35; cursor: default; }

/* The menubar's own menu (§18a). The surface is `.house-menu` — the same box,
   the same --border-strong edge, the same rows as an ActionMenu — and these
   are the parts a menubar row has that a row-action does not: a check column,
   a key-chord column, a rule between groups, and a submenu marker.
   A menubar menu is chrome, so its rows sit on the compact tier (§18a: nav
   controls are `compact`); a row-action menu is read at the default 14px. */
.house-menu.house-app-menu { min-width: 220px; }
.house-app-menu > .house-menu-item,
.house-app-menu > .house-app-menu-node > .house-menu-item {
  min-height: 24px; gap: var(--space-sm); padding: var(--space-xs) var(--space-md); font-size: 12px;
}
/* One em of column, present only when the menu has something checkable in it:
   a menu of plain verbs is not indented by a column it never fills. */
.house-menu-item-check { flex: 0 0 1em; text-align: center; }
.house-menu-item-label { flex: 1 1 auto; white-space: nowrap; }
/* The chord is a reminder, not a control: recessive ink, the menu's own face,
   no cap around it (the palette's hairline <kbd> is a key being TAUGHT). */
.house-menu-item-keys {
  flex-shrink: 0; margin-left: var(--space-lg);
  font: inherit; font-size: 11px; font-weight: 400; color: var(--text-light);
}
.house-menu-item-more { flex-shrink: 0; margin-left: var(--space-sm); color: var(--text-light); }
.house-menu-rule { height: 0; margin: var(--space-xs) 0; border: 0; border-top: var(--rule-hair) solid var(--border-color); }
/* A submenu opens sideways off its row, the same move the rail's flyout makes
   and for the same reason: down is where the parent menu already is. */
.house-app-menu-node { position: relative; display: flex; }
.house-menu.house-app-menu-submenu { top: 0; left: 100%; right: auto; margin-top: calc(-1 * var(--space-xs)); }
/* A context menu (v6.0.0, gap G10) is placed at the pointer by `anchor`; near
   the right edge its submenus open leftward instead. */
.house-app-menu[data-submenu-side='left'] .house-menu.house-app-menu-submenu { left: auto; right: 100%; }
/* A heading over a group: chrome (§5), recessive, and not a row. */
.house-menu-header {
  padding: var(--space-xs) var(--space-md) var(--space-xxs);
  color: var(--text-light); font-size: 0.62rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--tracking-chrome); white-space: nowrap;
}
/* A disabled row that says why keeps the pointer for its tooltip; it keeps the
   disabled face too, and hovering it changes nothing. */
.house-menu-item[aria-disabled='true'],
.house-menu-item[aria-disabled='true']:hover { opacity: 0.3; cursor: default; }

/* Toolbar: a --frame-bar rail of 32px icon squares. The group boundary is a
   24px centred hairline, not whitespace. */
.house-tool-rail {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-xs);
  box-sizing: border-box; width: var(--frame-bar); flex-shrink: 0; padding: var(--space-sm) 0;
  background: var(--bg-color); color: var(--text-color);
  border-right: var(--rule-hair) solid var(--border-color);
}
.house-tool-rail-rule { flex-shrink: 0; width: 24px; height: 0; margin: var(--space-xs) 0; border: 0; border-top: var(--rule-hair) solid var(--border-color); }
.house-tool-rail-node { position: relative; display: flex; }
/* The variants wedge: a real sibling button in the tool's corner (never a
   control nested inside the tool's <button>). The triangle is drawn by
   ::after; the button is the 12px corner quadrant around it. */
.house-tool-rail-wedge {
  position: absolute; right: 0; bottom: 0; width: 12px; height: 12px; padding: 0;
  background: transparent; border: 0; cursor: pointer;
}
.house-tool-rail-wedge::after {
  content: ''; position: absolute; right: 1px; bottom: 1px; width: 0; height: 0;
  border-left: 6px solid transparent; border-bottom: 6px solid var(--text-light);
}
.house-tool-rail-wedge:hover::after,
.house-tool-rail-wedge[aria-expanded='true']::after { border-bottom-color: var(--text-color); }
/* The flyout is a menu, not a second rail: `.house-menu` anchored beside the
   rail, because a rail discloses sideways and a menu otherwise drops down. */
.house-menu.house-tool-rail-flyout { top: 0; left: calc(100% + var(--space-xs)); right: auto; margin-top: 0; }
/* The checked variant reads by weight — the menu's own rows carry no pressed
   face, and a tint would be a second one. */
.house-menu-item[aria-checked='true'] { font-weight: 700; }

/* Footer bar: toggles │ command line │ readouts. `content-box`, so the hairline
   sits ON TOP of a full 24px row — the row is the target (§17), and a
   border-box 24px bar would leave every control in it 23px, under the floor. */
.house-status-bar {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: stretch;
  box-sizing: content-box; height: var(--frame-footer); flex-shrink: 0;
  background: var(--bg-color); color: var(--text-color);
  border-top: var(--rule-hair) solid var(--border-color);
  font-size: 11px; line-height: 1;
}
.house-status-bar-toggles { grid-column: 1; display: flex; align-items: stretch; gap: var(--space-xxs); padding: 0 var(--space-xs); border-right: var(--rule-hair) solid var(--border-color); }
.house-status-bar-main { grid-column: 2; display: flex; align-items: stretch; min-width: 0; overflow: hidden; }
.house-status-bar-readouts {
  grid-column: 3; display: flex; align-items: center; gap: var(--space-md); padding: 0 var(--space-sm);
  border-left: var(--rule-hair) solid var(--border-color); white-space: nowrap;
}
/* A toggle chip fills the row: its mark is small, its target is the full 24px. */
.house-status-toggle {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  min-height: 24px; padding: 0 var(--space-sm);
  background: transparent; color: var(--text-light); border: 0;
  font: inherit; font-size: 0.62rem; font-weight: 400; line-height: 1;
  text-transform: uppercase; letter-spacing: var(--tracking-chrome); white-space: nowrap;
  cursor: pointer; transition: var(--transition);
}
.house-status-toggle:hover { opacity: 0.5; }
.house-status-toggle:disabled { opacity: 0.35; cursor: default; }
.house-status-toggle > svg { width: 12px; height: 12px; }
/* The wash, the ink and the weight — and again no --active-border. The chip
   FILLS the row (§18d: the row is the target), so its top and bottom edges are
   already the footer's own hairline and the zone rule beside it; an edge of its
   own would double both. The weight is what the square cannot spend, since a
   square has no text. */
.house-status-toggle[aria-pressed='true'] { background: var(--active-bg); color: var(--text-color); font-weight: 700; }
/* A status field is a titleblock cell laid on its side: the §11 cell label
   beside a tabular value, rather than over it. */
.house-status-field { display: inline-flex; align-items: center; gap: var(--space-xs); min-height: 24px; white-space: nowrap; }
.house-status-field > .house-cell-label { display: inline; }
.house-status-field-value { font-weight: 700; font-variant-numeric: tabular-nums; }
/* A control standing in a field (a units select) is a value you change, not a
   number you read — it keeps the control's own weight. */
.house-status-field-value > .house-input,
.house-status-field-value > .house-select > .house-input { font-weight: 400; }

/* The command prompt: `<tool> >`, a message, then a BARE input. No border and
   no focus box — the caret is the affordance, and a boxed field inside a 24px
   bar is a second frame. Focus is still drawn, once, off the input: the prompt
   goes from grey to ink while the line has it (§17 — focus is never removed,
   only moved to where it does not frame the frame). */
.house-command-prompt {
  display: flex; align-items: center; gap: var(--space-sm); flex: 1 1 auto;
  min-width: 0; overflow: hidden; padding: 0 var(--space-sm);
  font-family: var(--font-mono); font-size: 11px;
}
.house-command-prompt-tool { flex-shrink: 0; color: var(--text-light); font-weight: 700; white-space: nowrap; transition: var(--transition); }
.house-command-prompt:focus-within > .house-command-prompt-tool { color: var(--text-color); }
.house-command-prompt-message { flex: 0 1 auto; min-width: 0; color: var(--text-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.house-command-prompt-message:empty { display: none; }
.house-command-prompt-message[data-tone='status'] { font-weight: 700; }
/* A refusal is the one line here that must not be skimmed past: ink, not grey.
   Still words and weight — never --danger, never motion (§2, §4). */
.house-command-prompt-message[data-tone='refusal'] { color: var(--text-color); font-weight: 700; }
/* The floor keeps somewhere to type when a long message takes the line. */
.house-command-prompt-input {
  flex: 1 1 0; min-width: 8ch; align-self: stretch; margin: 0; padding: 0;
  background: transparent; color: var(--text-color); border: 0;
  font: inherit;
}
.house-command-prompt-input::placeholder { color: var(--text-light); }
.house-command-prompt-input:focus-visible { outline: none; box-shadow: none; }

/* ---- The floating frame — the asset fills the view (§18, DR-037) --------
   The canvas runs edge to edge; the four surfaces float above it as islands.
   Floating is drawn flat (§3): a --bg-color island, a hairline on all four
   sides, and --frame-float of the work's ground showing around it. No shadow
   and no z-index: the chrome is later in the tree than the canvas, so it
   paints above it, and an overlay inside the chrome still competes in the
   page's own --layer-* order rather than in a layer this shell would make.
   The shell never paints --bg-canvas (§9a): that is the product's one line,
   on the element it passes as the canvas. */
.house-editor-shell { position: relative; overflow: hidden; width: 100%; height: 100%; min-height: 0; }
.house-editor-shell-canvas { position: absolute; inset: 0; }
.house-editor-shell-chrome {
  position: absolute; inset: 0; pointer-events: none;
  display: grid; gap: var(--frame-float); padding: var(--frame-float);
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas: 'header header header' 'rail . panel' 'footer footer footer';
}
/* An island: chrome's ground and one hairline per side. Between islands the
   clicks fall through to the canvas; on one they stop. */
.house-editor-float {
  pointer-events: auto; min-width: 0;
  background: var(--bg-color); color: var(--text-color);
  border: var(--rule-hair) solid var(--border-color);
}
.house-editor-shell-header { grid-area: header; }
.house-editor-shell-footer { grid-area: footer; }
/* The rail and the panel hug their content; the work shows below them. */
.house-editor-shell-rail { grid-area: rail; align-self: start; }
.house-editor-shell-panel {
  grid-area: panel; align-self: start; box-sizing: border-box; width: var(--frame-panel); max-height: 100%;
  overflow-y: auto; padding: var(--space-xs) var(--space-md) var(--space-md);
}
/* Each docked surface draws one hairline toward the canvas (§18). Inside an
   island the island draws all four, so the surface's own is dropped — one
   line per boundary, never two. The docked rules stay as they are for a
   frame that is not floating. */
.house-editor-float > .house-app-header { border-bottom: 0; }
.house-editor-float > .house-tool-rail { border-right: 0; }
.house-editor-float > .house-status-bar { border-top: 0; }
.house-editor-shell-panel > .house-panel-section:first-child { border-top: 0; }

/* ---- Command palette (§18e, DR-023) -------------------------------------
   One list, two hosts. The list is a listbox of rows: a name, its typed words,
   its keys, and a line saying why it matched. The ACTIVE row is the pressed
   face (--active-bg) and nothing else — no hue, no weight change, no left rule;
   that last one is ListRow's "current", a different meaning.

   An `unavailable` row (not yet, never) is muted ink and still selectable, so
   its reason can be read. It is never --danger: "not here" is not an error. */
.house-command-palette {
  display: flex; flex-direction: column; min-height: 0;
  background: var(--bg-color); color: var(--text-color);
  font-size: 14px; line-height: 1.35;
}
.house-command-palette-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--space-xs) 0; }
.house-command-palette-group-label { display: block; padding: var(--space-sm) var(--space-lg) var(--space-xxs); color: var(--text-light); }
/* A row is the default 32px tier, not the compact floor: the palette is a
   panel read and chosen from, like a dialog, not chrome squeezed into a 24px
   bar (§17's two sizes). A two-line row grows by its second line. The grid
   keeps words and keys right-aligned in their own columns, so a scanned list
   reads as a table. */
.house-command-palette-option {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center;
  column-gap: var(--space-md); row-gap: var(--space-xxs); min-height: 32px; box-sizing: border-box;
  padding: var(--space-xs) var(--space-lg); cursor: pointer;
}
.house-command-palette-option[aria-selected='true'] { background: var(--active-bg); }
.house-command-palette-leading { grid-column: 1; display: inline-flex; }
.house-command-palette-leading > svg { width: 16px; height: 16px; }
.house-command-palette-title { grid-column: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.house-command-palette-words { grid-column: 3; color: var(--text-light); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.house-command-palette-keys { grid-column: 4; display: inline-flex; gap: var(--space-xxs); }
/* A key is a hairline cap: the control boundary, square, no fill. */
.house-command-palette-key {
  display: inline-block; min-width: 1.5em; padding: 0 var(--space-xs); box-sizing: border-box;
  font: inherit; font-size: 11px; line-height: 16px; text-align: center; color: var(--text-light);
  border: var(--rule-hair) solid var(--border-strong);
}
.house-command-palette-sub {
  grid-column: 2 / -1; min-width: 0; color: var(--text-light); font-size: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.house-command-palette-option[data-tone='unavailable'] > .house-command-palette-title { color: var(--text-light); }
/* On the ACTIVE row every muted part goes to ink (§17, platform#137).
   --text-light is tuned for the GROUND (5.0:1 on white); over --active-bg it is
   4.02:1, under the 4.5 floor. Every other --active-bg consumer in this file
   already sets `color: var(--text-color)`; the palette row was the one place
   muted text sat on the fill. That includes an `unavailable` row's title: while
   it is the active row its sub line says why in words, which is what §2 asks
   to carry the meaning — and it is muted again the moment the row is left. */
.house-command-palette-option[aria-selected='true'] > .house-command-palette-title,
.house-command-palette-option[aria-selected='true'] > .house-command-palette-words,
.house-command-palette-option[aria-selected='true'] > .house-command-palette-sub,
.house-command-palette-option[aria-selected='true'] .house-command-palette-key { color: var(--text-color); }
.house-command-palette-empty { padding: var(--space-md) var(--space-lg); color: var(--text-light); font-size: 12px; }

/* Host 1 — the dialog. It is a Dialog (the scrim, the trap, Escape are the
   platform's), set high on the page like every palette the hand already knows,
   with its own field docked above the list and only the list scrolling. */
.house-dialog.house-command-palette-dialog[open] { align-items: flex-start; padding-top: 15vh; }
.house-command-palette-dialog > .house-dialog-panel { max-height: 70vh; }
.house-command-palette-dialog .house-dialog-body { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.house-command-palette-field {
  display: flex; align-items: center; gap: var(--space-md); flex-shrink: 0;
  padding: 0 var(--space-lg); border-bottom: var(--rule-hair) solid var(--border-color);
}
.house-command-palette-mode { flex-shrink: 0; transition: var(--transition); }
/* The field is bare, like the prompt's: focus is moved to the mode label going
   to ink, never removed (§17). */
.house-command-palette-field:focus-within > .house-command-palette-mode { color: var(--text-color); }
.house-command-palette-input {
  flex: 1 1 auto; min-width: 0; margin: 0; padding: var(--space-md) 0;
  background: transparent; color: var(--text-color); border: 0; font: inherit; font-size: 14px;
}
.house-command-palette-input::placeholder { color: var(--text-light); }
.house-command-palette-input:focus-visible { outline: none; box-shadow: none; }

/* Both hosts carry the same head and foot: what is typed, and which keys move
   it. The head of the popover is an ECHO of the product's command line — a
   static caret, no field — so there is still one place to type. */
.house-command-palette-head {
  display: flex; align-items: center; gap: var(--space-md); flex-shrink: 0;
  min-height: var(--space-2xl); padding: 0 var(--space-lg); box-sizing: border-box;
  border-bottom: var(--rule-hair) solid var(--border-color);
}
.house-command-palette-query { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.house-command-palette-query::after {
  content: ''; display: inline-block; width: var(--rule-hair); height: 1.1em;
  margin-left: 1px; vertical-align: text-bottom; background: var(--text-color);
}
.house-command-palette-query[data-empty='true'] { color: var(--text-light); }
/* Nothing typed: the caret waits where typing will start, before the hint. */
.house-command-palette-query[data-empty='true']::after { content: none; }
.house-command-palette-query[data-empty='true']::before {
  content: ''; display: inline-block; width: var(--rule-hair); height: 1.1em;
  margin-right: 1px; vertical-align: text-bottom; background: var(--text-color);
}
.house-command-palette-hints {
  display: flex; align-items: center; justify-content: center; gap: var(--space-lg); flex-shrink: 0;
  min-height: 24px; padding: var(--space-xs) var(--space-lg); box-sizing: border-box;
  border-top: var(--rule-hair) solid var(--border-color);
  color: var(--text-light); font-size: 11px;
}
.house-command-palette-hints > span { display: inline-flex; align-items: center; gap: var(--space-xxs); }

/* Host 2 — the floating panel. Centred high in the app by default, exactly
   where the dialog host's panel sits, and drawn with the dialog panel's edge:
   one object, whichever product shows it. Not modal, so no scrim — the canvas
   stays live behind it. `anchor` keeps the v2.6.0 placement over a prompt. */
.house-command-palette-popover {
  position: fixed; z-index: var(--layer-overlay); box-sizing: border-box;
  display: flex; flex-direction: column;
  max-width: var(--measure); max-height: 50vh;
  background: var(--bg-color); color: var(--text-color);
  border: var(--rule-frame) solid var(--border-strong);
}
.house-command-palette-popover[data-placement='center'] {
  top: 15vh; left: 0; right: 0; margin: 0 auto;
  width: calc(100% - 2 * var(--space-lg));
  max-height: min(70vh, calc(85vh - var(--space-xl)));
}
.house-command-palette-popover > .house-command-palette { flex: 1 1 auto; min-height: 0; }

/* ---- Motion floor: honour the OS ---------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .house-loading-mark { animation: none; }
}
