/*
 * Rails asset manifest. Under Propshaft `*= require` is inert, so the DS and
 * component stylesheets are pulled in via CSS @import (Propshaft rewrites each
 * url() to its digested path). Wired into the layout with a single
 * stylesheet_link_tag "application".
 */
@import url("/assets/design-system-090ef4b8.css");
@import url("/assets/components-e1f2ba70.css");

/* ===== console shell (port of ui_kits/console/ConsoleShell.jsx) ===== */
.console-shell{display:flex;min-height:100vh;background:var(--bg-page);color:var(--text-primary);font:var(--type-body)}
/* skip-to-content (REVIEW_28 a11y fix): off-screen until keyboard focus */
.skip-link{position:absolute;left:8px;top:8px;z-index:100;transform:translateY(-200%);padding:8px 12px;background:var(--bg-surface);color:var(--text-primary);font:var(--type-ui);border:1px solid var(--border-default);border-radius:var(--radius-control);text-decoration:none}
.skip-link:focus{transform:none;box-shadow:var(--ring-focus)}
.side-nav{width:var(--sidebar-w);flex:0 0 auto;border-right:1px solid var(--border-subtle);background:var(--bg-subtle);display:flex;flex-direction:column;position:sticky;top:0;height:100vh}
.side-nav__brand{height:var(--topbar-h);display:flex;align-items:center;gap:8px;padding:0 16px;border-bottom:1px solid var(--border-subtle)}
/* #440: the wordmark is a link to the overview — keep the wordmark look (no
   underline/link color from the base anchor rule in tokens/base.css). */
.side-nav__brand .wordmark{font:var(--weight-medium) var(--text-16)/1 var(--font-sans);letter-spacing:-0.03em;color:inherit;text-decoration:none}
.side-nav__brand .wordmark b{color:var(--accent);font-weight:var(--weight-medium)}
.side-nav__brand .tag{font:var(--type-mono-sm);color:var(--text-tertiary)}
/* #440: a long nav scrolls INTERNALLY (min-height:0 lets the flex child
   shrink below its content) so the footer stays pinned at the viewport
   bottom; no double scrollbar since .side-nav is exactly 100vh tall. */
.side-nav__nav{flex:1;min-height:0;overflow-y:auto;padding:14px 8px;display:flex;flex-direction:column;gap:18px}
.side-nav__group{display:flex;flex-direction:column;gap:2px}
.side-nav__group-label{font:var(--type-eyebrow);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--text-tertiary);padding:0 8px 6px}
.side-nav__link{display:flex;align-items:center;gap:9px;height:30px;padding:0 8px;border:0;border-radius:var(--radius-2);cursor:pointer;background:transparent;color:var(--text-secondary);font:var(--type-ui);text-align:left;text-decoration:none;transition:var(--transition-control)}
.side-nav__link:hover{background:var(--bg-hover);color:var(--text-primary)}
.side-nav__link.is-active{background:var(--bg-active);color:var(--text-primary)}
.side-nav__footer{padding:10px 12px;border-top:1px solid var(--border-subtle);display:flex;flex-direction:column;gap:8px}
.side-nav__ref{display:flex;align-items:center;gap:8px;font:var(--type-ui);color:var(--text-secondary);text-decoration:none}
.side-nav__ref:hover{color:var(--text-primary)}
.side-nav__user-link{display:block;color:inherit;text-decoration:none}
.side-nav__user{display:flex;align-items:center;gap:8px;padding-top:8px;border-top:1px solid var(--border-subtle)}
.side-nav__avatar{width:22px;height:22px;border-radius:var(--radius-2);background:var(--gray-950);color:#fff;font:var(--type-mono-sm);display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}
.side-nav__user-meta{flex:1;min-width:0}
.side-nav__user-email{font:var(--type-ui);overflow:hidden;text-overflow:ellipsis}
.side-nav__user-tier{font:var(--type-mono-sm);color:var(--text-tertiary)}
.top-bar{height:var(--topbar-h);flex:0 0 auto;display:flex;align-items:center;gap:12px;padding:0 20px;border-bottom:1px solid var(--border-subtle);background:var(--bg-page)}
.top-bar__crumbs{flex:1;min-width:0;display:flex;align-items:center;gap:8px}
.top-bar__title{font:var(--type-ui);font-size:var(--text-14)}
.top-bar__actions{display:flex;align-items:center;gap:8px}
.console-main{flex:1;min-width:0;display:flex;flex-direction:column}

/* ===== auth shell (port of ui_kits/console/AuthScreens.jsx) ===== */
.auth-shell{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;background:var(--bg-subtle);color:var(--text-primary);font:var(--type-body);padding:24px 16px}
.auth-shell__brand{display:flex;align-items:center;gap:8px}
.auth-shell__brand .wordmark{font:var(--weight-medium) var(--text-20)/1 var(--font-sans);letter-spacing:-0.03em}
.auth-shell__brand .wordmark b{color:var(--accent);font-weight:var(--weight-medium)}
.auth-shell__brand .tag{font:var(--type-mono-sm);color:var(--text-tertiary)}
/* auth-card wraps yield so each auth screen renders inside a centered card */
.auth-card{width:380px;max-width:100%;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-surface);padding:24px}
.auth-shell__foot{font:var(--type-mono-sm);color:var(--text-tertiary)}
/* ===== auth screens (#173): the .auth-form/.auth-field rhythm every auth view
   renders (login/signup/confirm/MFA) — title/body/alert spacing, stacked fields
   with breathing room, and the submit button separated from the last input
   (the "no space between input and button" complaint). Mirrors the .field*
   rules below so the two rhythms stay identical. ===== */
.auth-form{display:flex;flex-direction:column;gap:14px}
.auth-form__title{font:var(--type-h2);margin:0}
.auth-form__hint{font:var(--type-body-sm);color:var(--text-secondary);margin:0}
.auth-form__alert{padding:9px 12px;border-radius:var(--radius-2);font:var(--type-body-sm);background:var(--status-err-bg);color:var(--status-err)}
.auth-form__body{display:flex;flex-direction:column;gap:12px}
.auth-form__body .ds-btn{margin-top:4px}
.auth-form__alt{font:var(--type-body-sm);color:var(--text-secondary)}
.auth-form__alt a{color:var(--accent)}
.auth-field{display:flex;flex-direction:column;gap:5px}
.auth-field + .auth-field{margin-top:6px}
.auth-field__label{font:var(--type-caption);color:var(--text-secondary);font-weight:500}
.auth-field__error{font:var(--type-caption);color:var(--status-err);min-height:0}
.auth-field[data-invalid] .ds-input{border-color:var(--status-err);box-shadow:var(--ring-focus)}

/* theme toggle: the glyph reflects the opposite of the active theme (swap on data-theme) */
.theme-toggle .ds-icon:last-child{display:none}
html[data-theme="dark"] .theme-toggle .ds-icon:first-child{display:none}
html[data-theme="dark"] .theme-toggle .ds-icon:last-child{display:inline-block}

/* locale toggle (#504): ghost/sm text button beside the theme toggle —
   min-width keeps EN/FR from reflowing the top-bar actions */
.locale-toggle{min-width:40px;font:var(--type-caption);letter-spacing:.04em}

/* ===== console screens (W4·T1) — content layout for the six product screens.
   Reuses ds-input/ds-btn/ds-badge; no new components are introduced. ===== */
.screen{padding:24px 32px;max-width:1080px;width:100%}
/* #342 follow-up (owner review): direct screen children carry no vertical
   margins of their own (.screen__row/.card have none), so stacked sections
   visually touched — e.g. Overview's metric row against the Daily volume
   card. One collapsing sibling margin restores a uniform 20px rhythm (it
   collapses with .screen__head's own margin-bottom — no doubling). */
.screen > * + *{margin-top:20px}
.screen__head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px}
.screen__head h1{font:var(--type-h2);margin:0}
.screen__head p{font:var(--type-body-sm);color:var(--text-secondary);margin:4px 0 0}
.screen__row{display:grid;grid-template-columns:1fr;gap:20px}
@media(min-width:960px){.screen__row--2{grid-template-columns:1fr 1fr}.screen__row--aside{grid-template-columns:1.6fr 1fr}.screen__row--stack>.agent-preview{grid-column:2;grid-row:1/span 3}
/* #475 (manual review): on the edit flow's testers step the interstitial
   used to auto-place into the narrow aside — the empty PATCH form kept the
   wide column and the preview dropped below it. Definite columns put the
   testers card in the main column and the preview in the aside on EVERY
   step (the empty form auto-flows to an invisible row). Stays inside this
   block: below 960px the row is one 1fr column and a definite column 2
   would open a phantom implicit track. */
.screen__row--aside>.agent-preview{grid-column:2}.screen__row--aside>[data-form-steps-target="testers"]{grid-column:1}}
@media(min-width:760px){.screen__row--3{grid-template-columns:1fr 1fr 1fr}}
.card{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-surface);padding:18px;overflow-x:auto}
.card__title{font:var(--type-ui);font-weight:var(--weight-medium);margin:0 0 12px}
.card__sub{font:var(--type-mono-sm);color:var(--text-tertiary);margin:0 0 12px}
.card__actions{display:flex;gap:8px;justify-content:flex-end;margin-top:14px}
/* #491 (review-4): right-aligned in-cell action group (agent show testers
   table; consumed by wave-2 sibling screens), mirroring .card__actions. */
.row-actions{display:flex;gap:8px;justify-content:flex-end;align-items:center}
/* forms reuse the auth-field rhythm but inline (two-up where it fits) */
.form-grid{display:grid;grid-template-columns:1fr;gap:14px}
fieldset{border:0;padding:0;margin:0;min-width:0}
legend{padding:0}
/* #368.11: a fieldset legend straddles the top border by default (the border
   passes through the legend's middle), so the Agent fieldset's border sat
   ~9px lower than the plain-card Preview aside in the same grid row — "the
   preview square is not aligned with the agent square". Floating the legend
   drops the straddle: it renders INSIDE the border like the aside's
   card__title and both card borders share the grid-row top. width:100%
   keeps the following content below the legend. */
fieldset.card>legend{float:left;width:100%}
/* #380 REVIEW_1 (gui interaction, critical): form fieldsets hold no wide
   tables (#194's overflow-x reason) but DO hold the #350 autocomplete
   listbox — overflow-x:auto computes overflow-y:auto too, clipping the
   absolutely-positioned dropdown at the card edge (~1 of 5 options visible
   at every viewport). Fieldsets drop the scroll container; flow-root keeps
   containing the floated legend without clipping. */
fieldset.card{display:flow-root;overflow:visible}
@media(min-width:760px){.form-grid--2{grid-template-columns:1fr 1fr}}
.field{display:flex;flex-direction:column;gap:5px}
.field__label{font:var(--type-caption);color:var(--text-secondary);font-weight:500}
.field__error{font:var(--type-caption);color:var(--status-err);min-height:0}
.field[data-invalid] .ds-input{border-color:var(--status-err);box-shadow:var(--ring-focus)}
.ds-input{height:var(--control-h);padding:0 10px;font:var(--type-ui);color:var(--text-primary);
  background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-control);width:100%}
.ds-input:focus{outline:none;box-shadow:var(--ring-focus)}
textarea.ds-input{height:auto;padding:8px 10px;resize:vertical;line-height:1.4}
select.ds-input{appearance:none}
/* tables */
.ds-table{width:100%;border-collapse:collapse;font:var(--type-body-sm)}
.ds-table th,.ds-table td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--border-subtle);white-space:nowrap}
.ds-table th{font:var(--type-caption);text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--text-tertiary);font-weight:500}
.ds-table td.mono,.ds-table th.mono{font:var(--type-mono-sm)}
/* paging + inline bits */
.pager{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px}
.empty{padding:24px;text-align:center;color:var(--text-tertiary);font:var(--type-body-sm)}
.kv{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-bottom:1px solid var(--border-subtle);font:var(--type-body-sm)}
.kv:last-child{border-bottom:0}
.kv__k{color:var(--text-secondary)}.kv__v{font:var(--type-mono-sm)}
.muted{color:var(--text-tertiary);font:var(--type-body-sm)}
.flash{padding:9px 12px;border-radius:var(--radius-2);font:var(--type-body-sm);margin-bottom:14px}
.flash--alert{background:var(--status-err-bg);color:var(--status-err)}.flash--notice{background:var(--status-ok-bg);color:var(--status-ok)}
/* handset preview (Send, #311) — a FAITHFUL render of the subscriber's RCS
   client, ported from rcs-relay-ui-design/components/messaging/HandsetPreview.jsx
   (built against Google's RBM X-Platform Playbook, uploads/refs-…-jfun.pdf).
   The interior deliberately uses the PLATFORMS' hardcoded palettes, not Open
   RCS tokens: the point is to show the OS surface, not our brand. The handset
   scheme follows the console data-theme (light/dark); the platform is pure
   CSS off data-platform, so the switch never rewrites the DOM. */
.handset-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}
.handset-head .card__title{margin:0}
.handset-switch{display:inline-flex;gap:2px;padding:2px;border:1px solid var(--border-default);border-radius:var(--radius-pill);background:var(--bg-surface)}
.handset-switch__btn{border:0;background:transparent;color:var(--text-secondary);font:var(--type-ui);height:30px;padding:0 14px;border-radius:var(--radius-pill);cursor:pointer}
.handset-switch__btn:hover{color:var(--text-primary);background:var(--bg-hover)}
.handset-switch__btn[aria-pressed="true"]{background:var(--bg-active);color:var(--text-primary);font-weight:500}

/* frame + Android (Google Messages) palette, light scheme (base/default).
   aspect-ratio is the member-pinned device ratio (161.15×74.24 mm ≈ 2.17:1):
   with height auto the flex column's min-content floor lets the handset GROW
   with content, but it never collapses below the ratio — a real device shape. */
.handset{
  --hs-screen:#FFFFFF;--hs-chrome:#EEF1F6;--hs-chrome-line:transparent;--hs-in-bg:#EEF1F6;--hs-in-fg:#1B1C1E;
  --hs-dim:#5F6368;--hs-faint:#747775;--hs-tint:#0B57D0;--hs-card:#EEF1F6;
  --hs-field:#EEF1F6;--hs-bar:#1B1C1E;--hs-accent-soft:#C2F0C7;--hs-bezel:#1B1B1E;
  position:relative;display:flex;flex-direction:column;width:min(340px,100%);min-width:264px;margin:0 auto;
  aspect-ratio:74.24/161.15;
  background:var(--hs-screen);color:var(--hs-in-fg);
  font-family:Roboto,"Noto Sans",system-ui,sans-serif;font-size:14px;
  border:9px solid var(--hs-bezel);border-radius:32px;box-shadow:var(--shadow-lg);overflow:hidden;
}
/* iOS 26 palette + geometry (Liquid Glass chrome) */
.handset[data-platform="ios"]{
  --hs-screen:#FFFFFF;--hs-chrome:rgba(255,255,255,.86);--hs-chrome-line:rgba(0,0,0,.10);--hs-in-bg:#E9E9EB;--hs-in-fg:#000000;
  --hs-dim:#6C6C70;--hs-faint:#8E8E93;--hs-tint:#0A7CFF;--hs-card:#FFFFFF;
  --hs-field:#FFFFFF;--hs-bar:#000000;
  font-family:-apple-system,"SF Pro Text","Helvetica Neue",system-ui,sans-serif;border-radius:44px;
}
/* dark handset schemes, still platform-accurate (a dark console shows a dark handset) */
[data-theme="dark"] .handset{
  --hs-screen:#131316;--hs-chrome:#1B1C20;--hs-in-bg:#26282D;--hs-in-fg:#E3E3E6;
  --hs-dim:#9AA0A6;--hs-faint:#8E9297;--hs-tint:#A8C7FA;--hs-card:#26282D;
  --hs-field:#26282D;--hs-bar:#E3E3E6;--hs-accent-soft:#1E4620;--hs-bezel:#3A3A3E;
}
[data-theme="dark"] .handset[data-platform="ios"]{
  --hs-screen:#000000;--hs-chrome:rgba(28,28,30,.86);--hs-chrome-line:rgba(255,255,255,.14);--hs-in-bg:#26262A;--hs-in-fg:#FFFFFF;
  --hs-dim:#98989F;--hs-faint:#8E8E93;--hs-tint:#4C9DFF;--hs-card:#1C1C1E;
  --hs-field:#1C1C1E;--hs-bar:#FFFFFF;
}

/* camera affordance: Android hole-punch dot → iOS Dynamic Island pill (bezel-black in any scheme) */
.handset__island{position:absolute;top:9px;left:50%;transform:translateX(-50%);width:11px;height:11px;border-radius:999px;background:#000;z-index:2}
.handset[data-platform="ios"] .handset__island{top:8px;width:82px;height:22px}

/* status bar: clock (+ DND bell on iOS) left, signal/wifi/battery right */
.handset__statusbar{display:flex;align-items:center;justify-content:space-between;padding:9px 16px 3px;font-size:12px;font-weight:600}
.handset[data-platform="ios"] .handset__statusbar{padding:11px 22px 3px}
.handset__clock{display:inline-flex;align-items:center;gap:4px}
.handset__bell{display:none}
.handset[data-platform="ios"] .handset__bell{display:inline-flex}
.handset__signals{display:inline-flex;align-items:center;gap:5px}

/* thread header — Google Messages app bar (back + overflow only: the agent
   identity lives in the hero below, per the RBM rich-card anatomy reference) */
.handset__header{display:flex;align-items:center;gap:11px;padding:5px 14px 10px;background:var(--hs-chrome);color:var(--hs-in-fg)}
.handset__header--android{justify-content:space-between;padding:6px 14px 2px}
.handset__logo{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:8px;background:var(--hs-tint);color:#fff}
/* Android agent hero (RBM anatomy): centered placeholder logo tile, name with
   the blue verified rosette, description — on the chrome surface */
.handset__hero{display:flex;flex-direction:column;align-items:center;gap:3px;padding:4px 16px 14px;background:var(--hs-chrome);text-align:center}
.handset__hero-logo{display:grid;place-items:center;flex:0 0 auto;width:64px;height:64px;border-radius:16px;margin-bottom:7px;background:var(--hs-tint);color:#fff}
.handset__hero-name{display:inline-flex;align-items:center;gap:6px;max-width:100%;font-size:17px;font-weight:500;line-height:1.3;color:var(--hs-in-fg)}
.handset__hero-name .handset__name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.handset__hero-name [data-icon]{flex:0 0 auto;color:var(--hs-tint)}
.handset__hero-desc{font-size:12px;line-height:1.35;color:var(--hs-dim)}
/* iOS 26: back circle — logo tile over a floating name capsule — spacer */
.handset[data-platform="ios"] .handset__header{gap:0;padding:2px 12px 8px;backdrop-filter:blur(12px);border-bottom:0.5px solid var(--hs-chrome-line)}
.handset__back{display:grid;place-items:center;width:30px;height:30px;border-radius:999px;background:var(--hs-card);box-shadow:0 1px 4px rgba(0,0,0,.12)}
.handset__id{display:flex;flex-direction:column;align-items:center;min-width:0}
.handset__capsule{display:inline-flex;align-items:center;gap:3px;max-width:180px;margin-top:-4px;padding:3px 9px;border-radius:999px;background:var(--hs-card);box-shadow:0 1px 4px rgba(0,0,0,.12);font-size:11px;font-weight:700;letter-spacing:-0.01em;white-space:nowrap}
.handset__capsule .handset__name{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* pure-CSS platform toggles (the switch only flips data-platform) */
.handset__header--ios{display:none}
.handset[data-platform="ios"] .handset__header--ios{display:grid}
.handset[data-platform="ios"] .handset__header--ios{grid-template-columns:30px 1fr 30px}
.handset[data-platform="ios"] .handset__header--android{display:none}

/* thread: the message is a plain incoming TEXT bubble on BOTH platforms (the
   send page authors text messages only — no rich card, #311 round 4). One
   shared element; CSS skins it per platform off data-platform. */
.handset__thread{flex:1;min-height:0;display:flex;flex-direction:column;gap:5px;padding:10px 12px 12px;background:var(--hs-screen);overflow-y:auto;scrollbar-width:none}
/* scrollable column: children keep their natural height (a shrinkable card
   would clip its own content — overflow:hidden zeroes its min-content floor) */
.handset__thread>*{flex:0 0 auto}
.handset[data-platform="ios"] .handset__thread{gap:3px}
.handset__day{align-self:center;margin:6px 0 8px;font-size:11px;font-weight:500;color:var(--hs-faint)}
/* pre-wrap: typed line breaks survive in the preview, as on a real client
   (gui-review interaction finding) */
.handset__bubble{align-self:flex-start;max-width:78%;padding:9px 14px 10px;border-radius:18px 18px 18px 4px;background:var(--hs-in-bg);color:var(--hs-in-fg);font-size:14px;line-height:1.4;word-break:break-word;white-space:pre-wrap}
.handset[data-platform="ios"] .handset__bubble{padding:7px 12px 8px;border-radius:19px 19px 19px 5px;font-size:15px;line-height:1.29;letter-spacing:-0.01em}
/* the Android-only chrome (agent hero, day divider) hides on iOS */
.handset[data-platform="ios"] :is(.handset__hero,.handset__day){display:none}

/* composer chrome (decorative — the real input is the console form) */
.handset__composer{align-items:center;gap:9px;padding:7px 11px 5px;background:var(--hs-chrome);color:var(--hs-in-fg)}
.handset__composer--android{display:flex}
.handset__composer--ios{display:none}
.handset[data-platform="ios"] .handset__composer--android{display:none}
.handset[data-platform="ios"] .handset__composer--ios{display:flex}
.handset[data-platform="ios"] .handset__composer{gap:8px;padding:7px 11px 4px;backdrop-filter:blur(12px)}
.handset__cfield{flex:1;display:flex;align-items:center;gap:9px;height:38px;padding:0 12px;border-radius:999px;background:var(--hs-field);font-size:13px;color:var(--hs-dim)}
.handset__chint{flex:1;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.handset__cfab{display:grid;place-items:center;flex:0 0 auto;width:38px;height:38px;border-radius:999px;background:var(--hs-accent-soft);color:var(--hs-in-fg)}
.handset__composer--ios .handset__cbtn{display:grid;place-items:center;width:30px;height:30px;border-radius:999px;background:var(--hs-card);box-shadow:0 1px 4px rgba(0,0,0,.10)}
.handset__composer--ios .handset__cfield{justify-content:space-between;height:32px;background:var(--hs-card);box-shadow:0 1px 4px rgba(0,0,0,.10);color:var(--hs-faint)}

/* home indicator / gesture pill */
.handset__home{display:block;width:96px;height:3px;margin:3px auto 6px;border-radius:999px;background:var(--hs-bar);opacity:.5}
.handset[data-platform="ios"] .handset__home{width:118px;height:4px;margin:4px auto 7px;opacity:1}

/* ===== agents index table (#443): the Name cell links to the agent page
   (no Manage column) with a 24px logo thumb; empty tile = dot fallback ===== */
.agent-name{display:inline-flex;align-items:center;gap:8px;color:var(--text-accent);text-decoration:none}
.agent-name:hover .agent-name__text{text-decoration:underline}
.agent-name__logo{width:24px;height:24px;border-radius:var(--radius-2);object-fit:cover;flex:none}
span.agent-name__logo{display:inline-flex;align-items:center;justify-content:center;background:var(--gray-950);color:#fff;font:var(--type-mono-sm)}

/* ===== agent form widgets (#354 W1.1) ===== */
/* char counter under agent_name/agent_description (driven by minlength/maxlength) */
.field__counter{font:var(--type-caption);color:var(--text-tertiary)}
/* #350 (W2.1): create-only company autocomplete. The wrapper is the
   .form-grid--2 grid child and anchors the absolute dropdown card under the
   input (the .field inside keeps its own rhythm); existing tokens only.
   #392: the card is bordered by the DS --hairline instead of --border-default
   and the shadow drops to --shadow-sm (elevation.css doctrine: depth comes
   from the 1px line, not the blur).
   #399: margin:0 is load-bearing — without it the UA default
   ul{margin-block:1em} pushes the absolute card 1em off its anchor. The
   vertical anchor itself (input bottom) is pinned by the controller's
   place(); top:100% here is only the pre-JS fallback. */
.company-autocomplete{position:relative}
.company-autocomplete__results{position:absolute;top:100%;margin:0;left:0;right:0;z-index:20;padding:6px;list-style:none;max-height:320px;overflow-y:auto;background:var(--bg-surface);border:var(--hairline);border-radius:var(--radius-2);box-shadow:var(--shadow-sm)}
.company-autocomplete__option{display:flex;flex-direction:column;gap:2px;padding:8px;border-radius:var(--radius-2);cursor:pointer;font:var(--type-body-sm);color:var(--text-primary)}
.company-autocomplete__option.is-active,.company-autocomplete__option:hover{background:var(--bg-hover);box-shadow:var(--ring-focus)}
.company-autocomplete__meta{font:var(--type-caption);color:var(--text-tertiary)}
/* color combo field: one .ds-input-styled wrapper — round swatch left,
   borderless hex input, native picker affordance right */
.color-field{display:flex;align-items:center;gap:8px;padding:0 8px}
.color-field:focus-within{box-shadow:var(--ring-focus)}
.color-field__swatch{flex:0 0 auto;width:20px;height:20px;border-radius:999px;border:1px solid var(--border-default);background:var(--bg-subtle)}
.color-field__hex{flex:1;min-width:0;height:100%;padding:0;border:0;background:transparent;font:var(--type-mono-sm);color:var(--text-primary)}
.color-field__hex:focus{outline:none}
.color-field__picker{flex:0 0 auto;width:26px;height:26px;padding:0;border:0;border-radius:999px;background:transparent;cursor:pointer}
.color-field__picker::-webkit-color-swatch-wrapper{padding:0}
.color-field__picker::-webkit-color-swatch{border:1px solid var(--border-default);border-radius:999px}
.color-field__picker::-moz-color-swatch{border:1px solid var(--border-default);border-radius:999px}
/* asset URL field: live thumbnail + image-spec hint (relay IMAGE_SPECS).
   #366.5: the logo thumb renders at the spec's real 1:1 ratio (224×224);
   the banner keeps its 1440×448 ≈ 3.2:1 box. */
.asset-field__thumb{margin-top:4px;width:56px;height:56px;object-fit:cover;border-radius:var(--radius-2);border:1px solid var(--border-subtle)}
.asset-field__thumb--banner{width:160px;height:50px}
.asset-field__specs{font:var(--type-mono-sm)}
/* W2.2 slot: the upload BUTTON markup is the sibling task's — the full-width
   tinted style ships now so W2.2 only renders class="ds-btn asset-field__upload". */
.asset-field__upload{width:100%;margin-top:6px;background:var(--bg-active);border-color:transparent;color:var(--accent)}
.asset-field__upload:hover{background:var(--bg-hover);color:var(--accent-hover)}

/* #366.11: the edit form's 2-step flow hides a step/preview via [hidden] —
   display rules would otherwise beat the UA [hidden] rule (#191 lesson). */
.form-grid[hidden],.agent-preview[hidden]{display:none}

/* #366.12: launch-confirmation modal (dialog controller) — fixed overlay +
   centred panel, same shape as the docs-search dialog. */
.modal{position:fixed;inset:0;z-index:100;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:20px}
.modal[hidden]{display:none}
.modal__panel{width:min(440px,92vw);background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-card);box-shadow:var(--shadow-lg);padding:20px}
.modal__panel .card__actions{justify-content:flex-end}

/* ===== agent info-sheet preview (agents form, #354 W1.1) — the Google
   Messages agent sheet (owner screenshot 2026-09-04), NOT the /send
   conversation handset. Metrics from rcs-relay-ui-design
   HandsetPreview.jsx AndroidAgent (92px hero, 46px logo tile breaking the
   hero edge by 26px, 50×38 tonal pills, 13px details card). Platform
   literals, not console tokens — same convention as the #311 handset. ===== */
.agent-preview{position:sticky;top:20px;align-self:start}
.agent-phone{
  --ap-sheet:#F0F4F9;--ap-card:#FFFFFF;--ap-fg:#1B1C1E;--ap-dim:#5F6368;--ap-faint:#9AA0A6;
  --ap-tint:#0B57D0;--ap-line:#E1E3E6;--ap-hero:#202124;--ap-bar:#1B1C1E;
  position:relative;display:flex;flex-direction:column;width:min(320px,100%);margin:0 auto;
  /* #366.7: standard smartphone ratio floor (19.5:9 at the 320px preview
     width ≈ 693px) even when only a few detail rows have values. */
  min-height:693px;
  background:var(--ap-sheet);color:var(--ap-fg);
  font-family:Roboto,"Noto Sans",system-ui,sans-serif;font-size:14px;
  border:9px solid #1B1B1E;border-radius:32px;box-shadow:var(--shadow-lg);overflow:hidden;
}
[data-theme="dark"] .agent-phone{
  --ap-sheet:#131316;--ap-card:#1B1C20;--ap-fg:#E3E3E6;--ap-dim:#9AA0A6;--ap-faint:#8E9297;
  --ap-tint:#A8C7FA;--ap-line:#2A2B2F;--ap-hero:#0B0C0E;--ap-bar:#E3E3E6;
}
.agent-phone__island{position:absolute;top:9px;left:50%;transform:translateX(-50%);width:11px;height:11px;border-radius:999px;background:#000;z-index:2}
.agent-phone__statusbar{display:flex;align-items:center;justify-content:space-between;padding:9px 16px 3px;font-size:12px;font-weight:600}
.agent-phone__signals{display:inline-flex;align-items:center;gap:5px}
.agent-phone__back{padding:6px 14px;color:var(--ap-fg)}
/* hero: the banner image, or the dark "hero · 1440×448" placeholder */
.agent-phone__hero{position:relative;height:92px;background:var(--ap-hero);display:grid;place-items:center}
.agent-phone__banner{width:100%;height:92px;object-fit:cover;display:block}
.agent-phone__hero-placeholder{font-size:11px;color:var(--ap-faint);letter-spacing:.02em}
/* logo tile breaks the hero's bottom edge (letter tile until a logo URL).
   #368.7: position:relative activates the z-index — without it the
   POSITIONED hero paints over the tile's 26px overlap. #368.5: the tile
   takes the agent color (--agent-tint), not just the action-pill icons.
   #368.14: the 3px sheet-colored frame is gone (owner: "remove the border
   around the logo") — radius + crop stay. */
.agent-phone__logo{display:grid;place-items:center;width:46px;height:46px;margin:-26px auto 0;position:relative;
  border-radius:12px;background:var(--agent-tint,var(--ap-tint));color:#fff;overflow:hidden;z-index:1}
.agent-phone__logo-letter{font-size:18px;font-weight:600}
.agent-phone__logo img{width:100%;height:100%;object-fit:cover}
.agent-phone__id{display:flex;flex-direction:column;align-items:center;gap:2px;padding:8px 16px 12px;text-align:center}
.agent-phone__name{font-size:16px;font-weight:500;line-height:1.3}
.agent-phone__desc{font-size:12px;line-height:1.35;color:var(--ap-dim)}
/* tonal action pills: 50×38, 17px icon (inline svg) + 10.5px label below;
   the agent color tints the icons via --agent-tint */
.agent-phone__actions{display:flex;justify-content:center;gap:10px;padding:2px 16px 14px}
.agent-phone__action{display:flex;flex-direction:column;align-items:center;gap:4px}
.agent-phone__pill{display:grid;place-items:center;width:50px;height:38px;border-radius:999px;background:var(--ap-card);color:var(--agent-tint,var(--ap-tint))}
.agent-phone__action-label{font-size:10.5px;color:var(--ap-dim)}
/* details cards: value-over-label rows; a row stays hidden until its value exists */
.agent-phone__details{display:flex;flex-direction:column;margin:0 12px;background:var(--ap-card);border-radius:13px;overflow:hidden}
.agent-phone__details + .agent-phone__details{margin-top:10px}
.agent-phone__row{display:flex;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--ap-line)}
.agent-phone__row:last-child{border-bottom:0}
.agent-phone__row>svg,.agent-phone__row>[data-icon]{flex:0 0 auto;color:var(--ap-dim)}
.agent-phone__meta{display:flex;flex-direction:column;min-width:0}
.agent-phone__value{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.agent-phone__label{font-size:11px;color:var(--ap-dim)}
/* display:flex/block above would beat the UA [hidden] rule — restore it
   (the #191 dialog lesson): rows/thumbs start hidden until a value exists. */
.agent-phone :is(.agent-phone__row,img)[hidden]{display:none}
.agent-phone__gesture{width:96px;height:3px;margin:10px auto 6px;border-radius:999px;background:var(--ap-bar);opacity:.5}
/* drawer (Events detail) — lightweight stand-in for a DS Drawer component */
.ds-drawer{position:fixed;inset:0;z-index:50;background:rgba(0,0,0,.4)}
.ds-drawer__panel{position:absolute;top:0;right:0;height:100%;width:380px;max-width:90vw;background:var(--bg-surface);border-left:1px solid var(--border-subtle);padding:18px;overflow:auto;box-shadow:var(--elevation-2,var(--shadow-card))}
.ds-drawer__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
/* #449 (plan #439 W1·T10): past 1392px (1080px .screen cap + 2×32px padding
   + 248px sidebar) the main column stops stretching, so give the event-detail
   panel its full double width; below that the 380px/90vw base applies. */
@media(min-width:1392px){.ds-drawer__panel{width:760px;max-width:calc(100vw - 248px)}}

/* ===== Overview dashboard (W2·T1, #168) — metric cards + pure-CSS stacked
   bar chart from the zero-filled by_date series (no chart library, no Node
   build). Heights are data-driven inline styles; colors are tokens. ===== */
.metric{display:flex;flex-direction:column;gap:8px}
.metric__label{font:var(--type-caption);font-weight:500;text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--text-tertiary)}
.metric__value{font-family:var(--font-mono);font-size:var(--text-30);font-weight:var(--weight-medium);letter-spacing:-0.02em;line-height:1.3}
.metric__sub{font:var(--type-mono-sm);color:var(--text-secondary)}
.bars{display:flex;align-items:flex-end;gap:10px}
.bars__day{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
.bars__group{display:flex;align-items:flex-end;gap:2px;height:120px}
.bars__bar{flex:1;min-width:2px;border-radius:var(--radius-1) var(--radius-1) 0 0}
.bars__bar--sent{background:var(--bg-active)}
.bars__bar--delivered{background:var(--accent)}
.bars__bar--failed{background:var(--status-err)}
.bars__label{font:var(--type-mono-sm);font-size:10px;color:var(--text-tertiary);text-align:center}
.bars__legend{display:flex;gap:16px;margin-top:12px}
.bars__legend span{display:inline-flex;align-items:center;gap:6px;font:var(--type-mono-sm);color:var(--text-tertiary)}
.bars__swatch{width:8px;height:8px;border-radius:var(--radius-1);flex:none}
.bars__swatch--sent{background:var(--bg-active)}
.bars__swatch--delivered{background:var(--accent)}
.bars__swatch--failed{background:var(--status-err)}

/* ===== responsive shell (REVIEW_28 fix): the fixed 248px sidebar starved the
   content column below 768px (~127px at 375px) and every console screen
   overflowed. The nav collapses off-canvas behind the .nav-toggle hamburger;
   wide tables scroll inside their cards instead of the page — the base
   `.card{overflow-x:auto}` rule (REVIEW_2 critical, #194) covers ALL widths:
   cards stay their own scroll container and grid items past 768px too. ===== */
.nav-toggle{display:none;flex-direction:column;align-items:center}
.nav-toggle .nav-toggle__bar{width:16px;height:2px;background:currentColor;border-radius:1px}
.nav-toggle .nav-toggle__bar + .nav-toggle__bar{margin-top:3px}
/* scrim painted whenever an off-canvas nav is open — base scope so it covers
   the docs tree drawer at ≤820px (#310), not only the console nav at ≤767px;
   the attribute is only ever set while a toggle is visible, so it can't fire
   on desktop. */
body[data-nav-open]::before{content:"";position:fixed;inset:0;z-index:55;background:rgba(0,0,0,.4)}
@media(max-width:767px){
.nav-toggle{display:inline-flex}
.side-nav{position:fixed;top:0;bottom:0;left:0;z-index:60;transform:translateX(-100%);transition:transform var(--transition-control);box-shadow:var(--elevation-2,var(--shadow-card))}
body[data-nav-open] .side-nav{transform:none}
.top-bar{padding:0 12px}
.top-bar__title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* #342 follow-up: the TopBar Send CTA goes icon-only on phones so the
   hamburger + crumbs + toggles cannot overflow (aria-label stays). */
.top-bar__send-label{display:none}
.screen{padding:16px}
.kv__v{overflow-wrap:anywhere}
}
/* ===== in-page section nav + stacked sections (Account, W2·T3): anchor jump
   links over stacked cards; fieldset reset so `disabled` locks a whole form. */
.section-nav{display:flex;gap:2px;margin-bottom:20px;border-bottom:1px solid var(--border-subtle)}
.section-nav a{padding:7px 10px;font:var(--type-ui);color:var(--text-secondary);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px}
.section-nav a:hover{color:var(--text-primary)}
.section-nav a:focus-visible{outline:none;box-shadow:var(--ring-focus)}
.section__title{font:var(--type-ui);font-weight:var(--weight-medium);margin:0 0 10px}
#general,#api,#usage{scroll-margin-top:16px}
fieldset.form-grid{border:0;padding:0;margin:0;min-width:0}
.topup-amounts{display:flex;flex-wrap:wrap;gap:8px}
/* #426: GUI review — presets must RENDER right under the amount field with
   "Continue to Stripe" last, but the DOM order stays actions→presets (the
   implicit-Enter default button — REVIEW_3 in billing.html.erb; implicit
   submission walks tree order, never visual order). form-grid is a single
   1fr-column grid, so .topup-form places its three unchanged children on
   explicit rows: tree order untouched, visual order flipped. #amount gets
   a narrow euro width instead of the full-width field. */
.topup-form>.field{grid-row:1}
.topup-form>.topup-amounts{grid-row:2}
.topup-form>.card__actions{grid-row:3}
.topup-form #amount{width:12ch;max-width:100%}
/* #310: five presets wrap 4+1 in a flex row at 375px — on narrow viewports
   they become an equal-width 3+2 grid. CSS-only: the REVIEW_3 DOM order
   (primary button first, then presets) must stay untouched. */
@media(max-width:480px){
.topup-amounts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.topup-amounts .ds-btn{width:100%}
}
.card__actions--start{justify-content:flex-start}

/* ===== docs site (public API reference — port of ui_kits/docs, issue #169).
   Distinct from the console shell: sticky header + 320px endpoint tree +
   680px prose column + 196px on-this-page rail. ===== */
.docs-shell{min-height:100vh;background:var(--bg-page);color:var(--text-primary);font:var(--type-body)}
.docs-header{position:sticky;top:0;z-index:20;height:var(--topbar-h);display:flex;align-items:center;gap:16px;padding:0 24px;border-bottom:1px solid var(--border-subtle);background:var(--bg-page)}
.docs-header__wordmark{font:var(--weight-medium) var(--text-16)/1 var(--font-sans);letter-spacing:-0.03em;color:var(--text-primary);text-decoration:none}
.docs-header__wordmark b{color:var(--accent);font-weight:var(--weight-medium)}
.docs-header__tag{font:var(--type-mono-sm);color:var(--text-tertiary)}
.docs-header__spacer{flex:1}
.docs-search__btn{display:inline-flex;align-items:center;gap:8px;height:28px;padding:0 8px 0 10px;border:1px solid var(--border-default);border-radius:var(--radius-2);background:var(--bg-surface);color:var(--text-tertiary);font:var(--type-ui);cursor:pointer}
.docs-search__kbd{font:var(--type-mono-sm);border:1px solid var(--border-subtle);border-radius:3px;padding:1px 4px}
.docs-header__back{font:var(--type-body-sm);color:var(--text-secondary);text-decoration:none}
.docs-header__back:hover{color:var(--text-primary)}
.docs-body{display:flex;align-items:stretch}
.docs-tree{width:320px;flex:0 0 auto;padding:24px 16px;border-right:1px solid var(--border-subtle);display:flex;flex-direction:column;gap:18px}
.docs-tree__label{display:block;font:var(--type-eyebrow);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--text-tertiary);padding-bottom:6px}
.docs-tree__items{display:flex;flex-direction:column;gap:3px;border-left:1px solid var(--border-subtle);padding-left:14px}
.docs-tree__link{display:flex;align-items:center;gap:8px;padding:4px 0 4px 14px;margin-left:-15px;border-left:1px solid transparent;color:var(--text-secondary);font:var(--type-body-sm);text-decoration:none;transition:var(--transition-control)}
.docs-tree__link:hover{color:var(--text-primary)}
.docs-tree__link.is-active{border-left-color:var(--accent);color:var(--text-primary);font-weight:var(--weight-medium)}
.docs-tree__link .ds-method{flex-shrink:0}
.docs-tree__path{font:var(--type-mono-sm);color:inherit;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
.docs-content{flex:1;min-width:0}
.docs-page{display:flex;align-items:stretch}
.docs-article{flex:1;min-width:0;max-width:var(--prose-max);padding:32px 24px 64px;display:flex;flex-direction:column;gap:20px}
.docs-rail{width:196px;flex:0 0 auto;padding:32px 20px;position:sticky;top:var(--topbar-h);align-self:flex-start;display:flex;flex-direction:column;gap:7px}
.docs-rail__label{font:var(--type-eyebrow);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--text-tertiary);margin-bottom:3px}
.docs-rail a{font:var(--type-caption);color:var(--text-secondary);text-decoration:none}
.docs-rail a:hover{color:var(--text-primary)}
.docs-eyebrow{font:var(--type-eyebrow);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--text-tertiary)}
.docs-h1{font:var(--type-h1);letter-spacing:var(--track-heading);margin:0}
.docs-h2{font:var(--type-h2);letter-spacing:var(--track-heading);margin:8px 0 0;border-top:1px solid var(--border-subtle);padding-top:20px}
.docs-h2--first{border-top:0;padding-top:0}
.docs-opline{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.docs-opline__path{font:var(--type-mono-sm);color:var(--text-primary);overflow-wrap:anywhere}
.docs-desc{margin:0;color:var(--text-secondary);white-space:pre-line}
.docs-desc code,.docs-desc-cell code{background:var(--code-inline-bg);padding:2px 5px;border-radius:3px}
.docs-desc-cell{color:var(--text-secondary)}
.docs-code{margin:0;background:var(--code-bg);color:var(--code-text);border:1px solid var(--code-border);border-radius:var(--radius-card);padding:12px 14px;overflow-x:auto;scrollbar-width:thin;scrollbar-color:var(--border-subtle) transparent;font:var(--type-mono-sm);white-space:pre}
.docs-example__title{margin:0;font:var(--type-ui);color:var(--text-tertiary)}
.docs-callout{display:block;padding:12px 14px;background:var(--bg-subtle);border:1px solid var(--border-subtle);border-radius:var(--radius-card);color:var(--text-secondary);font:var(--type-body-sm)}
/* docs tables scroll in their own box (like console .card) instead of
   forcing the page wide on phones; display:block makes the table element
   itself the scroll container. */
.docs-article .ds-table{display:block;overflow-x:auto;scrollbar-width:thin;scrollbar-color:var(--border-subtle) transparent}
/* ⌘K dialog */
.docs-search__dialog{position:fixed;inset:0;z-index:100;background:rgba(0,0,0,.4);display:flex;align-items:flex-start;justify-content:center;padding-top:12vh}
/* the base display:flex beats the UA [hidden] rule — without this override
   the dialog renders permanently open and intercepts every click (#191). */
.docs-search__dialog[hidden]{display:none}
/* Panel aligns to the article column (#441): same --prose-max width token
   as .docs-article, left edge on the 320px tree boundary; the auto right
   margin soaks up the overlay's free space, which makes the dialog's
   justify-content:center inert in that axis (no DOM change, no vertical move
   — 12vh padding-top keeps the drop position). */
.docs-search__panel{width:min(var(--prose-max),calc(100vw - 352px));margin-left:320px;margin-right:auto;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-card);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;overflow:hidden}
.docs-search__input{margin:10px}
.docs-search__results{display:flex;flex-direction:column;max-height:50vh;overflow-y:auto;padding:0 6px 8px}
.docs-search__hit{display:flex;align-items:center;gap:10px;padding:8px;border-radius:var(--radius-2);color:var(--text-secondary);font:var(--type-body-sm);text-decoration:none}
.docs-search__hit.is-selected,.docs-search__hit:hover{background:var(--bg-hover);color:var(--text-primary)}
.docs-search__hit code{font:var(--type-mono-sm);color:var(--text-tertiary);margin-left:auto}
.docs-search__empty{padding:12px;color:var(--text-tertiary);font:var(--type-body-sm)}
/* #475 (manual review): the ⌘K button aligns with the article column like
   the panel (#441) — left edge on the 320px tree boundary. The header is
   position:sticky (a containing block), so absolute + left:320px lands it
   at viewport x=320, the panel's own margin-left. Out of the flex flow:
   the spacer still pushes "Back to console" right. ≤820px keeps the flex
   flow — the panel re-centers there too. #484: the button also spans the
   article column via the panel's exact width formula, so bar, panel, and
   article share widths; border-box keeps the 1px border inside. */
@media(min-width:821px){.docs-search__btn{position:absolute;left:320px;top:50%;transform:translateY(-50%);width:min(var(--prose-max),calc(100vw - 352px));box-sizing:border-box}}
/* docs responsive: drop the rail, then send the tree off-canvas (#310 — it
   used to vanish with display:none, leaving ⌘K as the only way to browse
   endpoints). Same contract as the console side nav: fixed panel restored by
   body[data-nav-open], base-scope scrim, Escape/scrim closed by the mobile-nav
   controller hosted on the docs <body>. Tree links navigate with a full page
   load, which resets the attribute. */
@media(max-width:1080px){.docs-rail{display:none}}
@media(max-width:820px){
.docs-tree-toggle{display:inline-flex}
.docs-tree{position:fixed;top:0;bottom:0;left:0;z-index:60;width:300px;max-width:85vw;background:var(--bg-page);overflow-y:auto;transform:translateX(-100%);transition:transform var(--transition-control);box-shadow:var(--elevation-2,var(--shadow-card))}
body[data-nav-open] .docs-tree{transform:none}
/* tree off-canvas → the article starts at the viewport edge, so the ⌘K
   panel falls back to the centered overlay (#441). */
.docs-search__panel{width:min(560px,90vw);margin:0}
}
/* phones: let the header reflow (fixed height + no wrap made "Back to
   console" collide with the brand block) and drop the non-essential tag. */
@media(max-width:640px){
  .docs-header{height:auto;min-height:var(--topbar-h);flex-wrap:wrap;padding:10px 16px;gap:6px 12px}
  .docs-header__tag{display:none}
}
