/*
 * Visual layer for the shared view tree. Structure + copy come from
 * client/src/views/*; this file owns appearance only, keyed on the `v-<type>`
 * classes the DOM renderer emits.
 *
 * The :root block below is GENERATED from client/src/tokens.ts — do not edit it
 * by hand. Change a token there and run `npm run tokens:css` to regenerate.
 */

/* tokens:start (generated from client/src/tokens.ts via npm run tokens:css — do not edit by hand) */
:root {
  /* color */
  --color-bg: #f5f8fc;
  --color-surface: #ffffff;
  --color-surface-sunken: #edf2f7;
  --color-ink: #1d2530;
  --color-muted: #5b6875;
  --color-line: #c8d3dc;
  --color-primary: #3b9df0;
  --color-primary-strong: #2f86d0;
  --color-primary-soft: #e4f1fd;
  --color-mint: #35c89a;
  --color-mint-soft: #e1f7ef;
  --color-grape: #8368ee;
  --color-grape-strong: #6b50d0;
  --color-jade: #12a079;
  --color-jade-strong: #0b7d66;
  --color-success: #35c89a;
  --color-success-text: #1b7355;
  --color-success-soft: #e3f3ec;
  --color-caution: #ffc63d;
  --color-caution-text: #8a6a0a;
  --color-caution-soft: #fff3d6;
  --color-danger: #ff7a66;
  --color-danger-text: #b23a22;
  --color-danger-soft: #f8e9e3;
  --color-danger-line: #e7c2b6;
  --color-info: #3b9df0;
  --color-info-text: #1e6ca8;
  --color-info-soft: #e4f1fd;
  --color-info-dot-1: #9ccdf5;
  --color-info-dot-2: #cfe6fa;
  --color-faint: #9aa6b2;

  /* type */
  --font-sans: DM Sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: DM Sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-wordmark: Fredoka, ui-rounded, "SF Pro Rounded", ui-sans-serif, system-ui, sans-serif;
  --text-xs: 12px;
  --text-sm: 14px;
  --text-base: 16px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --text-3xl: 38px;
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --tracking-label: 0.08em;
  --tracking-tight: -0.02em;

  /* spacing + radius */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-full: 999px;
}
/* tokens:end */

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--color-line);
  color: var(--color-ink);
  font-family: var(--font-sans);
}

button,
input {
  font: inherit;
}

.stage {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}

#root {
  display: contents;
}

/* ---- Phone shell ---------------------------------------------------------- */

.v-jarvis-screen {
  position: relative;
  /* Own stacking context (position + z-index) so the drawer's negative
   * z-index (JAR-130) resolves *within* the shell — behind the chat card but
   * above this element's own background — instead of escaping to the root
   * context and hiding beneath the shell entirely. */
  z-index: 0;
  width: min(390px, 100%);
  /* A *bounded* shell height, not just a floor: the shell is the fixed
   * viewport frame the internal scroll regions (`.v-conversation-body` and
   * `.v-deck-full-screen-body`, both `overflow-y: auto`) are meant to scroll
   * inside. With only `min-height`, tall content (a long deck or transcript)
   * stretched the shell past the viewport, so those regions never engaged and
   * the window scrolled instead — and the `inset: 0` full-screen overlay,
   * sized to the grown shell and dragged by its scroll offset, slid its header
   * off-screen and exposed the page background below it. Capping the height
   * keeps the overflow inside the auto regions where it belongs. */
  height: min(844px, calc(100vh - 48px));
  /* The shell paints the drawer's surface tone: the drawer sits behind the
   * chat card (JAR-130 reveal), so this is what shows in the revealed gutter. */
  background: var(--color-surface);
  /* JAR-265: no frame border. The shell used to carry a faint 8%-ink hairline
   * (a "white/grey outline around the viewport" in the centered-card layout);
   * the narrow-viewport rule already dropped it on phones, so removing it here
   * gives the frameless treatment parity across widths. The shell's edge still
   * reads via its surface-on-page-background contrast. */
  display: flex;
  flex-direction: column;
  /* `clip`, not `hidden`: the `inset: 0` full-screen overlay (a positioned
   * descendant whose containing block is this shell) adds its own box to the
   * shell's scroll-overflow, so with `hidden` the shell stayed a scroll
   * container — and a focus/anchor scroll could nudge it, dragging the overlay
   * (header included) out of view. `clip` clips identically for the drawer
   * reveal but makes the shell genuinely non-scrollable (scrollTop pinned at
   * 0), so the overlay can never be displaced. */
  overflow: clip;
}

/* The chat card — the opaque layer holding the app bar, transcript, and
 * composer. It fills the shell and paints over the drawer that sits behind it;
 * opening the drawer slides it aside (see the drawer section below). */
.v-chat-card {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  overflow: hidden;
  transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ---- App bar -------------------------------------------------------------- */

.v-app-bar {
  height: 50px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-5);
}

/* JAR-265: the Connections button — a plain, borderless line-icon button
 * mirroring `.v-drawer-toggle` in the opposite corner (was a 30×30 bordered
 * "DM" avatar chip). */
.v-profile {
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-app-bar-left {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.v-drawer-toggle {
  position: relative;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* JAR-321 S4 behavior 4: the closed-drawer toggle's aggregate unread count —
 * the discovery affordance for unread rows while the drawer itself is
 * closed. Shown whenever the count is > 0 (no explicit drawer-open
 * suppression — design Open Risk, reversible on review). */
.v-drawer-unread-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  font-size: 10px;
  font-weight: var(--font-weight-semibold);
  line-height: 16px;
  text-align: center;
}

/* ---- Conversation body ---------------------------------------------------- */

.v-conversation-body {
  flex: 1 1 auto;
  overflow: auto;
  display: flex;
  flex-direction: column;
}

/* ---- Icons ---------------------------------------------------------------- */

.v-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.v-icon svg {
  width: 20px;
  height: 20px;
  display: block;
}

/* ---- Empty / first-run ---------------------------------------------------- */

/* JAR-265: the daily-home column follows the D34-0 design's three-zone model —
 * a top-pinned greeting, one elastic `flex-grow` mic-hero zone that centers the
 * orb in the leftover space, and the bottom-pinned shortcuts + "Type instead".
 * Blocks stack flush (no container gap); each zone owns its own spacing, so a
 * change to one gap never moves the others. */
.v-empty-state {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0;
  /* Greeting sits 20px below the app bar; 24px of breathing room at the base. */
  padding: 20px 24px 24px;
}

.v-invitation {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  max-width: 320px;
}

/* The one elastic zone (D34-0 mic-hero, `flex-grow:1` + `justify-content:center`):
 * the orb centers in the space between the greeting and the shortcuts. The orb
 * keeps its own `margin-bottom` reserve for the overflowing mic-core, so the
 * full ring-plus-mic-button visual (not just the ring) is what gets centered. */
.v-mic-hero-zone {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
}

.v-eyebrow {
  color: var(--color-faint);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-label);
}

.v-headline {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  line-height: 1.08;
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
}

.v-subhead {
  color: var(--color-muted);
  font-size: var(--text-base);
  line-height: 1.4;
}

.v-mic-orb {
  width: 236px;
  height: 236px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  /* Small reserve for the mic-core, which dips 6px past the ring's bottom edge
   * (`bottom: -6px`). JAR-265: cut from 56px — that larger reserve existed to
   * clear the since-removed "Tap to talk" caption, and it was shoving the orb
   * upward off-centre in the elastic hero zone. The ring now centres per D34-0. */
  margin-bottom: 8px;
}

.v-ripple-outer,
.v-ripple-mid {
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-ripple-outer {
  width: 236px;
  height: 236px;
  /* Otto primary (`--color-primary` #3B9DF0) at low opacity — rgba-for-opacity
   * idiom, same pattern as the mascot speech bubble's shadow below. */
  background: rgba(59, 157, 240, 0.06);
}

.v-ripple-mid {
  width: 188px;
  height: 188px;
  background: rgba(59, 157, 240, 0.1);
}

/* JAR-231 behavior 1 (JAR-258 behavior 4 retuned the size): the listening
 * pose sits centered inside the rings via the ripple-mid flex container
 * above — sized well within the 188px ring. */
.v-mic-orb .v-mascot-pose {
  width: 152px;
}

.v-mic-core {
  /* JAR-231 behavior 1/2 (JAR-258 behavior 4 retuned the size/offset to the
   * frozen board): net-new mascot placement moves the mic-core out of flow
   * so it overlaps the outer ring's bottom edge instead of sitting centered
   * — its disabled/reserved markup (behavior 2) is untouched. */
  position: absolute;
  left: 50%;
  bottom: -6px;
  margin-left: -30px;
  width: 60px;
  height: 60px;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-mic-core .v-icon svg {
  /* Scaled with the button's 104px->60px retune (JAR-258 behavior 4) to keep
   * the same glyph-to-button proportion; no frozen value pins this size. */
  width: 22px;
  height: 22px;
}

/* JAR-265: the "Tap to talk" caption (`.v-mic-caption`) was removed to match
 * the D34-0 daily-home design, which carries none. */

.v-shortcuts {
  /* JAR-265: bottom-pinned block (D34-0), gap 12, a hair of top padding. */
  flex-shrink: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding-top: 4px;
}

.v-shortcuts-label {
  color: var(--color-muted);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.v-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
}

.v-suggestion-chip {
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-ink);
  padding: 9px 14px;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.v-type-instead {
  /* JAR-265: the D34-0 chips → "Type instead" gap (44px). The mic-hero zone's
   * `flex-grow` already pins this block to the bottom, so no `margin-top:auto`
   * tug-of-war is needed. */
  flex-shrink: 0;
  margin-top: 44px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-ink);
  padding: 11px 16px;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

.v-type-instead .v-icon svg {
  width: 18px;
  height: 18px;
}

/* ---- Transcript ----------------------------------------------------------- */

.v-transcript-region {
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--space-5) 18px;
}

.v-turn {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-3) 0 22px;
}

.v-caption {
  align-self: flex-end;
  color: var(--color-muted);
  font-size: var(--text-xs);
}

.v-request-bubble {
  align-self: flex-end;
  max-width: 82%;
  border-radius: var(--radius-lg);
  /* JAR-231 behavior 6 (Otto 13): the asymmetric "tail" corner, flattened. */
  border-bottom-right-radius: 6px;
  background: var(--color-primary);
  color: var(--color-surface);
  padding: var(--space-3) var(--space-4);
  line-height: 1.35;
  font-size: var(--text-base);
  overflow-wrap: anywhere;
}

/* JAR-321 S4 behavior 3: a routine session's opener turn renders
 * assistant-first — this lead-in line sits where `.v-caption`/
 * `.v-request-bubble` would, left-aligned like the assistant response below
 * it (never the right-aligned "You" treatment). */
.v-opener-lead-in {
  align-self: flex-start;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.v-response-surface {
  color: var(--color-ink);
  font-size: var(--text-base);
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.v-response-surface[data-variant="thinking"],
.v-response-surface[data-variant="error"],
.v-response-surface[data-variant="done"] {
  white-space: normal;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* ---- Surface lead-in composition -------------------------------------------
   A settled surface turn's streamed lead-in prose, stacked above its resolved
   surface node. `.v-surface-composition` is a plain spacing wrapper only — no
   stacking-context styling, fill, or overflow clipping, since it becomes a new
   ancestor of the deck receipt (whose own JAR-42 edge treatment depends on
   owning its stacking context). `.v-surface-lead-in` reuses the settled-prose
   markdown treatment (`.v-response-surface[data-variant="done"]`) for visual
   continuity with the streaming→settled transition. */

.v-surface-composition {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.v-surface-lead-in {
  color: var(--color-ink);
  font-size: var(--text-base);
  line-height: 1.55;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* ---- Markdown blocks + inline marks ---------------------------------------
   Behavior-1 block subset (headings, paragraphs, lists, fenced code) and the
   behavior-2 inline subset (bold, italic, inline code, links). Block nodes
   own their own spacing — the surface itself no longer relies on
   `white-space: pre-wrap` for the `done` variant (see above). */

.v-md-h1,
.v-md-h2,
.v-md-h3,
.v-md-h4,
.v-md-h5,
.v-md-h6 {
  font-weight: var(--font-weight-semibold);
  line-height: 1.3;
  margin: 0;
}

.v-md-p {
  margin: 0;
  line-height: 1.55;
}

.v-md-ul,
.v-md-ol {
  margin: 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.v-md-pre {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  overflow-x: auto;
  white-space: pre;
}

.v-md-code {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: var(--text-sm);
}

.v-md-pre .v-md-code {
  white-space: pre;
}

.v-md-strong {
  font-weight: var(--font-weight-semibold);
}

.v-md-em {
  font-style: italic;
}

.v-md-link {
  color: var(--color-primary);
  text-decoration: underline;
}

/* ---- Thinking ------------------------------------------------------------- */

.v-presence-indicator {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.v-dot-row {
  display: flex;
  gap: var(--space-1);
}

/* JAR-241 behavior 8: the thinking dots ramp across the info loading-dot
 * ramp (base + the two dot tints) rather than a flat muted fill. */
.v-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--color-info);
  animation: pulse 900ms ease-in-out infinite;
}

.v-dot:nth-child(2) {
  background: var(--color-info-dot-1);
  animation-delay: 120ms;
}

.v-dot:nth-child(3) {
  background: var(--color-info-dot-2);
  animation-delay: 240ms;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 0.35;
    transform: translateY(0);
  }
  50% {
    opacity: 1;
    transform: translateY(-2px);
  }
}

.v-skeleton {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.v-skeleton-line {
  height: 13px;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
}

/* ---- Error ---------------------------------------------------------------- */

.v-error-lead {
  color: var(--color-ink);
  font-size: var(--text-base);
  line-height: 1.5;
}

.v-error-card {
  border-radius: var(--radius-md);
  background: var(--color-danger-soft);
  /* JAR-241 behavior 5: the danger hairline (`--color-danger-line`), not the
   * coral-at-opacity rgba border. */
  border: 1px solid var(--color-danger-line);
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.v-error-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-danger-text);
}

.v-error-head .v-icon svg {
  width: 16px;
  height: 16px;
}

.v-error-title {
  font-size: var(--text-sm);
  font-weight: var(--font-weight-bold);
  color: var(--color-danger-text);
}

.v-error-detail {
  color: var(--color-danger-text);
  font-size: var(--text-sm);
  line-height: 1.4;
}

.v-recovery-actions {
  display: flex;
  gap: var(--space-2);
}

.v-retry-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary-strong);
  color: var(--color-surface);
  padding: 9px 14px;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

.v-retry-button .v-icon svg {
  width: 15px;
  height: 15px;
}

.v-dismiss-button {
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-ink);
  padding: 9px 14px;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

/* ---- Composer dock -------------------------------------------------------- */

.v-composer-dock {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-3) var(--space-4) 18px;
}

.v-jarvis-screen.composer-hidden .v-composer-dock {
  display: none;
}

.v-input-pill {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 50px;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
}

.v-input-pill:focus-within {
  border-color: var(--color-primary);
}

/* JAR-231 behavior 5 (Otto 19 dock): typing switches the pill's own border
 * to primary (not just on focus); turn-live swaps its ground to --color-bg. */
.v-composer-dock.is-typing .v-input-pill {
  border-color: var(--color-primary);
}

.v-composer-dock.is-stopping .v-input-pill {
  background: var(--color-bg);
}

/* JAR-265: the input pill's leading search icon was removed; its icon rule
 * went with it. */

.v-text-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--color-ink);
  font-size: var(--text-base);
  padding: 14px 0;
}

.v-text-input::placeholder {
  color: var(--color-faint);
}

/* JAR-231 behavior 5 (Otto 19 dock): at rest the trailing control is a
 * reserved mic in a soft info circle (primary-stroked glyph, not filled) —
 * .is-typing/.is-stopping below override to the solid primary + white glyph
 * treatment. */
.v-dock-action {
  width: 50px;
  height: 50px;
  flex: 0 0 auto;
  border: 0;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-info-soft);
  color: var(--color-primary);
  cursor: pointer;
}

.v-dock-action[disabled] {
  cursor: default;
}

.v-dock-action .v-icon svg {
  width: 22px;
  height: 22px;
}

.v-dock-action .v-icon:nth-child(2),
.v-dock-action .v-icon:nth-child(3) {
  display: none;
}

.v-composer-dock.is-typing .v-dock-action {
  background: var(--color-primary);
  color: var(--color-surface);
}

.v-composer-dock.is-typing .v-dock-action .v-icon:nth-child(1) {
  display: none;
}

.v-composer-dock.is-typing .v-dock-action .v-icon:nth-child(2) {
  display: inline-flex;
}

/* Stop (JAR-140 behavior 8) — a third trailing-control state, taking
 * precedence over typing; mutually exclusive with .is-typing (syncDockAction
 * never sets both). Per Paper artboard "Composer — trailing control states"
 * (restyled to the Otto 19 dock's solid-primary treatment, JAR-231 behavior
 * 5), Stop takes the slot the send arrow uses and shares its color — the
 * same control language, distinguished only by the stop glyph, not a danger
 * color. */
.v-composer-dock.is-stopping .v-dock-action {
  background: var(--color-primary);
  color: var(--color-surface);
}

.v-composer-dock.is-stopping .v-dock-action .v-icon:nth-child(1),
.v-composer-dock.is-stopping .v-dock-action .v-icon:nth-child(2) {
  display: none;
}

.v-composer-dock.is-stopping .v-dock-action .v-icon:nth-child(3) {
  display: inline-flex;
}

/* ---- Narrow viewport ------------------------------------------------------ */

@media (max-width: 520px) {
  .stage {
    padding: 0;
  }

  .v-jarvis-screen {
    width: 100%;
    /* Full-viewport frame on mobile — `dvh` so the shell tracks the dynamic
     * viewport as the browser chrome collapses, and bounded (not `min-`) for
     * the same reason as the base rule: the internal regions do the scrolling,
     * never the shell. */
    height: 100dvh;
    border: 0;
    box-shadow: none;
  }
}

/* ---- Connections hub ------------------------------------------------------ */

.hub-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: none;
  flex-direction: column;
  justify-content: flex-end;
  /* Otto ink (`--color-ink` #1D2530) at low opacity — rgba-for-opacity idiom,
   * same pattern as the mascot speech bubble's shadow below. */
  background: rgba(29, 37, 48, 0.32);
}

.hub-overlay.is-open {
  display: flex;
}

.v-connections-hub {
  background: var(--color-surface);
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-height: 88%;
  overflow: auto;
}

.v-hub-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.v-hub-title {
  font-family: var(--font-display);
  /* JAR-265: unify the settings-family header title size on --text-2xl. */
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
}

.v-hub-close-button {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  /* JAR-265: match the shared settings-family close control's sunken fill
   * (was --color-surface white, which read as a white-on-white hairline). */
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-hub-close-button::before {
  content: "×";
  font-size: 20px;
  line-height: 1;
}

.v-account-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-md);
}

.v-account-avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  background: var(--color-line);
  flex: 0 0 auto;
}

.v-account-name {
  font-weight: var(--font-weight-semibold);
}

.v-account-email {
  font-size: var(--text-sm);
  color: var(--color-muted);
}

.v-provider-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.v-provider-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

.v-provider-label {
  font-weight: var(--font-weight-semibold);
}

.v-provider-services {
  font-size: var(--text-sm);
  color: var(--color-muted);
}

.v-provider-action {
  flex: 0 0 auto;
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  padding: 8px 16px;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

.v-provider-action[data-action="dismiss-error"] {
  background: var(--color-surface);
  border-color: var(--color-line);
  color: var(--color-muted);
}

.v-provider-error-actions {
  display: flex;
  gap: var(--space-2);
}

.v-provider-indicator {
  flex: 0 0 auto;
  font-size: var(--text-sm);
  color: var(--color-muted);
}

.v-provider-connected {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-success-text);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
}

.v-provider-connected .v-icon svg {
  width: 18px;
  height: 18px;
}

.v-hub-footer {
  font-size: var(--text-xs);
  color: var(--color-muted);
  line-height: 1.5;
}

/* ---- Past-conversations drawer (JAR-130) ----------------------------------
 * A left-anchored panel that is *revealed* — not overlaid — by sliding the
 * chat card aside (design: chat card pushed right with rounded left corners and
 * a left shadow, E2 · Drawer). The drawer sits behind the chat card via a
 * negative z-index, so it paints between the shell's background and the opaque,
 * in-flow chat card. Deliberately NOT positioned/transformed itself: keeping
 * the chat card the only transformed layer leaves the deck full-screen
 * overlay's containing block anchored at the shell (JAR-102), never re-rooted
 * onto this drawer. */

.drawer-overlay {
  position: absolute;
  inset: 0;
  /* Behind the in-flow chat card (which paints at the normal-flow layer),
   * above the shell's own background. */
  z-index: -1;
  display: none;
}

.drawer-overlay.is-open {
  display: block;
}

.v-drawer-overlay {
  position: absolute;
  inset: 0;
}

.v-conversation-drawer {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 86%;
  max-width: 338px;
  background: var(--color-surface);
  display: flex;
  flex-direction: column;
}

/* The reveal: while the drawer is open, the chat card slides right by the
 * drawer's width (86% of the shell), leaving a sliver, and picks up the rounded
 * left corners + left-edge shadow that read it as a card lifted off the drawer. */
.v-jarvis-screen.drawer-open .v-chat-card {
  transform: translateX(86%);
  border-top-left-radius: 26px;
  border-bottom-left-radius: 26px;
}

.v-drawer-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-4) var(--space-3);
  flex: 0 0 auto;
}

/* Otto Phase 5 (JAR-234 behavior 5): the Otto mascot (neutral pose),
 * replacing the placeholder blue-circle-with-dot mark this class pair
 * previously drew — retained only as dead selectors would be, so removed
 * rather than left unused. Sized 30×30 per the frozen mock (FRS-0); the
 * mascot's own illustration palette is the sanctioned raw-hex exception
 * (Testing Decisions: "Mascot"), not a token-discipline gap. */
.v-drawer-brand .v-mascot-pose {
  width: 30px;
  flex: 0 0 auto;
}

.v-drawer-wordmark-text {
  font-family: var(--font-wordmark);
  font-size: var(--text-lg);
  font-weight: var(--font-weight-bold);
  color: var(--color-ink);
}

/* Sticker shortcuts (JAR-314 behaviors 1, 5) — three static die-cut tiles
 * above the conversation history, matching HSP-0's "flex flex-col
 * gap-[14px]" block: To-do + Routines side by side, Shopping below at a
 * fixed width. Every fill/border/shadow color resolves through a token
 * (TIER 1 fidelity) — box-shadow included, unlike this file's usual
 * rgba-for-opacity exception. */
.v-sticker-shortcuts {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 0 var(--space-4) var(--space-3);
  flex: 0 0 auto;
}

.v-sticker-row {
  display: flex;
  gap: var(--space-3);
}

/* Row 1 (To-do + Routines): two equal-width tiles. */
.v-sticker-row[data-testid="sticker-row-1"] > .v-sticker-tile {
  flex: 1 1 0;
}

/* Row 2 (Shopping): a single fixed-width tile, left-aligned. */
.v-sticker-row[data-testid="sticker-row-2"] > .v-sticker-tile {
  width: 134px;
  flex: 0 0 auto;
}

.v-sticker-tile {
  position: relative;
  min-height: 104px;
  padding: 13px;
  border-radius: 18px;
  border: 2px solid transparent;
  display: flex;
  flex-direction: column;
  text-align: left;
  cursor: pointer;
}

.v-sticker-tile[data-variant="todo"] {
  background: var(--color-primary);
  border-color: var(--color-primary-strong);
  box-shadow: var(--color-primary-strong) 3px 4px 0px;
}

.v-sticker-tile[data-variant="routines"] {
  background: var(--color-grape);
  border-color: var(--color-grape-strong);
  box-shadow: var(--color-grape-strong) 3px 4px 0px;
}

.v-sticker-tile[data-variant="shopping"] {
  background: var(--color-jade);
  border-color: var(--color-jade-strong);
  box-shadow: var(--color-jade-strong) 3px 4px 0px;
}

.v-sticker-tile-icon {
  color: var(--color-surface);
}

.v-sticker-tile-icon .v-icon svg {
  width: 28px;
  height: 28px;
}

.v-sticker-tile-label {
  margin-top: auto;
  /* The frozen mock's prose calls for the wordmark's own display font here,
   * but JAR-230's hardened rule confines that font to the wordmark alone
   * everywhere else in the app (QA-B3-1) — out of this contract's TIER 1
   * fidelity scope (colors only), so DM Sans stays consistent with every
   * other label in the drawer instead. */
  font-family: var(--font-display);
  font-weight: var(--font-weight-semibold);
  font-size: var(--text-base);
  color: var(--color-surface);
}

.v-drawer-list {
  flex: 1 1 auto;
  overflow: auto;
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
}

.v-drawer-group-header {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}

.v-session-row {
  position: relative;
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-3);
  border: none;
  background: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  color: var(--color-ink);
}

/* JAR-321 S4 behavior 4: the in-drawer unread dot — the row's own "from
 * Otto" signal, gone the instant the row is opened (no durable marker). */
.v-session-unread-dot {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
}

.v-session-row[data-active="true"] {
  background: var(--color-primary-soft);
}

.v-session-title {
  font-size: var(--text-sm);
  font-weight: var(--font-weight-regular);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.v-drawer-empty {
  padding: var(--space-6) var(--space-4);
  color: var(--color-muted);
  font-size: var(--text-sm);
  text-align: center;
}

.v-drawer-footer {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4);
  border-top: 1px solid var(--color-line);
}

.v-drawer-settings {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: none;
  background: none;
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  cursor: default;
  padding: var(--space-2);
}

.v-drawer-new-chat {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: none;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
}

/* Icon + label share the footer buttons' currentColor (muted for Settings,
 * surface for the primary New chat pill). */
.v-drawer-settings .v-icon svg,
.v-drawer-new-chat .v-icon svg {
  width: 18px;
  height: 18px;
}

/* ---- Destination placeholder overlay (JAR-314 behaviors 2-4) --------------
 * The shared titled empty-screen surface every sticker tile lands on this
 * slice — a dimmed scrim (mirrors `.hub-overlay`'s rgba-for-opacity idiom)
 * behind a centered card, so the scrim area itself is a real, reachable
 * backdrop-tap target (contract Assumptions), not just a defensive no-op. */

.destination-overlay {
  position: absolute;
  inset: 0;
  z-index: 11;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(29, 37, 48, 0.32);
}

.destination-overlay.is-open {
  display: flex;
}

.v-destination-placeholder {
  position: relative;
  width: min(300px, 84%);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-5) var(--space-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
}

.v-destination-placeholder-header {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
}

.v-destination-placeholder-close {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-destination-placeholder-close::before {
  content: "×";
  font-size: 20px;
  line-height: 1;
}

.v-destination-placeholder-icon svg {
  width: 40px;
  height: 40px;
  color: var(--color-primary);
}

.v-destination-placeholder-title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-destination-placeholder-subtitle {
  font-size: var(--text-sm);
  color: var(--color-muted);
}

/* ---- Shared Settings backdrop (JAR-219) -----------------------------------
 * One dimming scrim for the entire Settings-family stack (settings sheet +
 * its category/sharing-rules/family/memory subviews) instead of each
 * sliding wrapper below painting its own — opening a subview no longer
 * compounds the dim. A persistent, screen-appended node (mirrors the
 * wrappers' own persistent-container precedent, JAR-201) toggled by the
 * wiring module whenever any Settings-family overlay is open. Sits below
 * the lowest overlay wrapper (`.settings-overlay`, z10) and above the chat
 * shell, and fades via opacity on the wrappers' own 240ms/cubic-bezier
 * timing so it never pops. */
.settings-backdrop {
  position: absolute;
  inset: 0;
  z-index: 9;
  background: #0b141c12;
  opacity: 0;
  transition: opacity 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
}

.settings-backdrop.is-open {
  opacity: 1;
}

/* JAR-265: the settings-family sheets' iPhone-style drag grabber
 * (`.v-sheet-grabber`) was removed from every surface. */

/* ---- Settings sheet (JAR-157, Paper 8ZG-0) --------------------------------
 * A fly-up sheet over the chat screen, modeled on the Connections hub's
 * overlay family (`.hub-overlay` above) but with the design's own, much
 * lighter backdrop and sheet geometry — not reused hub CSS (design Decision
 * 1). */

/* JAR-201: the transform + transition live on this persistent container
 * (appended once in mount-app.ts) rather than on the `replaceChildren`-
 * rebuilt `.v-settings-sheet` inside it — see `.family-overlay` below for
 * why. Unlike the sub-view overlays' right-to-left push, the top-level sheet
 * flies up (translateY) to match its bottom-anchored "fly-up sheet"
 * geometry. */
.settings-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  transform: translateY(100%);
  transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
}

.settings-overlay.is-open {
  transform: translateY(0);
  pointer-events: auto;
}

.v-settings-sheet {
  position: absolute;
  top: 54px;
  left: 0;
  width: 100%;
  height: 790px;
  max-height: calc(100% - 54px);
  background: var(--color-bg);
  border-top-left-radius: 22px;
  border-top-right-radius: 22px;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

.v-settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px 28px 18px;
}

.v-settings-title {
  font-family: var(--font-display);
  /* JAR-265: unify the settings-family header title size on --text-2xl. */
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

.v-settings-close-button {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-settings-close-button::before {
  content: "×";
  font-size: 20px;
  line-height: 1;
}

.v-settings-account-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 28px;
  padding: 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

.v-settings-account-avatar {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: #fff;
  font-size: 21px;
  font-weight: var(--font-weight-semibold);
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-settings-account-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.v-settings-account-name {
  font-size: 17px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.v-settings-account-email {
  font-size: 13.5px;
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.v-settings-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-5) 28px 26px;
}

.v-settings-group-label {
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
  text-transform: uppercase;
  margin-bottom: var(--space-2);
}

.v-settings-group-card {
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.v-settings-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  cursor: default;
}

.v-settings-row:not(:last-child) {
  border-bottom: 1px solid var(--color-line);
}

.v-settings-row .v-icon {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-settings-row .v-icon svg {
  width: 18px;
  height: 18px;
}

.v-settings-row-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.v-settings-row-title {
  font-size: 15.5px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-settings-row-subtitle {
  font-size: var(--text-xs);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.v-settings-row-chip {
  flex: 0 0 auto;
  background: var(--color-success-soft);
  color: var(--color-success-text);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  padding: 4px 10px;
  border-radius: var(--radius-full);
}

/* Otto Phase 5 (JAR-234 behavior 1): the trailing disclosure chevron every
 * row shows now, wired or inert — retires `.v-settings-row-soon`. A
 * dedicated wrapper class (not a bare `.v-icon`) so this can override the
 * leading-icon slot's 32px primary-soft box treatment below without a
 * specificity fight. */
.v-settings-row-chevron {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-line);
}

.v-settings-row-chevron .v-icon {
  width: 18px;
  height: 18px;
  background: none;
}

.v-settings-row-chevron .v-icon svg {
  width: 18px;
  height: 18px;
}

.v-settings-sign-out {
  display: block;
  margin: 8px auto 34px;
  border: none;
  background: none;
  color: var(--color-danger-text);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

/* ---- Sharing rules screen (JAR-188 behaviors 10-13) ------------------------
 * Reached from the settings sheet's "Sharing rules" row — the same
 * overlay/sheet geometry as `.settings-overlay` above, a sibling screen
 * rather than a restyled one. Frozen design tokens
 * (workflows/2026-08-31-jar-188-phase-5-policy-engine/design/tokens.css)
 * already match this app's own custom properties value-for-value. */
/* JAR-201: transform + transition on this persistent container, not the
 * `replaceChildren`-rebuilt `.v-sharing-rules` sheet — see `.family-overlay`
 * above. */
.sharing-rules-overlay {
  position: absolute;
  inset: 0;
  z-index: 11;
  transform: translateX(100%);
  transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
}

.sharing-rules-overlay.is-open {
  transform: translateX(0);
  pointer-events: auto;
}

.v-sharing-rules {
  position: absolute;
  top: 54px;
  left: 0;
  width: 100%;
  height: 790px;
  max-height: calc(100% - 54px);
  background: var(--color-bg);
  border-top-left-radius: 22px;
  border-top-right-radius: 22px;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

.v-sharing-rules-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
}

/* Otto Phase 5 (JAR-234 behavior 6): muted ink (was primary blue) — the
 * cross-surface chrome-consistency guard, matching Memory/Family's own back
 * row. */
.v-sharing-rules-back {
  flex: 1 1 auto;
  text-align: left;
  border: none;
  background: none;
  padding: 0;
  color: var(--color-muted);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.v-sharing-rules-close-button {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-sharing-rules-close-button::before {
  content: "×";
  font-size: 20px;
  line-height: 1;
}

.v-sharing-rules-title-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0 28px 18px;
}

.v-sharing-rules-title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

.v-sharing-rules-subtitle {
  font-size: 15px;
  line-height: 22px;
  color: var(--color-muted);
}

.v-sharing-rules-error {
  margin: 0 28px var(--space-3);
  padding: var(--space-3);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

.v-sharing-rules-section-label {
  margin: var(--space-4) 28px var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-muted);
}

.v-sharing-policy-list {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  margin: 0 28px;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.v-sharing-policy-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-line);
}

.v-sharing-policy-row:last-child {
  border-bottom: none;
}

.v-sharing-everything-else-card {
  flex-shrink: 0;
  margin: 0 28px;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.v-sharing-everything-else-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
}

.v-sharing-policy-row-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Otto Phase 5 (JAR-234 behavior 2): primary-soft slot (was mint-soft),
 * stroke-colored primary — the seven categories' new line-icon idiom. The
 * Everything-else row reuses this same class for its (unchanged) emoji
 * glyph, so the font-size stays for that text glyph's own sizing. */
.v-sharing-policy-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: var(--color-primary-soft);
  color: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
}

.v-sharing-policy-icon .v-icon {
  width: 16px;
  height: 16px;
}

.v-sharing-policy-icon .v-icon svg {
  width: 16px;
  height: 16px;
}

.v-sharing-policy-row-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.v-sharing-policy-row-label {
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

/* Otto Phase 5 (JAR-234 behavior 2): the per-category verbatim static
 * subtitle — a new node this phase adds under the label. */
.v-sharing-policy-row-subtitle {
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--color-muted);
}

.v-sharing-everything-else-subtitle {
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--color-muted);
}

.v-sharing-policy-diverged {
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--color-mint);
  font-weight: var(--font-weight-medium);
}

.v-sharing-policy-control {
  display: flex;
  gap: 3px;
  padding: 3px;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
}

.v-sharing-policy-option {
  flex: 1 1 0;
  border: none;
  border-radius: var(--radius-full);
  padding: 6px 0;
  background: none;
  color: var(--color-muted);
  font-size: 13px;
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  text-align: center;
}

.v-sharing-policy-option[data-active="true"] {
  background: var(--color-primary);
  color: #fff;
  font-weight: var(--font-weight-semibold);
}

.v-sharing-rules-summary {
  margin: var(--space-3) 28px 0;
  font-size: var(--text-sm);
  color: var(--color-muted);
}

.v-sharing-rules-footer {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5) 28px 34px;
}

.v-sharing-rules-footer-caption {
  font-size: var(--text-xs);
  line-height: 18px;
  color: var(--color-muted);
}

.v-sharing-rules-reset {
  align-self: center;
  border: none;
  background: none;
  padding: 0;
  color: var(--color-primary);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

/* ---- Memory overlay (JAR-200 behaviors 6-9) --------------------------------
 * Reached from the settings sheet's renamed "Memory" row — the same overlay/
 * sheet geometry as `.settings-overlay` above, a sibling screen rather than
 * a restyled one. Frozen design tokens
 * (workflows/2026-09-01-jar-200-memory-view/design/tokens.css) already match
 * this app's own custom properties value-for-value. */
.memory-overlay {
  position: absolute;
  inset: 0;
  z-index: 13;
  transform: translateX(100%);
  transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
}

.memory-overlay.is-open {
  transform: translateX(0);
  pointer-events: auto;
}

.v-memory {
  position: absolute;
  top: 54px;
  left: 0;
  width: 100%;
  height: 790px;
  max-height: calc(100% - 54px);
  background: var(--color-bg);
  border-top-left-radius: 22px;
  border-top-right-radius: 22px;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

.v-memory-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
}

/* Otto Phase 5 (JAR-234 behavior 6): muted ink (was primary blue) — the
 * cross-surface chrome-consistency guard, matching Sharing rules/Family's
 * own back row. */
.v-memory-back {
  flex: 1 1 auto;
  text-align: left;
  border: none;
  background: none;
  padding: 0;
  color: var(--color-muted);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.v-memory-close-button {
  /* JAR-265: match the shared settings-family close control — a 32×32
   * bordered sunken circle (was a 30×30 borderless white circle). */
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-memory-close-button::before {
  content: "×";
  font-size: 20px;
  line-height: 1;
}

.v-memory-title-block {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 6px 28px 16px;
}

.v-memory-title {
  font-family: var(--font-display);
  /* JAR-265: was a hardcoded 30px — unify on the --text-2xl token like the
   * rest of the settings-family headers. */
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

.v-memory-subtitle {
  font-size: 15px;
  line-height: 22px;
  color: var(--color-muted);
}

.v-memory-error {
  margin: 0 28px var(--space-3);
  padding: var(--space-3);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

.v-memory-tabs {
  display: flex;
  padding: var(--space-1);
  gap: var(--space-1);
  margin: 0 28px;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
}

.v-memory-tab-option {
  flex: 1 1 0;
  border: none;
  border-radius: var(--radius-full);
  padding: 9px 0;
  background: none;
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  text-align: center;
}

.v-memory-tab-option[data-active="true"] {
  background: var(--color-surface);
  color: var(--color-primary);
  font-weight: var(--font-weight-semibold);
}

.v-memory-category-filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-4) 28px 0;
}

.v-memory-category-option {
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-3);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: 13px;
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.v-memory-category-option[data-active="true"] {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-surface);
}

.v-memory-loading,
.v-memory-empty {
  margin: var(--space-5) 28px;
  color: var(--color-muted);
  font-size: var(--text-sm);
  text-align: center;
}

.v-memory-list {
  display: flex;
  flex-direction: column;
  margin: var(--space-4) 28px 0;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: 16px;
  min-height: 0;
  overflow-y: auto;
}

.v-memory-item-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 14px var(--space-4);
  border-bottom: 1px solid var(--color-line);
}

.v-memory-item-row:last-child {
  border-bottom: none;
}

.v-memory-item-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.v-memory-item-text,
.v-memory-item-title {
  font-size: 15px;
  line-height: 20px;
  color: var(--color-ink);
}

.v-memory-item-title {
  font-weight: var(--font-weight-semibold);
}

.v-memory-item-snippet {
  font-size: var(--text-sm);
  line-height: 18px;
  color: var(--color-muted);
  padding-left: var(--space-2);
  border-left: 2px solid var(--color-line);
}

.v-memory-item-caption {
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--color-muted);
}

.v-memory-item-toggle {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-memory-item-toggle[data-sharing="shared"] {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-surface);
}

.v-memory-item-toggle[data-state="pending"] {
  opacity: 0.6;
}

.v-memory-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: var(--space-2) 28px var(--space-6);
  flex-wrap: wrap;
}

.v-memory-pager-prev,
.v-memory-pager-next,
.v-memory-pager-page {
  border: none;
  background: none;
  border-radius: var(--radius-full);
  height: 30px;
  min-width: 30px;
  padding: 0 var(--space-2);
  color: var(--color-muted);
  font-size: 13px;
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.v-memory-pager-page[data-active="true"] {
  background: var(--color-primary);
  color: var(--color-surface);
  font-weight: var(--font-weight-semibold);
}

.v-memory-pager-prev[disabled],
.v-memory-pager-next[disabled] {
  color: var(--color-line);
  cursor: default;
}

/* ---- Family (household) surface (JAR-167 behaviors 7-10) ------------------
 * Reached from the settings sheet's "Family" row — the same overlay/sheet
 * geometry as `.settings-overlay` above, a sibling screen rather than a
 * restyled one. Frozen design tokens
 * (workflows/2026-08-28-jar-167-household-add-members/design/tokens.css)
 * already match this app's own custom properties value-for-value. */

/* JAR-201: the transform + transition live on this persistent, never-
 * recreated container (appended once in mount-app.ts) rather than on the
 * `.v-household-*` sheet inside it, which `replaceChildren` rebuilds from
 * scratch on every render — a transition on a freshly-inserted node with its
 * target state already applied never fires (contract Context). Toggling
 * `.is-open` on this same long-lived node animates cleanly instead. */
.family-overlay {
  position: absolute;
  inset: 0;
  z-index: 12;
  transform: translateX(100%);
  transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
}

.family-overlay.is-open {
  transform: translateX(0);
  pointer-events: auto;
}

.v-household-create,
.v-household-roster,
.v-household-add,
.v-household-member-detail {
  position: absolute;
  top: 54px;
  left: 0;
  width: 100%;
  height: 790px;
  max-height: calc(100% - 54px);
  background: var(--color-bg);
  border-top-left-radius: 22px;
  border-top-right-radius: 22px;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

.v-household-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px 28px 18px;
}

.v-household-title {
  font-family: var(--font-display);
  /* JAR-265: unify the settings-family header title size on --text-2xl. */
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

.v-household-close-button {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-household-close-button::before {
  content: "×";
  font-size: 20px;
  line-height: 1;
}

.v-household-error {
  margin: 0 28px var(--space-3);
  padding: var(--space-3);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

.v-household-name-label,
.v-household-add-name-label,
.v-household-member-detail-name-label {
  margin: 0 28px 8px;
  color: var(--color-muted);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
}

.v-household-name-input,
.v-household-add-name-input,
.v-household-member-detail-name-input {
  display: block;
  width: calc(100% - 56px);
  margin: 0 28px 22px;
  height: 56px;
  padding: 0 18px;
  border: 1.5px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: 17px;
  font-weight: var(--font-weight-medium);
}

.v-household-create-submit,
.v-household-add-submit,
.v-household-member-detail-save {
  margin: auto 28px 28px;
  height: 56px;
  border: none;
  border-radius: var(--radius-lg);
  background: var(--color-primary);
  color: #fff;
  font-size: 17px;
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

/* Otto Phase 5 (JAR-234 behavior 4): the muted "‹ Settings" back row,
 * alongside the header's retained close control — mirrors
 * `.v-sharing-rules-back` / `.v-memory-back`, muted per this surface's own
 * frozen mock (GHK-0). */
.v-household-back {
  flex: 1 1 auto;
  text-align: left;
  border: none;
  background: none;
  padding: 0;
  color: var(--color-muted);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

/* JAR-234 behavior 4: the roster hero — house icon + household name +
 * static member-count subtitle — replaces the old title-row/count-line
 * pair (`.v-household-roster-name`/`.v-household-roster-count`, retired). */
/* JAR-265: the roster's title/subtitle block — the settings-family header
 * pattern (mirrors `.v-sharing-rules-title-block` / `.v-memory-title-block`),
 * replacing JAR-234's house-icon hero. */
.v-household-title-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0 28px 18px;
}

.v-household-roster-title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

.v-household-roster-subtitle {
  font-size: 15px;
  line-height: 22px;
  color: var(--color-muted);
}

.v-household-members-section-label {
  margin: var(--space-4) 28px var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-muted);
}

/* JAR-234 behavior 4: the trailing chevron every member row shows now,
 * tappable or not — mirrors `.v-settings-row-chevron` (behavior 1). */
.v-household-member-chevron {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-line);
}

.v-household-member-chevron .v-icon {
  width: 18px;
  height: 18px;
}

.v-household-member-chevron .v-icon svg {
  width: 18px;
  height: 18px;
}

.v-household-member-list {
  /* JAR-265: gray outline + 28px inset to match the other settings-family
   * list cards (`.v-sharing-policy-list`, `.v-settings-group-card`). */
  margin: 0 28px var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: clip;
}

.v-household-member-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 15px 16px;
  border-bottom: 1px solid var(--color-line);
  cursor: default;
}

.v-household-member-row[data-action] {
  cursor: pointer;
}

.v-household-member-row:last-child {
  border-bottom: none;
}

.v-household-member-avatar {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: #fff;
  font-size: 17px;
  font-weight: var(--font-weight-semibold);
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-household-member-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.v-household-member-name {
  font-size: 16px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-household-member-subtitle {
  font-size: 13.5px;
  color: var(--color-muted);
}

.v-household-member-pill {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-medium);
}

/* Otto Phase 5 (JAR-234 behavior 4): outlined (was filled) — border + primary
 * text/icon on a transparent background, with a leading plus icon. */
.v-household-roster-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin: auto 20px 28px;
  height: 56px;
  border: 1.5px solid var(--color-primary);
  border-radius: var(--radius-lg);
  background: none;
  color: var(--color-primary);
  font-size: 17px;
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

.v-household-roster-add .v-icon svg {
  width: 18px;
  height: 18px;
}

/* ---- Project deck --------------------------------------------------------- */

.v-deck {
  display: flex;
  flex-direction: column;
  /* No gap: the live card, the projects list card, and the hint each own their
   * own top spacing (JAR-265 replaced the old overlapping peek stack — whose
   * negative margins a row gap would have cancelled — with a separate card). */
  gap: 0;
}

/* Taskless tenant: a single soft, dashed empty card. */
.v-deck-empty {
  border: 1px dashed var(--color-line);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  padding: var(--space-5) var(--space-4);
  text-align: center;
  font-size: var(--text-sm);
  line-height: 1.45;
}

/* The full live card at the top of the stack: a flow block that grows to fit
 * its rows (the transcript scrolls vertically as it does today — no clipping
 * viewport, no transform, no measured height). */
.v-deck-card {
  position: relative;
  /* Sits in front of the whole peek stack, hiding each peek's top edge. */
  z-index: 10;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  /* Header, rows, and composer are full-bleed (mockup D) — clip them to the
   * card's rounded corners. */
  overflow: hidden;
}

/* Card header: the project name only (no open-count, progress, or status dot). */
.v-deck-card-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-5) var(--space-4);
}

.v-deck-card-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.15;
}

/* The promote hint closes the stack below all peeks: muted grey, sentence
 * case, with a leading arrow (mockup D). */
.v-deck-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  color: var(--color-muted);
  font-size: 13px;
  font-weight: var(--font-weight-medium);
  padding-top: 14px;
}

/* JAR-233 behavior 7: the shared `arrow-down` glyph rotated 180° — points
 * up, toward the cards this hint introduces, rather than down at itself.
 * A CSS rotation (not a second `arrow-up` icon) so it can never drift from
 * the source glyph. */
.v-deck-hint .v-icon svg {
  width: 14px;
  height: 14px;
  transform: rotate(180deg);
}

/* JAR-265: the other projects live in one separate list card below the live
 * card (mock F8K-0), replacing the receding "fanned deck" peek stack. The card
 * sits 24px narrower than the live card (the old first-peek step), centered,
 * with a small gap above it. */
.v-deck-peek-list {
  width: calc(100% - 24px);
  margin: 12px auto 0;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: clip;
}

/* Each project is a row in the list card — the whole row is the promote tap
 * target: name left, open-count right. Rows after the first carry a hairline. */
.v-deck-peek {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  background: var(--color-surface);
  color: var(--color-ink);
  border: 0;
  cursor: pointer;
}

.v-deck-peek + .v-deck-peek {
  border-top: 1px solid var(--color-line);
}

/* Project name left, open-count right-aligned beside it (mock F8K-0). */
.v-deck-peek-name {
  flex: 1;
  min-width: 0;
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  line-height: 20px;
}

.v-deck-peek-count {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: var(--font-weight-semibold);
  line-height: 16px;
  color: var(--color-faint);
}

.v-deck-empty-project {
  color: var(--color-muted);
  font-size: var(--text-sm);
  line-height: 1.4;
  padding: 0 var(--space-5) var(--space-4);
}

/* Full-bleed rows, divided by hairlines: a line under the header (this
 * border-top) and one between each row (the row-wrap adjacency below). */
.v-deck-rows {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--color-line);
}

/* JAR-265: a project with no rows (e.g. an empty "Finances" project) renders
 * an empty rows container — drop its header hairline so it doesn't stack with
 * the add-task footer's own border-top into one doubly-thick line. */
.v-deck-rows:empty {
  border-top: 0;
}

.v-deck-row-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.v-deck-row-wrap + .v-deck-row-wrap {
  border-top: 1px solid var(--color-line);
}

.v-deck-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: 14px var(--space-5);
}

/* JAR-233 behavior 8: reconciled to the frozen board's rounded square
 * (radius 7px — no `--radius-*` token matches exactly; `--radius-sm` is
 * 8px), not the pre-JAR-233 full circle. */
.v-deck-checkbox {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1.5px solid var(--color-line);
  border-radius: 7px;
  background: var(--color-surface);
  color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-deck-checkbox[data-checked="true"] {
  border-color: var(--color-success);
  background: var(--color-success);
  color: var(--color-surface);
  cursor: default;
}

.v-deck-checkbox .v-icon svg {
  width: 13px;
  height: 13px;
}

/* The title + status column lane (JAR-93): sits between the checkbox and the
 * trailing caret lane, stacking the title and its status node (pill or text
 * label) on their own lines instead of side by side. */
.v-deck-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.v-deck-row-title {
  font-size: 15px;
  line-height: 21px;
  font-weight: var(--font-weight-medium);
  color: var(--color-ink);
}

/* JAR-233 behavior 8: the frozen board's Done title color (#9AA6B2 =
 * --color-faint), not the pre-JAR-233 --color-muted tone. */
.v-deck-row[data-struck="true"] .v-deck-row-title {
  color: var(--color-faint);
  text-decoration: line-through;
}

/* The undesigned-state plain text label (JAR-85 behavior 8). JAR-99 behavior
 * 1 drops the last state that used it (`handed-off`) — no view node emits
 * this class anymore, but the rule stays declared/unused rather than risk
 * a stray future undesigned state losing a home. */
.v-deck-row-status {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-muted);
  white-space: nowrap;
}

/* The three designed status pills (JAR-85 behavior 8, JAR-99 behavior 6): a
 * soft-tint rounded pill with a small colored dot + label. Blue (primary)
 * for in-progress, amber for needs-input, red for error — open/done carry no
 * pill at all. */
/* JAR-233 behavior 8: padding reconciled to the frozen board's exact
 * `3px 9px 3px 8px` (was a symmetric `4px 10px`). */
.v-deck-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: var(--radius-full);
  padding: 3px 9px 3px 8px;
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}

/* JAR-233 behavior 8: 7x7 (was 6x6), per the frozen board. */
.v-deck-pill-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
}

/* JAR-99 behavior 6 (design spec): In progress uses the blue token pair, not
 * the accent pair JAR-85 originally shipped. JAR-241 behavior 4: mapped onto
 * the meaning-only info scale — the blue values are unchanged (info base/
 * soft equal primary/primary-soft), but the label moves off the bright base
 * onto info deep text, the same illegibility class as needs-input. */
.v-deck-pill[data-row-status="in-progress"] {
  background: var(--color-info-soft);
  color: var(--color-info-text);
}

.v-deck-pill[data-row-status="in-progress"] .v-deck-pill-dot {
  background: var(--color-info);
}

/* JAR-99 behavior 6: renamed from "Needs review". JAR-241 behavior 2: the
 * label reads legibly — caution deep text on caution soft, base dot (was
 * the bright caution base as type on its own tint). */
.v-deck-pill[data-row-status="needs-input"] {
  background: var(--color-caution-soft);
  color: var(--color-caution-text);
}

.v-deck-pill[data-row-status="needs-input"] .v-deck-pill-dot {
  background: var(--color-caution);
}

/* JAR-97 behavior 8: the red Error pill, the same treatment as in-progress/
 * needs-input above. JAR-241 behavior 3: the label reads legibly — danger
 * deep text on danger soft, base dot. */
.v-deck-pill[data-row-status="error"] {
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
}

.v-deck-pill[data-row-status="error"] .v-deck-pill-dot {
  background: var(--color-danger);
}

/* JAR-97 behavior 8: an error row's observable reason, surfaced beneath its
 * pill — same muted-text treatment as the row body, tinted danger red. */
.v-deck-row-error-reason {
  font-size: 13px;
  line-height: 18px;
  color: var(--color-danger-text);
}

/* Fixed-width trailing lane (mockup D): right-aligns the caret so open-row
 * carets sit in one vertical column regardless of title length. */
.v-deck-row-trail {
  flex: 0 0 auto;
  width: 74px;
  display: flex;
  justify-content: flex-end;
}

/* The collapsed row's only trailing control (JAR-85 behavior 6; JAR-242
 * behavior 1: opens full screen directly). The caret always renders
 * collapsed — there is no expanded-state rotation to key on. */
.v-deck-caret {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-deck-caret .v-icon svg {
  width: 18px;
  height: 18px;
}

@keyframes deck-ellipsis {
  0% {
    content: ".";
  }
  33% {
    content: "..";
  }
  66% {
    content: "...";
  }
  100% {
    content: ".";
  }
}

/* The full-screen form (JAR-99 behavior 5): a full-viewport view-local
 * overlay layer above the whole deck stack. */
/* The full-screen form (JAR-99 behavior 5): a view-local overlay layer above
 * the whole deck stack, confined to the phone shell (JAR-102) — `absolute`
 * against `.v-jarvis-screen`'s `position: relative` (no positioned ancestor
 * sits between them), not `fixed` against the browser viewport. */
.v-deck-full-screen {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
}

.v-deck-full-screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-5);
  border-bottom: 1px solid var(--color-line);
}

.v-deck-full-screen-title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.15;
}

.v-deck-full-screen-close {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-muted);
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
}

.v-deck-full-screen-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
}

/* One turn: a fixed left-gutter author label ("YOU" / "JARVIS") beside the
 * turn text (design spec). An Error row's turn bubbles tint danger-soft. */
.v-deck-turn {
  display: flex;
  gap: var(--space-3);
}

.v-deck-turn-author {
  flex: 0 0 56px;
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-muted);
}

.v-deck-turn-text {
  flex: 1;
  min-width: 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-ink);
  /* JAR-107 behavior 1: a Jarvis reply now renders as a block ViewNode tree
   * (parseMarkdown), not one opaque text node — block spacing lives here,
   * not in the view (Testing Decisions). */
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* JAR-222 behavior 5: the load-bearing muted-italic vs ink-regular
 * distinction between the activity line and a settled Jarvis reply (frozen
 * design bundle: Inter 14px italic `--color-muted` vs Inter 15px regular
 * `--color-ink`). The live bubble's `data-streaming` marker (JAR-138) lives
 * on the enclosing `.v-deck-turn` (`liveTurnBubble`'s own props), not on
 * `.v-deck-turn-text` itself — hence the descendant selector. The base rule
 * above already supplies 14px (`--text-sm`) and ink color, so only the
 * italic + muted overrides are needed here; the settled-size bump below is
 * scoped to `author="jarvis"` so a "you" bubble (never streaming) is
 * unaffected. */
.v-deck-turn[data-streaming="true"] .v-deck-turn-text {
  font-style: italic;
  color: var(--color-muted);
}

.v-deck-turn[data-author="jarvis"]:not([data-streaming="true"]) .v-deck-turn-text {
  font-size: 15px;
}

.v-deck-full-screen[data-row-status="error"] .v-deck-turn[data-author="jarvis"] .v-deck-turn-text {
  background: var(--color-danger-soft);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

/* JAR-153 behavior 6: the full-screen live turn's gap-based ellipsis — the
 * same CSS-only pseudo-element idiom as the expanded body's above, now the
 * *only* activity cue on this surface — the caret and the pulsing-dots row
 * (JAR-138 behavior 1) are gone. The gap flag `mount-app.ts`'s stream
 * consumer owns.
 *
 * JAR-153 behavior 8 (no reflow): mirrors the expanded body's split above —
 * the base `[data-gap]` rule (attribute presence, not value) reserves the
 * `1.5em` box unconditionally, so it never appears/disappears as the flag
 * toggles; only the glyph content/animation are keyed on `="true"`. */
.v-deck-turn-text[data-gap]::after {
  content: "";
  display: inline-block;
  flex: 0 0 auto;
  width: 1.5em;
}

.v-deck-turn-text[data-gap="true"]::after {
  content: ".";
  animation: deck-ellipsis 1200ms steps(3, jump-none) infinite;
}

/* JAR-223 behavior 4: mirrors the expanded body's inline treatment above —
 * same reasoning, same scoping to `[data-gap]` only, same untouched
 * `::after` ellipsis rules. `.v-deck-turn-text` also participates as a flex
 * item of its own parent `.v-deck-turn` (`flex: 1` above); that's this
 * element's *own* box-type-as-a-child, unrelated to its `display` here,
 * which only governs how it lays out its own children. */
.v-deck-turn-text[data-gap] {
  display: block;
}

.v-deck-turn-text[data-gap] > .v-md-p {
  display: inline;
}

/* The "Tell Otto what to do" field: text field, reserved (disabled) mic,
 * trailing round Send button — the sunken input-pill shape (mockup D). */
.v-deck-instructions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 5px 5px 5px var(--space-4);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
}

/* The composer pinned at the bottom of full screen (design spec) — the
 * scrollable body above is the flex-1 sibling that absorbs the remaining
 * height, so this needs only its own inset margin. */
.v-deck-full-screen > .v-deck-instructions {
  margin: 0 var(--space-5) var(--space-5);
  flex: 0 0 auto;
}

/* Add-task composer (mockup D): a full-bleed sunken footer bar — text field,
 * reserved mic, and a trailing circular send button, divided from the rows by a
 * hairline. No pill, no leading "+", no "Add" text. */
.v-deck-add-footer {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-3) 14px;
  border-top: 1px solid var(--color-line);
  /* JAR-265: white (was --color-surface-sunken gray) so the add-task field
   * matches the white task rows above it; the border-top still separates it. */
  background: var(--color-surface);
}

.v-deck-instructions-input,
.v-deck-add-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--color-ink);
  font-size: var(--text-sm);
}

.v-deck-instructions-input {
  padding: 8px 0;
}

.v-deck-add-input {
  line-height: 18px;
  padding: 0;
}

.v-deck-instructions-input::placeholder,
.v-deck-add-input::placeholder {
  color: var(--color-faint);
}

.v-deck-mic {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: default;
  opacity: 0.55;
}

.v-deck-mic .v-icon svg {
  width: 18px;
  height: 18px;
}

/* JAR-258 behaviors 1-2: the add-task footer's single morphing control — a
 * bare wire glyph in a 28x28 slot, no filled circle (the deck's secondary/
 * wire weight; the chat dock's/full-screen composer's primary/filled
 * controls above are untouched). Mic at an empty draft, arrow once typing,
 * both the same muted wire gray (frozen deck.structure.jsx: `stroke
 * #7A8794`; no exact :root token exists for it — contract.md Assumptions). */
.v-deck-add-control {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  background: transparent;
  color: #7a8794;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-deck-add-control[disabled] {
  cursor: default;
}

.v-deck-add-control .v-icon svg {
  width: 20px;
  height: 20px;
}

.v-deck-add-control .v-icon:nth-child(2) {
  display: none;
}

.v-deck-add-control[data-state="send"] .v-icon:nth-child(1) {
  display: none;
}

.v-deck-add-control[data-state="send"] .v-icon:nth-child(2) {
  display: inline-flex;
}

/* Send: a 34px filled circle, primary bg, white arrow icon (mockup D). */
.v-deck-send {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-deck-send .v-icon svg {
  width: 16px;
  height: 16px;
}

/* Stop (JAR-140 behavior 9) — the row-scoped twin of the main composer's
 * Stop: same 34px circle as this row's Send and sharing its navy (Paper
 * "One control, three states" — Stop takes the send slot's look, set apart
 * only by the stop glyph, not a danger color). */
.v-deck-stop {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-deck-stop .v-icon svg {
  width: 16px;
  height: 16px;
}

/* ---- Deck receipt (JAR-27): a collapsed older deck turn -------------------- */
/* The bar is a native <button>, so it must shed the user-agent widget styling
 * or that shows through: the default `2px outset` button border (near-black,
 * barely-rounded) framed the card as a dark line, since the `::before` face is
 * inset flush *inside* that border. `appearance: none` + `border: 0` remove it;
 * the face owns the real fill and border. (No `background` reset here — the
 * face paints over the UA background, and a fill on the bar itself would sort
 * ahead of the negative-z-index edge, the JAR-42 defect.)
 * `isolation: isolate` gives the bar its own stacking context so the negative
 * z-index on the stacked edge below can never bleed behind an unrelated
 * ancestor — contained purely within this one component. */
.v-deck-receipt {
  position: relative;
  isolation: isolate;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: 52px;
  margin-bottom: 6px;
  padding: 0 var(--space-4);
  text-align: left;
  cursor: pointer;
}

/* Also drop the UA focus ring on pointer focus (same near-black outline), but
 * keep a real ring on :focus-visible so keyboard users still see focus —
 * offset outward from the face so it clears the peek. */
.v-deck-receipt:focus {
  outline: none;
}
.v-deck-receipt:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-md);
}

/* The bar's own opaque card face, generated rather than painted directly on
 * `.v-deck-receipt` — that's what lets it sort by z-index like any other
 * layer instead of always painting first (the stacking-context root's own
 * background paints ahead of any negative z-index descendant, which is why
 * the edge below used to show through). Less-negative than the edge, so the
 * face always covers it; both stay negative so the in-flow content (icon,
 * text, jump affordance) always paints above both. Covers the bar exactly
 * (inset: 0), so the receipt's outer box — and surrounding spacing — is
 * unchanged. */
.v-deck-receipt::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

/* A second card edge behind the bar's face, stepped in by the same 24px the
 * primary deck's first peek layer uses (`.v-deck-peek[data-depth="0"]`;
 * 12px per side, centered) and peeking out only below — never to either
 * side. More negative z-index than the face above, so the face's fill
 * always covers it; a real stacked card edge (not a drop shadow) hinting at
 * the collapsed card(s) underneath. Purely decorative: no text, no
 * testId-driven logic. */
.v-deck-receipt-edge {
  position: absolute;
  z-index: -2;
  inset: 6px 12px -6px 12px;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

.v-deck-receipt-lead {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.v-deck-receipt-lead > .v-icon svg {
  width: 18px;
  height: 18px;
  color: var(--color-muted);
}

.v-deck-receipt-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.v-deck-receipt-title {
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-deck-receipt-time {
  font-size: 13px;
  color: var(--color-muted);
}

.v-deck-receipt-jump {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 13px;
  font-weight: var(--font-weight-medium);
  color: var(--color-primary);
}

.v-deck-receipt-jump .v-icon svg {
  width: 14px;
  height: 14px;
}

/* ---- Surface placeholder (JAR-210): an inert reopened-turn receipt --------- */
/* Reuses `.v-deck-receipt`'s shape/tokens (isolation for the stacked edge's
 * z-index, the generated face, the stepped-edge peek) but a distinct
 * selector, a plain `<div>` (render-dom.ts has no TAG_BY_TYPE entry for
 * `surface-placeholder`, so it never becomes a button), and no jump lane —
 * display-only, no pointer cursor. */
.v-surface-placeholder {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 52px;
  margin-bottom: 6px;
  padding: 0 var(--space-4);
  text-align: left;
}

.v-surface-placeholder::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

.v-surface-placeholder-edge {
  position: absolute;
  z-index: -2;
  inset: 6px 12px -6px 12px;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

.v-surface-placeholder-lead {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.v-surface-placeholder-lead > .v-icon svg {
  width: 18px;
  height: 18px;
  color: var(--color-muted);
}

.v-surface-placeholder-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.v-surface-placeholder-title {
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-surface-placeholder-time {
  font-size: 13px;
  color: var(--color-muted);
}

/* ---- Recipe card / picker (JAR-7) ------------------------------------------ */

.v-recipe-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.v-recipe-hero {
  width: 100%;
  height: 200px;
  object-fit: cover;
  display: block;
  background: var(--color-surface-sunken);
}

.v-recipe-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
}

.v-recipe-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.2;
}

/* JAR-235 (Otto 12): borderless pill, not uppercase-forced (the design's own
 * "NYT COOKING" is the source text's natural case, not a CSS transform). */
.v-recipe-source-badge {
  align-self: flex-start;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-muted);
  padding: 4px 10px;
  font-size: 11px;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
}

.v-recipe-meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

/* JAR-235 (Otto 12): inline icon-plus-text, muted — no bordered pill. Real
 * and placeholder chips share the same treatment; only the text differs. */
.v-recipe-meta-time,
.v-recipe-meta-servings,
.v-recipe-meta-time-placeholder,
.v-recipe-meta-servings-placeholder {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--color-muted);
  font-size: 13px;
  font-weight: var(--font-weight-medium);
}

.v-recipe-meta-time .v-icon svg,
.v-recipe-meta-servings .v-icon svg,
.v-recipe-meta-time-placeholder .v-icon svg,
.v-recipe-meta-servings-placeholder .v-icon svg {
  width: 16px;
  height: 16px;
}

.v-recipe-ingredients,
.v-recipe-steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
}

/* JAR-235 (Otto 12): a 5px primary bullet leads each ingredient. */
.v-recipe-ingredient {
  position: relative;
  padding-left: 15px;
  color: var(--color-ink);
  font-size: var(--text-sm);
  line-height: 1.4;
}

.v-recipe-ingredient::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
}

/* JAR-235 (Otto 12): a hairline divides the ingredients from the numbered
 * steps group — no extra DOM node, the divider rides on the steps list's
 * own top border. */
.v-recipe-steps {
  counter-reset: recipe-step;
  border-top: 1px solid var(--color-line);
  padding-top: var(--space-3);
}

.v-recipe-step {
  counter-increment: recipe-step;
  position: relative;
  min-height: 22px;
  padding-left: 34px;
  color: var(--color-ink);
  font-size: var(--text-sm);
  line-height: 22px;
}

.v-recipe-step::before {
  content: counter(recipe-step);
  position: absolute;
  left: 0;
  top: 0;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  color: var(--color-primary-strong);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-bold);
}

/* JAR-235 (Otto 17): one card shell holding text-only rows, divided by
 * hairlines — no per-row bordered card, no thumbnail slot. */
.v-recipe-picker {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.v-recipe-picker-row {
  display: flex;
  align-items: center;
  gap: 13px;
  border: 0;
  border-radius: 0;
  border-top: 1px solid var(--color-line);
  background: transparent;
  padding: 11px 13px;
  cursor: pointer;
  text-align: left;
}

.v-recipe-picker-row:first-child {
  border-top: 0;
}

.v-recipe-picker-row-name {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  line-height: 19px;
  color: var(--color-ink);
}

/* The trailing disclosure chevron — reuses the shared `chevron` glyph,
 * tinted faint (Otto 17). */
.v-recipe-picker-row .v-icon {
  flex: 0 0 auto;
  display: flex;
  color: var(--color-faint);
}

.v-recipe-picker-row .v-icon svg {
  width: 18px;
  height: 18px;
}

/* ---- Shopping-list card (JAR-8) -------------------------------------------- */

/* JAR-235 (Otto 11): reads as one family with the deck/to-do card — the
 * card itself carries no padding; the header and each row own their own
 * full-bleed padding, divided by hairlines. */
.v-shopping-list-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* The kickoff card shell (JAR-299) — the one element novel to this ticket,
 * reusing the shared card shell (surface fill, --color-line border, --radius-lg)
 * per the frozen HV7-0/HXQ-0 "Card shell" so it never drifts from its siblings. */
.v-task-kickoff-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.v-shopping-list-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
  line-height: 24px;
  padding: 16px 18px 12px;
}

.v-shopping-list-rows {
  display: flex;
  flex-direction: column;
}

.v-shopping-item-row {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 13px 18px;
  border-top: 1px solid var(--color-line);
}

.v-shopping-item-row:first-child {
  border-top: 0;
}

/* JAR-233 family match: the deck's tokenless 7px rounded square (was
 * --radius-full — see contract.md Assumptions). */
.v-shopping-checkbox {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1.5px solid var(--color-line);
  border-radius: 7px;
  background: var(--color-surface);
  color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-shopping-checkbox[data-checked="true"] {
  border-color: var(--color-success);
  background: var(--color-success);
  color: var(--color-surface);
}

.v-shopping-checkbox .v-icon svg {
  width: 14px;
  height: 14px;
}

.v-shopping-item-name {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  line-height: 20px;
  font-weight: var(--font-weight-medium);
  color: var(--color-ink);
}

.v-shopping-item-row[data-struck="true"] .v-shopping-item-name {
  color: var(--color-faint);
  text-decoration: line-through;
}

.v-shopping-empty {
  color: var(--color-muted);
  padding: 0 18px 20px;
  text-align: center;
  font-size: var(--text-sm);
  line-height: 1.45;
}

/* ---- Welcome screen (JAR-118, Paper 6S2-0; restyled JAR-232 behavior 2,
 * Paper D0E-0) --------------------------------------------------------------
 * Otto rebrand: inverts from the pre-rebrand dark `--color-primary-strong`
 * ground to the light Otto `--color-bg` ground, ink/muted/faint text, and a
 * centered hero (mascot → headline → subhead) instead of the top-offset
 * fanned-mark eyebrow. */

.v-welcome-screen {
  display: flex;
  flex-direction: column;
  /* Match the app's phone-shell footprint (`.v-jarvis-screen`) so the boot
   *  screens sit in the same cutout — the boot gate mounts these outside the
   *  app shell (asserted in auth-boot-gate.test.ts), so they carry the frame
   *  themselves rather than inheriting it. */
  width: min(390px, 100%);
  min-height: min(844px, calc(100vh - 48px));
  overflow: hidden;
  background: var(--color-bg);
}

.v-welcome-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-grow: 1;
  justify-content: center;
  gap: 28px;
  padding: 12px 32px 8px;
}

.v-welcome-headline {
  margin: 0;
  font-family: var(--font-display);
  font-size: 34px;
  line-height: 38px;
  letter-spacing: var(--tracking-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
  text-align: center;
}

.v-welcome-subhead {
  max-width: 300px;
  font-size: 15px;
  line-height: 23px;
  color: var(--color-muted);
  text-align: center;
}

.v-welcome-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* Anchor to the bottom of the screen, matching the artboard's bottom-aligned
   *  Auth zone (pb 40). */
  margin-top: auto;
  padding: 0 var(--space-6) 40px;
}

.v-google-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  /* JAR-258 behavior 3: the frozen welcome artboard's 58px pill with a 1px
   * hairline in the line token (was borderless 56px). */
  height: 58px;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  cursor: pointer;
}

.v-google-button-label {
  font-size: var(--text-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-welcome-error {
  font-size: var(--text-sm);
  line-height: 1.4;
  text-align: center;
  /* JAR-232: the Welcome ground inverted to light (behavior 2) — the
   *  danger-soft light-on-dark tint (JAR-241 behavior 7) is no longer
   *  legible here, so this now resolves danger type per its ground the same
   *  way `.v-settings-sign-out` does on its own plain light ground: the
   *  danger-text deep tier. */
  color: var(--color-danger-text);
}

.v-welcome-terms {
  padding-top: var(--space-2);
  font-size: var(--text-xs);
  line-height: 1.5;
  text-align: center;
  color: var(--color-faint);
}

/* ---- Authorizing screen (JAR-118, Paper 6T1-0; restyled JAR-232
 * behaviors 4-5, Paper D1W-0) -------------------------------------------
 * Otto rebrand: the badge/disc/ring is gone — the yawning Otto hero
 * (`.v-mascot-hero`, shared with Welcome) takes its place. */

.v-authorizing-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Same phone-shell footprint as `.v-welcome-screen` above — mounted outside
   *  the app shell, so it carries the cutout frame itself. */
  width: min(390px, 100%);
  min-height: min(844px, calc(100vh - 48px));
  overflow: hidden;
  padding-bottom: 80px;
  gap: var(--space-6);
  background: var(--color-bg);
}

.v-authorizing-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}

.v-authorizing-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  line-height: 32px;
  letter-spacing: var(--tracking-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
}

.v-authorizing-subhead {
  font-size: 15px;
  line-height: 18px;
  text-align: center;
  color: var(--color-muted);
}

/* ---- Connecting overlay (JAR-141, Paper A2C-0 / 1LN-0) --------------------
 * The shared full-shell "connecting" beat for B4 (Web browsing provision)
 * and screen 5 (Google consent return). Mounted as a shell-covering overlay
 * layer the same way the hub/drawer overlays are (see `.hub-overlay` /
 * `.drawer-overlay` above), but above both (z-index 20) since it can be
 * triggered while the hub is open (tapping Connect on Web browsing). */

.connect-overlay {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: none;
}

.connect-overlay.is-open {
  display: block;
}

.v-connect-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 36px;
  padding: 0 32px;
  background: var(--color-bg);
}

.v-connect-emblem-row {
  display: flex;
  align-items: center;
  gap: 18px;
}

/* JAR-232 behavior 6: matched with `.v-connect-logo` below — same white
 * hairline circle, Otto's head on the left instead of the service mark. */
.v-connect-otto-circle {
  width: 60px;
  height: 60px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Reuses the committed `final` pose (design.md Decision 4), scaled and
 * offset so only the head shows through the circle's `overflow: hidden` —
 * the same coordinate space as the frozen artboard's own
 * `viewBox="121 10 300 300"` head crop, at a 0.2 scale (300 viewBox units
 * -> 60px circle). Tier-2 visual fidelity is deferred (contract.md Open
 * Questions) — confirm at verify. */
.v-connect-otto-circle .v-mascot-pose {
  width: 108px;
  position: relative;
  left: -24px;
  top: -2px;
}

.v-connect-dot-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Reuses the app's existing `.v-dot` / `@keyframes pulse` idiom (see
 * "Thinking" above) rather than the prototype's own `connect-dot-pulse`
 * keyframes, so there's one dot-animation idiom in the client. JAR-241
 * behavior 8: ramps across the info loading-dot ramp, same as `.v-dot`. */
.v-connect-dot {
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-info);
  animation: pulse 900ms ease-in-out infinite;
}

.v-connect-dot:nth-child(2) {
  background: var(--color-info-dot-1);
  animation-delay: 120ms;
}

.v-connect-dot:nth-child(3) {
  background: var(--color-info-dot-2);
  animation-delay: 240ms;
}

/* Behavior 5: with reduced motion requested, hold the dots at their static
 * staggered opacities instead of pulsing. The client has no other
 * `prefers-reduced-motion` rule today — this is new. */
@media (prefers-reduced-motion: reduce) {
  .v-connect-dot {
    animation: none;
    opacity: 1;
  }

  .v-connect-dot:nth-child(2) {
    opacity: 0.5;
  }

  .v-connect-dot:nth-child(3) {
    opacity: 0.25;
  }

  /* JAR-201: the Settings sheet + its sub-view overlays suppress their
   * slide — the transform end-state (`.is-open`, unaffected here) still
   * applies, so views land correctly placed, just without the tween. */
  .settings-overlay,
  .sharing-rules-overlay,
  .family-overlay {
    transition: none;
  }

  /* JAR-219: the shared Settings backdrop suppresses its opacity fade the
   * same way — the `.is-open` end-state still applies, so the dim appears
   * instantly rather than fading. */
  .settings-backdrop {
    transition: none;
  }
}

.v-connect-logo {
  width: 60px;
  height: 60px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  display: flex;
  align-items: center;
  justify-content: center;
}

.v-connect-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.v-connect-headline {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  line-height: 30px;
  letter-spacing: var(--tracking-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
  text-align: center;
}

.v-connect-subhead {
  max-width: 280px;
  font-size: 15px;
  line-height: 21px;
  text-align: center;
  color: var(--color-muted);
}

/* ---- Mascot (JAR-230, JAR-232) ---------------------------------------------
 * The flat-interface rule (behavior 4) makes the mascot speech bubble the
 * sole element in the app that keeps a soft shadow. One class serves both
 * content shapes (JAR-232 behavior 1): pose-wrapping (`MascotSpeechBubble`,
 * JAR-230 behavior 7 — no background/padding needed, the pose SVG is its
 * own art) and the text-carrying comic-bubble chrome (`MascotSpeechText`) —
 * white surface, hairline border, one squared speech-tail corner, Otto
 * voice type. The italic `data-variant` modifier (Authorizing's "Yawnn…")
 * needs no renderer change — `variant` is already whitelisted
 * (`render-dom.ts`). */

.v-mascot-speech-bubble {
  box-shadow: 0 8px 24px rgba(29, 37, 48, 0.12);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: 16px 16px 16px 5px;
  padding: 9px 14px;
  width: max-content;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  line-height: 18px;
  color: var(--color-ink);
}

.v-mascot-speech-bubble[data-variant="italic"] {
  font-style: italic;
  color: var(--color-muted);
}

/* MascotHero (JAR-232 design.md Decision 3): the shared pose + text-bubble
 * placement idiom Welcome and Authorizing both use — one relative wrapper,
 * the bubble absolutely offset over the pose's top-right corner. */
.v-mascot-hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  flex-shrink: 0;
  width: 236px;
  height: 210px;
}

.v-mascot-hero .v-mascot-pose {
  width: 212px;
}

.v-mascot-hero .v-mascot-speech-bubble {
  position: absolute;
  top: -6px;
  right: -6px;
}

/* The pose's `viewBox` is the scaling handle (fixed width/height stripped
 * on inline) — a consuming screen sizes the pose by constraining this
 * wrapper's own width. */
.v-mascot-pose {
  display: inline-flex;
}

.v-mascot-pose svg {
  width: 100%;
  height: auto;
  display: block;
}

/* ---- Durable To-do view (JAR-315) -----------------------------------------
 * Graduates the side-nav drawer's To-do card (JAR-314) past the shared
 * Coming-soon placeholder: a full-bleed screen swap (design HBD-0/IAF-0),
 * not a centered dialog like `.destination-overlay` above. No exit-transition
 * scheduler (mirrors `.destination-overlay`'s own precedent) — the contract
 * only specifies the tap's observable end state. Existing live tokens only
 * (contract Anti-Behaviors: the `:root` count stays 47) — the frozen
 * bundle's `--color-jarvis-*` names map to this app's own `--color-*`
 * equivalents (design Decision 12). */
.todo-view-overlay {
  position: absolute;
  inset: 0;
  z-index: 12;
  display: none;
}

.todo-view-overlay.is-open {
  display: block;
}

/* `position: relative` is the containing block the nested full-screen task
 * overlay (`.v-deck-full-screen`, `position: absolute; inset: 0`) pins
 * against — mirrors `.v-jarvis-screen`'s own role for the chat deck (design
 * Decision 14). */
.v-todo-view {
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.v-todo-view-app-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  padding: var(--space-4) var(--space-6) var(--space-3);
}

.v-todo-view-back {
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* The frozen design's app-bar glyph is a back affordance; this app has no
 * dedicated `back` icon (contract Anti-Behaviors: no new icon art) — the
 * existing right-pointing `chevron` rotated reads as "back". */
.v-todo-view-back .v-icon {
  transform: rotate(180deg);
}

/* The right avatar pill is rendered for fidelity but stays inert this
 * ticket (contract Assumptions: "Back path") — an empty, decorative slot. */
.v-todo-view-avatar {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
}

.v-todo-view-title-block {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex-shrink: 0;
  padding: var(--space-1) var(--space-5) var(--space-3);
}

.v-todo-view-title {
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-tight);
}

.v-todo-view-subtitle {
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
}

.v-todo-view-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--space-5) var(--space-6);
}

.v-todo-view-empty {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-6);
}

.v-todo-view-empty-title {
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
}

.v-todo-view-empty-subtitle {
  color: var(--color-muted);
  font-size: var(--text-sm);
  max-width: 240px;
}

/* Mirrors a collapsed peek row's own geometry (`.v-deck-peek`) — the inline
 * add-project draft reads as one more row in that same list (design's IAF-0),
 * even though it's rendered by the view host, not `renderDeck` (Decision 8). */
.v-todo-view-add-project {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 14px 16px;
  margin-top: 9px;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
}

.v-todo-view-add-project-input {
  flex: 1;
  border: none;
  background: none;
  outline: none;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-ink);
}

.v-todo-view-add-project-submit {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* 60px circular FAB, bottom-right (design spec) — no box-shadow (the
 * token-discipline ledger's raw-literal box-shadow count is fixed at
 * exactly one, the mascot speech bubble; see otto-token-discipline.test.ts). */
.v-todo-view-fab {
  position: absolute;
  bottom: 28px;
  right: 20px;
  width: 60px;
  height: 60px;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-todo-view-fab .v-icon svg {
  width: 26px;
  height: 26px;
}

/* ---- Durable Routines view (JAR-322 S7) -----------------------------------
 * Graduates the side-nav drawer's Routines card past the shared Coming-soon
 * placeholder (design `routines-main` / `routine-detail-winner` /
 * `routine-detail-paused-save-failed`, `contentHash.tokens: da722f82`) — a
 * full-bleed screen swap over two internal screens (cadence-grouped list,
 * detail), mirrors `.todo-view-overlay`'s own precedent exactly. Existing
 * live tokens only — no new custom property. */
.routines-view-overlay {
  position: absolute;
  inset: 0;
  z-index: 12;
  display: none;
}

.routines-view-overlay.is-open {
  display: block;
}

.v-routines-view,
.v-routine-detail {
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.v-routines-view-app-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  padding: var(--space-4) var(--space-6) var(--space-3);
}

.v-routines-view-back {
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* No dedicated "back" glyph exists in this app (mirrors `.v-todo-view-back`'s
 * own precedent) — the existing right-pointing chevron rotated reads as
 * "back". */
.v-routines-view-back .v-icon {
  transform: rotate(180deg);
}

.v-routines-view-avatar {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
}

.v-routines-view-title-block {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex-shrink: 0;
  padding: var(--space-1) var(--space-5) var(--space-3);
}

.v-routines-view-title {
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-tight);
}

.v-routines-view-subtitle {
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
}

.v-routines-view-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--space-5) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.v-routines-view-empty {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-6);
}

.v-routines-view-empty-title {
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
}

.v-routines-view-empty-subtitle {
  color: var(--color-muted);
  font-size: var(--text-sm);
  max-width: 240px;
}

/* Cadence group — a faint uppercase label over a rounded, bordered card of
 * routine rows (design `routines-main`). */
.v-routines-group {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.v-routines-group-label {
  padding: 6px 4px 2px;
  color: var(--color-faint);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.v-routines-group-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
}

.v-routine-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 13px 15px;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
}

.v-routine-row:not(:first-child) {
  border-top: 1px solid var(--color-line);
}

.v-routine-row-status-dot {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-sunken);
}

.v-routine-row-status-dot::after {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-faint);
}

.v-routine-row-status-dot[data-enabled="true"] {
  background: var(--color-success-soft);
}

.v-routine-row-status-dot[data-enabled="true"]::after {
  background: var(--color-success);
}

.v-routine-row-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.v-routine-row-title {
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
}

.v-routine-row-subtitle {
  color: var(--color-muted);
  font-size: 13px;
}

.v-routine-row-chevron {
  flex-shrink: 0;
  color: var(--color-faint);
  display: flex;
}

.v-routines-view-fab {
  position: absolute;
  bottom: 28px;
  right: 20px;
  width: 60px;
  height: 60px;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-routines-view-fab .v-icon svg {
  width: 26px;
  height: 26px;
}

/* Detail screen (design `routine-detail-winner` / `routine-detail-paused-
 * save-failed`) — header (title + Live/Paused badge), the two inline-
 * editable fields, and the footer (autosave/save-failed + actions row). */
.v-routine-detail-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-line);
  flex-shrink: 0;
}

.v-routine-detail-header-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.v-routine-detail-title,
.v-routine-detail-title-input {
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  border: none;
  background: none;
  padding: 0;
}

.v-routine-detail-title-input[data-invalid="true"] {
  color: var(--color-danger-text);
}

.v-routine-detail-status-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.v-routine-detail-badge {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px 3px 7px;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
}

.v-routine-detail-badge[data-enabled="true"] {
  background: var(--color-success-soft);
}

.v-routine-detail-badge-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  background: var(--color-faint);
}

.v-routine-detail-badge[data-enabled="true"] .v-routine-detail-badge-dot {
  background: var(--color-success);
}

.v-routine-detail-badge-label {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-bold);
  color: var(--color-muted);
}

.v-routine-detail-badge[data-enabled="true"] .v-routine-detail-badge-label {
  color: var(--color-success-text);
}

.v-routine-detail-last-run {
  color: var(--color-muted);
  font-size: 13px;
}

.v-routine-detail-close {
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.v-routine-detail-close .v-icon {
  transform: rotate(180deg);
}

.v-routine-detail-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) 0;
}

.v-routine-detail-objective-field,
.v-routine-detail-when-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.v-routine-detail-objective-label,
.v-routine-detail-when-label {
  padding: 0 2px;
  color: var(--color-faint);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-label);
}

.v-routine-detail-objective-input,
.v-routine-detail-when-input {
  padding: 14px 16px;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-size: 15px;
}

.v-routine-detail-when-input[data-invalid="true"] {
  border: 1.5px solid var(--color-danger);
}

.v-routine-detail-when-helper {
  padding: 0 2px;
  color: var(--color-faint);
  font-size: var(--text-xs);
}

.v-routine-detail-when-helper[data-invalid="true"] {
  color: var(--color-danger-text);
}

.v-routine-detail-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-5) var(--space-6);
  flex-shrink: 0;
}

.v-routine-detail-autosave-line {
  color: var(--color-muted);
  font-size: 13px;
}

.v-routine-detail-save-banner {
  color: var(--color-danger-text);
  font-size: 13px;
  text-align: center;
}

.v-routine-detail-actions {
  align-self: stretch;
  display: flex;
  gap: var(--space-3);
}

.v-routine-detail-delete,
.v-routine-detail-pause-resume,
.v-routine-detail-delete-cancel {
  flex: 1;
  height: 50px;
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-ink);
  cursor: pointer;
}

.v-routine-detail-delete,
.v-routine-detail-delete-confirm {
  border-color: var(--color-danger-line);
  color: var(--color-danger-text);
}

.v-routine-detail-delete-confirm {
  flex: 1;
  height: 50px;
  border-radius: var(--radius-full);
  background: var(--color-danger-soft);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

.v-routine-detail-save {
  align-self: stretch;
  flex: 1;
  height: 50px;
  border-radius: var(--radius-full);
  border: none;
  background: var(--color-primary);
  color: var(--color-surface);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
