
@font-face{font-family:"Space Grotesk";font-style:normal;font-weight:300 700;font-display:swap;
  src:url(/assets/fonts/space-grotesk-latin-variable.woff2) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Onest";font-style:normal;font-weight:100 900;font-display:swap;
  src:url(/assets/fonts/onest-variable.woff2) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Geist Mono";font-style:normal;font-weight:400 700;font-display:swap;
  src:url(/assets/fonts/geist-mono-variable.woff2) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
:root{color-scheme:dark;
  --bg:#1A1816;--card:#242120;--elev:#2E2A28;--line:rgba(237,231,220,.14);--line2:rgba(237,231,220,.22);
  --ink:#EDE7DA;--mute:#C9C2B4;--faint:#A9A192;
  --accent:#3FB3A6;--accent2:#6FD0C4;--on-accent:#12100E;--sky:#6FD0C4;
  --agn-mark:#5FBFB4;
  --warn:#D9A05B;--on-warn:#1a1206;--ok:#7FB77E;--danger-ink:#EE8272;--danger-line:rgba(224,122,107,.16);
  --eyebrow:#D9A05B;--ok-soft:rgba(127,183,126,.16);--amber-soft:rgba(217,160,91,.16);--info-soft:rgba(63,179,166,.14);
  --navpill:#EDE7DA;--navpill-ink:#1A1816;--cta:#EDE7DA;--cta-ink:#12100E;
  --id-0:#3FB3A6;--id-1:#D9A05B;--id-2:#7FB77E;--id-3:#B9A7D9;--id-4:#7FA7C9;--id-5:#E07A6B;
  --shadow:none;--shadow-lift:0 8px 24px rgba(0,0,0,.40);--ring:0 0 0 2px var(--bg),0 0 0 4px var(--accent);
  /* THE PORTED AgnView SET (dark half). ADR-062: a colour the estate uses is a
     colour the canonical palette declares, so the set app/ui/v3/ds-agnview-tokens.css
     carries is declared HERE and aliased there, rather than living only in the
     panel where no theme can reach it. Ready reuses --sky and needs no value of
     its own; every other value below is genuinely new to this palette. */
  --ds-surface-1:#12161F;--ds-surface-2:#1A202B;--ds-surface-3:#232B38;--ds-surface-hover:#1F2734;
  --ds-border:#2B3442;--ds-border-strong:#3D4859;
  --ds-text-2:#A3AEC0;--ds-text-3:#78829A;
  --ds-status-pending:#94A3B8;--ds-status-ready:var(--sky);--ds-status-running:#22D3EE;
  --ds-status-completed:#86EFAC;--ds-status-failed:#F43F5E;--ds-status-revision:#FBBF24;
  /* Social sign-in brand colours (operator decision 2026-09-23). Identical in
     both theme blocks on purpose: each provider's own guidelines fix these
     regardless of the page's light/dark state, so they are theme-INVARIANT
     tokens rather than theme-aware ones - declared twice, never resolved
     differently, which is the honest way to say "this one does not change". */
  --brand-google-red:#EA4335;--brand-google-blue:#4285F4;--brand-google-yellow:#FBBC05;--brand-google-green:#34A853;
  --brand-google-text:#1F1F1F;--brand-google-border:#DADCE0;--brand-google-border-hover:#1A73E8;--brand-google-bg-hover:#F8F9FA;
  --brand-white:#FFFFFF;--brand-apple-bg:#000000;--brand-apple-bg-hover:#1A1A1A;
  --brand-facebook:#1877F2;--brand-facebook-hover:#166FE5;
  --brand-github-bg:#151515;--brand-github-border:#2A2A2A;--brand-github-bg-hover:#24292E;
  --brand-ms-red:#F25022;--brand-ms-green:#7FBA00;--brand-ms-blue:#00A4EF;--brand-ms-yellow:#FFB900;--brand-ms-border:#8C8C8C;--brand-dark-edge:#6A645C;
  /* Round 127 (ADR-101): the brand faces are back, and self-hosted this time,
     so they actually load instead of being named at a CDN nobody calls. Onest
     for body, Geist Mono for code, Space Grotesk (600) for display
     headings. --font-display is THE display token and --display is kept as
     an alias because the ui-components contract names --display. Segoe UI stays as the FIRST fallback, so a surface that has not
     got the face yet still reads in the round 126 face rather than in a
     browser default. Values mirror shared/theme.css in slobal.com. */
  --sans:"Onest","Segoe UI","Segoe UI Variable Text",system-ui,-apple-system,sans-serif;
  --mono:"Geist Mono",ui-monospace,"Cascadia Code",Consolas,monospace;
  --font-display:"Space Grotesk",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --display:var(--font-display);}
@media (prefers-color-scheme:light){:root{color-scheme:light;
  --bg:#F1ECE3;--card:#FAF7F1;--elev:#F4EFE6;--line:#DDD5C7;--line2:#C9C0B0;
  --ink:#2B2622;--mute:#5C544B;--faint:#676057;--accent:#0D6E89;--accent2:#0891B2;--on-accent:#FFFFFF;--sky:#0E7490;
  --agn-mark:#03625A;
  /* ROUND 140. --ok and --danger-ink carried the DARK theme's values in this
     LIGHT block, and both failed WCAG AA here: #3fbf9f is about 2.0:1 on white
     and #FF8B7D about 2.5:1, which is why qa/a11y/axe-gate.mjs shipped in round
     139 with two named contrast exceptions and a note saying the fix was two
     token values. These are those two values, and they are not new colours: the
     design system already declares --ds-ok-ink #067647 and --ds-bad-ink #B42318
     for the same two roles in the same theme. The dark block above is untouched,
     where those values are the readable ones. The exceptions are gone from the
     gate, which is strict from this round on. */
  --warn:#8D5A1C;--on-warn:var(--bg);--ok:#387349;--danger-ink:#A4443A;--danger-line:#F3DFDA;
  /* --on-warn is light here, not the dark #1a1206 the Ink block below keeps:
     the darker Linen --warn moved as part of the UI v4 palette migration and
     dark text on it measures 3.19:1, which fails WCAG AA's 4.5:1 for the
     10.5px badge text that reads it (relay/lib/shell.mjs's .snav .badge). The
     Ink pairing (dark text on the lighter Ink --warn) was never affected and
     stays at 8.06:1. --bg is --paper's relay name, already 4.94:1 here. */
  --eyebrow:#9C6420;--ok-soft:#E4EEE3;--amber-soft:#F3E7D3;--info-soft:#DDEBEE;
  --navpill:#2B2622;--navpill-ink:#F1ECE3;--cta:#2B2622;--cta-ink:#F1ECE3;
  --id-0:#0E7490;--id-1:#9C6420;--id-2:#3C7A4E;--id-3:#6E5A8A;--id-4:#3F6B8C;--id-5:#A9463C;
  --shadow:none;--shadow-lift:0 6px 18px rgba(43,38,34,.10);--ring:0 0 0 2px var(--bg),0 0 0 4px var(--accent);
  /* THE PORTED AgnView SET (light half). The same six status roles, verified in
     the AgnView audit against the Vienot 1999 deuteranopia transform and against
     measured greyscale luminance, so they stay separable for a red-green colour
     blind reader and in a monochrome print. Surface 1 reuses --card and ready
     reuses --accent; the other eleven values are new to this palette. --ok
     (#067647) and --warn (#F59E0B) were NOT reused for completed and revision:
     those two are the panel's own audited pair (#15803D, #B45309) and swapping
     in near neighbours would have moved a tested contrast set. */
  --ds-surface-1:var(--card);--ds-surface-2:#FAFAF7;--ds-surface-3:#EDECE6;--ds-surface-hover:#F1F0EA;
  --ds-border:#DEDCD3;--ds-border-strong:#C2BFB2;
  --ds-text-2:#4A4F5C;--ds-text-3:#6E7382;
  --ds-status-pending:#475569;--ds-status-ready:var(--accent);--ds-status-running:#155E75;
  --ds-status-completed:#15803D;--ds-status-failed:#881337;--ds-status-revision:#B45309;
  /* Social sign-in brand colours - same values as the dark block, see the note
     there. Declared again here rather than left to fall through, because a
     literal missing from either block reads as "undeclared" to the gate that
     checks this file, not as "inherited". */
  --brand-google-red:#EA4335;--brand-google-blue:#4285F4;--brand-google-yellow:#FBBC05;--brand-google-green:#34A853;
  --brand-google-text:#1F1F1F;--brand-google-border:#DADCE0;--brand-google-border-hover:#1A73E8;--brand-google-bg-hover:#F8F9FA;
  --brand-white:#FFFFFF;--brand-apple-bg:#000000;--brand-apple-bg-hover:#1A1A1A;
  --brand-facebook:#1877F2;--brand-facebook-hover:#166FE5;
  --brand-github-bg:#151515;--brand-github-border:#2A2A2A;--brand-github-bg-hover:#24292E;
  --brand-ms-red:#F25022;--brand-ms-green:#7FBA00;--brand-ms-blue:#00A4EF;--brand-ms-yellow:#FFB900;--brand-ms-border:#8C8C8C;--brand-dark-edge:#6A645C;}}
html[data-theme="dark"]{color-scheme:dark;
  --bg:#1A1816;--card:#242120;--elev:#2E2A28;--line:rgba(237,231,220,.14);--line2:rgba(237,231,220,.22);
  --ink:#EDE7DA;--mute:#C9C2B4;--faint:#A9A192;
  --accent:#3FB3A6;--accent2:#6FD0C4;--on-accent:#12100E;--sky:#6FD0C4;
  --agn-mark:#5FBFB4;
  --warn:#D9A05B;--on-warn:#1a1206;--ok:#7FB77E;--danger-ink:#EE8272;--danger-line:rgba(224,122,107,.16);
  --eyebrow:#D9A05B;--ok-soft:rgba(127,183,126,.16);--amber-soft:rgba(217,160,91,.16);--info-soft:rgba(63,179,166,.14);
  --navpill:#EDE7DA;--navpill-ink:#1A1816;--cta:#EDE7DA;--cta-ink:#12100E;
  --id-0:#3FB3A6;--id-1:#D9A05B;--id-2:#7FB77E;--id-3:#B9A7D9;--id-4:#7FA7C9;--id-5:#E07A6B;
  --shadow:none;--shadow-lift:0 8px 24px rgba(0,0,0,.40);--ring:0 0 0 2px var(--bg),0 0 0 4px var(--accent);
  /* THE PORTED AgnView SET (dark half). ADR-062: a colour the estate uses is a
     colour the canonical palette declares, so the set app/ui/v3/ds-agnview-tokens.css
     carries is declared HERE and aliased there, rather than living only in the
     panel where no theme can reach it. Ready reuses --sky and needs no value of
     its own; every other value below is genuinely new to this palette. */
  --ds-surface-1:#12161F;--ds-surface-2:#1A202B;--ds-surface-3:#232B38;--ds-surface-hover:#1F2734;
  --ds-border:#2B3442;--ds-border-strong:#3D4859;
  --ds-text-2:#A3AEC0;--ds-text-3:#78829A;
  --ds-status-pending:#94A3B8;--ds-status-ready:var(--sky);--ds-status-running:#22D3EE;
  --ds-status-completed:#86EFAC;--ds-status-failed:#F43F5E;--ds-status-revision:#FBBF24;
  /* Social sign-in brand colours (operator decision 2026-09-23). Identical in
     both theme blocks on purpose: each provider's own guidelines fix these
     regardless of the page's light/dark state, so they are theme-INVARIANT
     tokens rather than theme-aware ones - declared twice, never resolved
     differently, which is the honest way to say "this one does not change". */
  --brand-google-red:#EA4335;--brand-google-blue:#4285F4;--brand-google-yellow:#FBBC05;--brand-google-green:#34A853;
  --brand-google-text:#1F1F1F;--brand-google-border:#DADCE0;--brand-google-border-hover:#1A73E8;--brand-google-bg-hover:#F8F9FA;
  --brand-white:#FFFFFF;--brand-apple-bg:#000000;--brand-apple-bg-hover:#1A1A1A;
  --brand-facebook:#1877F2;--brand-facebook-hover:#166FE5;
  --brand-github-bg:#151515;--brand-github-border:#2A2A2A;--brand-github-bg-hover:#24292E;
  --brand-ms-red:#F25022;--brand-ms-green:#7FBA00;--brand-ms-blue:#00A4EF;--brand-ms-yellow:#FFB900;--brand-ms-border:#8C8C8C;--brand-dark-edge:#6A645C;}
html[data-theme="light"]{color-scheme:light;
  --bg:#F1ECE3;--card:#FAF7F1;--elev:#F4EFE6;--line:#DDD5C7;--line2:#C9C0B0;
  --ink:#2B2622;--mute:#5C544B;--faint:#676057;--accent:#0D6E89;--accent2:#0891B2;--on-accent:#FFFFFF;--sky:#0E7490;
  --agn-mark:#03625A;
  /* ROUND 140. --ok and --danger-ink carried the DARK theme's values in this
     LIGHT block, and both failed WCAG AA here: #3fbf9f is about 2.0:1 on white
     and #FF8B7D about 2.5:1, which is why qa/a11y/axe-gate.mjs shipped in round
     139 with two named contrast exceptions and a note saying the fix was two
     token values. These are those two values, and they are not new colours: the
     design system already declares --ds-ok-ink #067647 and --ds-bad-ink #B42318
     for the same two roles in the same theme. The dark block above is untouched,
     where those values are the readable ones. The exceptions are gone from the
     gate, which is strict from this round on. */
  --warn:#8D5A1C;--on-warn:var(--bg);--ok:#387349;--danger-ink:#A4443A;--danger-line:#F3DFDA;
  /* --on-warn is light here, not the dark #1a1206 the Ink block below keeps:
     the darker Linen --warn moved as part of the UI v4 palette migration and
     dark text on it measures 3.19:1, which fails WCAG AA's 4.5:1 for the
     10.5px badge text that reads it (relay/lib/shell.mjs's .snav .badge). The
     Ink pairing (dark text on the lighter Ink --warn) was never affected and
     stays at 8.06:1. --bg is --paper's relay name, already 4.94:1 here. */
  --eyebrow:#9C6420;--ok-soft:#E4EEE3;--amber-soft:#F3E7D3;--info-soft:#DDEBEE;
  --navpill:#2B2622;--navpill-ink:#F1ECE3;--cta:#2B2622;--cta-ink:#F1ECE3;
  --id-0:#0E7490;--id-1:#9C6420;--id-2:#3C7A4E;--id-3:#6E5A8A;--id-4:#3F6B8C;--id-5:#A9463C;
  --shadow:none;--shadow-lift:0 6px 18px rgba(43,38,34,.10);--ring:0 0 0 2px var(--bg),0 0 0 4px var(--accent);
  /* THE PORTED AgnView SET (light half). The same six status roles, verified in
     the AgnView audit against the Vienot 1999 deuteranopia transform and against
     measured greyscale luminance, so they stay separable for a red-green colour
     blind reader and in a monochrome print. Surface 1 reuses --card and ready
     reuses --accent; the other eleven values are new to this palette. --ok
     (#067647) and --warn (#F59E0B) were NOT reused for completed and revision:
     those two are the panel's own audited pair (#15803D, #B45309) and swapping
     in near neighbours would have moved a tested contrast set. */
  --ds-surface-1:var(--card);--ds-surface-2:#FAFAF7;--ds-surface-3:#EDECE6;--ds-surface-hover:#F1F0EA;
  --ds-border:#DEDCD3;--ds-border-strong:#C2BFB2;
  --ds-text-2:#4A4F5C;--ds-text-3:#6E7382;
  --ds-status-pending:#475569;--ds-status-ready:var(--accent);--ds-status-running:#155E75;
  --ds-status-completed:#15803D;--ds-status-failed:#881337;--ds-status-revision:#B45309;
  /* Social sign-in brand colours - same values as the dark block, see the note
     there. Declared again here rather than left to fall through, because a
     literal missing from either block reads as "undeclared" to the gate that
     checks this file, not as "inherited". */
  --brand-google-red:#EA4335;--brand-google-blue:#4285F4;--brand-google-yellow:#FBBC05;--brand-google-green:#34A853;
  --brand-google-text:#1F1F1F;--brand-google-border:#DADCE0;--brand-google-border-hover:#1A73E8;--brand-google-bg-hover:#F8F9FA;
  --brand-white:#FFFFFF;--brand-apple-bg:#000000;--brand-apple-bg-hover:#1A1A1A;
  --brand-facebook:#1877F2;--brand-facebook-hover:#166FE5;
  --brand-github-bg:#151515;--brand-github-border:#2A2A2A;--brand-github-bg-hover:#24292E;
  --brand-ms-red:#F25022;--brand-ms-green:#7FBA00;--brand-ms-blue:#00A4EF;--brand-ms-yellow:#FFB900;--brand-ms-border:#8C8C8C;--brand-dark-edge:#6A645C;}

/* SYSTEMIC 2.2 (UX programme): one target-size token and one gap token for every relay surface.
   --hit-min is 32px with a fine pointer and 44px with a coarse one. --gap-min is 8px. The .act rules
   take them, so a desktop action row is 32px tall with 8px between neighbours. The coarse rules
   further down keep their own 44px and are not touched. */
:root{--hit-min:32px;--gap-min:8px}
@media (pointer:coarse){:root{--hit-min:44px}}
/* UX programme S3: pressed and busy state, the pattern of the Helmsly panel (tokens.css, panel sweep 02).
   aria-busy="true" dims the label, draws a ring and refuses input. :active scales the button by .98. */
.act[aria-busy="true"],html[data-ds] .ds-btn[aria-busy="true"],html[data-ds] .ds-btn-primary[aria-busy="true"]{position:relative;cursor:progress;pointer-events:none;opacity:.7}
.act[aria-busy="true"]::after,html[data-ds] .ds-btn[aria-busy="true"]::after,html[data-ds] .ds-btn-primary[aria-busy="true"]::after{content:"";display:inline-block;width:12px;height:12px;border-radius:50%;border:2px solid currentColor;border-right-color:transparent;animation:gspin .8s linear infinite;flex:none}
@keyframes gspin{to{transform:rotate(360deg)}}
.act:active:not([disabled]):not([aria-busy="true"]),html[data-ds] .ds-btn:active:not(:disabled):not([aria-busy="true"]),html[data-ds] .ds-btn-primary:active:not(:disabled):not([aria-busy="true"]){transform:scale(.98)}
@media (prefers-reduced-motion:reduce){.act[aria-busy="true"]::after,html[data-ds] .ds-btn[aria-busy="true"]::after,html[data-ds] .ds-btn-primary[aria-busy="true"]::after{animation:none}}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html{-webkit-text-size-adjust:100%}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
body{margin:0;font:16px/1.55 var(--sans);background:var(--bg);color:var(--ink);min-height:100vh;
  padding:0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  font-feature-settings:"kern" 1,"liga" 1;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;position:relative}

.topbar{display:flex;align-items:center;gap:12px;min-height:50px;padding:env(safe-area-inset-top) calc(16px + env(safe-area-inset-right)) 0 calc(16px + env(safe-area-inset-left));background:var(--card);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:10}
.topbar::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:1px;background:linear-gradient(90deg,transparent,rgba(6,182,212,.5) 40%,rgba(56,189,248,.4) 62%,transparent)}
/* Round 101 (TagFix sweep): the top-left of every surface is the FULL Slobal
   wordmark - the logotype, not the bare torus - and the whole brand slot is a
   LINK to the apex home. The letters are drawn in currentColor so the ◐ toggle
   recolours them with the rest of the bar; the O keeps its gradient in both
   themes. A product name (a workspace, or "Helmsly" on the offline page)
   follows behind a hairline divider, Entra style, and IT is the part that
   ellipsises - the logotype itself never squeezes or wraps. .brand shrinks
   (flex:0 1 auto + min-width:0) so a long workspace name can never widen the
   page; relay-fit.mjs holds that at an iPhone-12 viewport. */
.brand{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:-.015em;font-size:15px;flex:0 1 auto;min-width:0;color:var(--ink);text-decoration:none}
a.brand:hover{color:var(--ink);text-decoration:none}
.brand .slobal-wordmark,.brand img,.brand .bsep{flex:none}
.brand .bsep{width:1px;height:19px;background:var(--line2)}
.brand .pname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The identity cluster must SHRINK, never widen the page: long emails
   ellipsise on narrow screens (QA-1..5 - every signed-in surface scrolled
   horizontally on a phone because this row had no min-width:0 path). */
.who{margin-left:auto;display:flex;align-items:center;gap:9px;font-size:13px;color:var(--mute);min-width:0;flex:0 1 auto}
.who>span,.who>a{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.who>a.act,.who .themetoggle{flex:none;overflow:visible}
/* The account pill never shrinks. It is NOT given overflow:visible: the group
   draws the 999px outline and has to clip its segments to it, and the
   ellipsis rule one line up already hands a direct child overflow:hidden. */
.who>.slgroup{flex:none}
/* The avatar keeps the flex:none and NOT the overflow:visible above.
   Round 128: that one declaration is what made the corner picture a hard
   square on every relay surface while the hub and AgnCred drew a circle.
   border-radius does not clip a REPLACED element's content on its own - the
   UA sheet's img{overflow:clip} is what does, and overflow:visible here
   overrode it, so the photo painted right out to its own corners with the
   round drawn behind it. (display:flex on the same element is harmless, and
   display:block alone does NOT bring the clip back - both measured.)
   The reset exists to undo the ellipsis rule one line up, whose selector is
   .who>span,.who>a - DIRECT children. Neither avatar is one: both sit inside
   a.whoami. So this element never needed the reset in the first place. */
.who .avatar{flex:none}
/* The identity IS the way to My Account (panel parity): the picture and the
   name, one link, no separate button beside them. 13.5px/600 in a 260px
   maximum - the panel's .acct, value for value. */
.who a.whoami{display:inline-flex;align-items:center;gap:8px;color:var(--mute);font-size:13.5px;font-weight:600;max-width:260px;text-decoration:none}
.who a.whoami:hover{color:var(--ink);text-decoration:none}
.who a.whoami .em{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Under 600px the corner loses the PORTAL segment only. Round 147, tag 01:
   the old rule hid the whole .slbadge class, and once Sign Out joined the
   pill that would have left a phone with no way out of the session. */
@media(max-width:600px){.topbar{gap:8px}.who{gap:7px}.who .slgroup #slhome{display:none}.who .slgroup #slhome+.slbadge{border-left:0}.who .slgroup #slhome+.soform>.slbadge{border-left:0}.brand{gap:8px}.brand .slobal-wordmark{height:18px}.brand .bsep{height:16px}.brand .pname{font-size:14px}}
/* Identical to the Helmsly panel's theme button: the constant ◐ glyph in the
   same rounded-rect chrome as every other header button; current mode lives
   in the tooltip, not the glyph. Panel size: a 34px square, 8px radius. */
.themetoggle{position:relative;background:var(--elev);color:var(--ink);border:1px solid var(--line2);border-radius:8px;width:34px;height:34px;padding:0;font:15px/1 var(--sans);cursor:pointer;transition:border-color .16s,color .16s;display:inline-flex;align-items:center;justify-content:center;flex:none}
.themetoggle:hover{border-color:var(--accent)}
/* Round 135 (goal 5.1, ADR-103) put a four-state dot in the bottom-right
   corner of the theme square. ROUND 147 (fixlist 11, tag 02) RETIRES IT on
   the operator's ruling: a green light on the control that changes the theme
   said nothing a reader could act on, and it read as a notification badge on
   a preference button. The square is a theme toggle again and nothing else.
   The four-state vocabulary is untouched: a surface's status is drawn by a
   content-level .ds-status pill, which is where a state has a label beside
   it. CHROME_STATES and the status parameter are still accepted and still
   validated, so no caller had to change; they simply render nothing.
   ADR-103 carries the amendment.
   The .ds-status-dot rule in the [data-ds] layer stays exactly as it is: it
   still paints the dot inside every content pill (the Helmsly card chip, the
   instance badge). Only the corner override is gone. */
/* The glyph is a child rather than the button's own text node, so the theme
   script can set the title without rewriting the button's contents. */
.themetoggle .tgglyph{display:block;line-height:1}
/* THE ACCOUNT PILL (round 147, fixlist 11, tag 01).
   PORTAL and Sign Out used to be two unrelated controls: a round pill and,
   beside it, a flat 28px ghost button. One is the way home and the other is
   the way out, both belong to the session, and nothing tied them together.
   They are now ONE two-segment pill - a single 999px outline with a hairline
   between the halves - and this block is the whole geometry:
     height 34, radius 999, font 800 12px/1 uppercase, tracking .08em,
     padding 0 13px per segment, 1px hairline divider, hover tint.
   The hub, AgnCred and Home transcribe those numbers; docs/brand.md carries
   the same list in words so a transcriber never has to read this file.
   The group draws the outline and clips it (overflow:hidden), so the segments
   carry no border or radius of their own and the outer rounding follows
   whichever segment is showing WITHOUT a per-page rule. When PORTAL is hidden
   - the portal's own page, round 102 - the adjacency rule drops the divider
   and Sign Out is simply the whole pill.
   Round 101 keeps the group off anonymous doors: there is no session to leave
   and no portal to go to from a sign-in page. */
.slgroup{display:inline-flex;align-items:center;flex:none;border:1px solid var(--line2);border-radius:999px;overflow:hidden}
.slgroup:hover{border-color:var(--accent)}
.slbadge{display:inline-flex;align-items:center;height:32px;padding:0 13px;background:var(--elev);font:800 12px/1 var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--mute);flex:none;text-decoration:none;transition:background-color .16s,color .16s}
.slbadge[hidden]{display:none}
a.slbadge:hover{color:var(--ink);text-decoration:none}
/* The divider, and the one rule that retires it when PORTAL is hidden. */
.slbadge+.slbadge{border-left:1px solid var(--line2)}
.slbadge[hidden]+.slbadge{border-left:0}
/* Sign Out is the quiet half: the bar's own ground, ink text, and it takes
   the tint on hover. PORTAL keeps the raised fill it has always had. */
.slgroup .signout{background:transparent;color:var(--ink)}
.slgroup a.signout:hover{background:var(--elev);color:var(--ink)}
/* X03: Sign Out is a submit button inside a POST form. The form takes no box
   (display:contents), so the button sits in the pill exactly where the anchor
   sat; the button rule clears the UA chrome a button brings (border, margin,
   appearance) and nothing else, so the segment keeps the anchor's size. The
   divider rules are restated for the form wrapper, which is the button's
   parent and so the sibling the adjacency selectors see. */
.soform{display:contents}
button.slbadge{border:0;margin:0;border-radius:0;-webkit-appearance:none;appearance:none;cursor:pointer}
.slbadge+.soform>.slbadge{border-left:1px solid var(--line2)}
.slbadge[hidden]+.soform>.slbadge{border-left:0}
.slgroup button.signout:hover{background:var(--elev);color:var(--ink)}
/* The same form round a page's own sign-out controls: a .btn keeps its own
   look, and a sign-out offered inside a sentence reads as the link it was. */
button.solink{background:none;border:0;margin:0;padding:0;font:inherit;color:var(--sky);cursor:pointer;-webkit-appearance:none;appearance:none}
button.solink:hover{text-decoration:underline}
/* ONE class, TWO elements, and until round 128 one rule for both: the stored
   PHOTO (an img.avatar) and the coloured INITIAL that stands in when there is
   none (a span.avatar). This rule is the geometry they genuinely share - the
   size, the round, and the letter's own colouring and centring, which an image
   simply ignores. Every size is still written here and only here; the rule
   that follows adds no geometry of its own. */
.avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));color:var(--on-accent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px}
/* The PICTURE's own rule, built the way the two surfaces the operator calls
   correct build theirs - the hub's .acctpic (app/ui/v3/tokens.css) and
   AgnCred's img.admin-avatar (apps/web/src/styles.css). Both put the round on
   the image itself with display:block and object-fit:cover, and neither lets
   overflow off the UA's clip. That last part is the whole defect: a replaced
   element paints its content through a border-radius unless the overflow keeps
   it in, so this rule STATES the clip rather than trusting that nobody
   overrides it again. display:block retires the inline-baseline gap; the two
   member grids override it with their own inline-block, because there the
   picture really does sit on a line of text (global-admin.mjs, tenant-admin.mjs
   - the round now comes from HERE and only the flow does not). object-fit:cover
   lives here too, so no caller writes it inline any more. */
img.avatar{display:block;overflow:hidden;object-fit:cover}
/* The identity picture in the TOP BAR is chrome, not content: it sits inside
   the 34px cluster at the panel's own .acctpic size (18px round), so it is
   sized here once rather than by whatever number each surface passed. The
   28px .avatar above stays the size the account page's own forms use.
   The 8px initial is the account page's own ratio (size / 2.3) at 18.
   Round 105 (tags 02-04): these two numbers are now the ONLY place the
   corner picture is sized. The account page used to write them inline as
   well, from its own arithmetic, where the cascade let them win - so WHOAMI
   below emits the picture with no width, height or font-size of its own and
   a test in server.test.mjs holds every surface to that. */
.who .avatar{width:18px;height:18px;font-size:8px}
/* L01: the legal identity footer. Tokens only. It wraps, never clips, and the
   long email and address break rather than push the page wide at 390px. */
.chromefoot{box-sizing:border-box;width:100%;max-width:100%;margin-top:32px;padding:16px calc(16px + env(safe-area-inset-right)) calc(16px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));border-top:1px solid var(--line);color:var(--mute);font-size:12.5px;line-height:1.5;text-align:center}
.chromefoot p{margin:0;overflow-wrap:anywhere}
.chromefoot .footextra{margin-top:6px;overflow-wrap:anywhere}
.chromefoot a{color:var(--mute);text-decoration:underline}
body::before{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(1100px 520px at 50% -10%,rgba(6,182,212,.06),transparent 60%)}
body::after{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;opacity:.022;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
.wrap{max-width:440px;margin:0 auto;padding:56px 22px}
.wide{max-width:960px}
.logo{font-size:38px;text-align:center;margin-bottom:8px;filter:drop-shadow(0 2px 10px rgba(6,182,212,.28))}
/* A hero that is an outline GLYPH rather than the Slobal mark: no brand glow
   behind it, and no inline line box from the mark-sized font. */
.logo.glyph{filter:none;font-size:0;line-height:1}
/* The page title is the one place the brand DISPLAY face speaks (round 127,
   ADR-101). It is an inline face and already condensed, so it takes positive
   tracking: the round 126 -.025em was drawn for Segoe and closes the inline
   strokes up until the counters fill in. */
h1{font-size:23px;margin:0 0 4px;text-align:center;letter-spacing:.02em;font-family:var(--font-display);font-weight:600;text-transform:uppercase}
.sub{color:var(--mute);text-align:center;margin:0 0 26px;font-size:14px;line-height:1.5}
.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:20px}
.btn{display:flex;align-items:center;justify-content:center;gap:9px;width:100%;border:0;border-radius:8px;padding:12px;font:600 15px var(--sans);cursor:pointer;text-decoration:none;margin:0 0 10px;transition:border-color .16s,background .16s,filter .16s}
.btn.sso{background:var(--elev);color:var(--ink);border:1px solid var(--line2)}
.btn.sso:hover{border-color:var(--accent)}
/* The social sign-in row (operator decision 2026-09-24): equal landscape
   rounded rectangles (1.3 to 1, the reference's shape), the
   provider's own logo and nothing else. Each brand's own required surface,
   fixed rather than theme-driven, because Apple's and Google's guidelines
   specify an exact one whatever the page around it looks like. flex-basis 0
   plus aspect-ratio keeps five squares on one row at 390px and shrinks them
   evenly rather than wrapping. */
.sso-row{display:flex;gap:8px;justify-content:center}
.sso-sq{flex:1 1 0;min-width:0;max-width:76px;aspect-ratio:1.3;display:flex;align-items:center;justify-content:center;border-radius:8px;border:1px solid var(--line2);text-decoration:none;transition:border-color .16s,background .16s}
.sso-sq:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.sso-sq.sso-apple svg{transform:translateY(-1px)}
.sso-sq.sso-google{background:var(--brand-white);border-color:var(--brand-google-border)}
.sso-sq.sso-google:hover{border-color:var(--brand-google-border-hover);background:var(--brand-google-bg-hover)}
.sso-sq.sso-apple{background:var(--brand-apple-bg);border-color:var(--brand-dark-edge)}
.sso-sq.sso-apple:hover{background:var(--brand-apple-bg-hover)}
.sso-sq.sso-facebook{background:var(--brand-facebook);border-color:var(--brand-facebook)}
.sso-sq.sso-facebook:hover{background:var(--brand-facebook-hover)}
.sso-sq.sso-microsoft{background:var(--brand-white);border-color:var(--brand-google-border)}
.sso-sq.sso-microsoft:hover{border-color:var(--brand-google-border-hover);background:var(--brand-google-bg-hover)}
.sso-sq.sso-github{background:var(--brand-apple-bg);border-color:var(--brand-dark-edge)}
.sso-sq.sso-github:hover{background:var(--brand-apple-bg-hover)}
.btn.primary{background:var(--accent);color:var(--on-accent)}
.btn.primary:hover{filter:brightness(1.08)}
/* The app-icon slot, same declaration the admin shell carries (round 86, tag
   03): the tenant front door names an app on a button, so its mark comes from
   APP_ICON like every other app mark on the platform rather than from a raw
   FLAG at a hand-picked size. */
.appic{flex:none;display:inline-flex;align-items:center;justify-content:center}
.appic>svg{display:block}
/* ROUND 140 (qa/a11y/axe-gate.mjs, now strict). This line was --faint, which is
   #6B7385 in the light theme: 4.31:1 on the page ground, and this is 12.5px
   text, so AA wants 4.5:1. --mute is #374151, 10.3:1, and is what every other
   secondary line on these pages already uses (.muted2 below, .sub above). No
   new colour, one token swapped for the one beside it.
   The anchors inside it are UNDERLINED for the other half of the same failure:
   axe's link-in-text-block rule wants a link distinguishable from its
   surrounding text by something other than hue, and --sky against --mute is
   1.24:1 of hue and nothing else. */
.muted{color:var(--mute);font-size:12.5px;text-align:center;margin-top:20px;line-height:1.5}
/* ROUND 148. .sub joins the same rule for the same reason: the status page
   carries a mailto in its sub line, and a link that is only --sky against the
   --mute it sits in is 1.73:1 of hue and nothing else, which is what axe's
   link-in-text-block rule refuses. One underline, no new colour. */
.muted a,.muted2 a,.sub a{text-decoration:underline}
.muted2{color:var(--mute)}
input{width:100%;background:var(--bg);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:12px 14px;font:inherit;margin:0 0 10px;transition:border-color .16s,box-shadow .16s}
input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(6,182,212,.14)}
table{width:100%;border-collapse:collapse;font-size:14px;font-variant-numeric:tabular-nums lining-nums}
th,td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th{font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--faint);font-weight:650}
.chip{display:inline-block;font:600 10.5px/1 var(--mono);letter-spacing:.03em;padding:3px 8px;border-radius:4px;border:1px solid var(--line2);margin:1px 2px;color:var(--mute)}
.chip.on{color:var(--accent);border-color:var(--accent)}.chip.off{color:var(--warn);border-color:var(--warn)}
.act{display:inline-flex;align-items:center;gap:6px;background:var(--elev);color:var(--ink);border:1px solid var(--line2);border-radius:6px;padding:5px 10px;min-height:var(--hit-min);font:500 12px var(--sans);cursor:pointer;margin:calc(var(--gap-min) / 2);transition:border-color .14s,background .14s}
.act:hover{border-color:var(--accent)}
.act.danger{color:var(--danger-ink);border-color:var(--danger-line)}
.act.go{background:var(--accent);color:var(--on-accent);border:1px solid var(--accent);font-weight:600}
.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:8px 0}
.pill{font-size:12px;color:var(--mute)}
a{color:var(--sky);text-decoration:none}a:hover{text-decoration:underline;text-underline-offset:2px}
.tier{border:1px solid var(--line);border-radius:8px;padding:16px;margin:0 0 12px;background:var(--bg)}
.tier h3{margin:0 0 2px;font-size:12px;text-transform:uppercase;letter-spacing:.07em;color:var(--accent)}
.tier .p{font-size:21px;font-weight:700;margin:4px 0;letter-spacing:-.02em}
@media(pointer:coarse){.btn{min-height:48px}.act{min-height:44px;padding:10px 14px}input,select{min-height:46px;font-size:16px}}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
/* ROUND 129 (ADR-102): design-system layer, scoped to html[data-ds]. */
/* Theme-dependent ds values. Base = DARK. Origins in the ds spec: BADGES
   ("tinted background with dark text"), TOKENS (--ok #067647, --ok-bg #ECFDF3,
   --warn #B54708, --warn-bg #FFFAEB, --neutral-bg #EFEEE8) and the Slobal
   context's --primary-tint #E3F1F5. The shell has a token for none of them.
   On a dark ground a tint glows, so a dark badge is a transparent ground with
   a coloured border and coloured text, and a dark card takes no shadow at all
   (a shadow on a dark ground is mud). */
html[data-ds]{
  --ds-ok-ink:var(--ok);--ds-ok-bg:transparent;--ds-ok-line:var(--ok);
  --ds-warn-ink:var(--warn);--ds-warn-bg:transparent;--ds-warn-line:var(--warn);
  --ds-neutral-ink:var(--mute);--ds-neutral-bg:transparent;--ds-neutral-line:var(--line2);
  --ds-tile-hover:var(--elev);
  --ds-shadow:none}
@media (prefers-color-scheme:light){html[data-ds]:not([data-theme="dark"]){
  --ds-ok-ink:#067647;--ds-ok-bg:#ECFDF3;--ds-ok-line:#ECFDF3;
  --ds-warn-ink:#B54708;--ds-warn-bg:#FFFAEB;--ds-warn-line:#FFFAEB;
  --ds-neutral-ink:var(--mute);--ds-neutral-bg:#EFEEE8;--ds-neutral-line:#EFEEE8;
  --ds-tile-hover:#E3F1F5;
  --ds-shadow:0 1px 2px rgba(10,14,26,.05)}}
html[data-ds][data-theme="light"]{
  --ds-ok-ink:#067647;--ds-ok-bg:#ECFDF3;--ds-ok-line:#ECFDF3;
  --ds-warn-ink:#B54708;--ds-warn-bg:#FFFAEB;--ds-warn-line:#FFFAEB;
  --ds-neutral-ink:var(--mute);--ds-neutral-bg:#EFEEE8;--ds-neutral-line:#EFEEE8;
  --ds-tile-hover:#E3F1F5;
  --ds-shadow:0 1px 2px rgba(10,14,26,.05)}
/* TYPE ROLES (ds spec TYPE SCALE). The display face speaks once per page, on
   the page title, at the spec's own 46/34 and 1.05. It is NOT uppercased here:
   the casing law puts uppercase in eyebrows and table headers only, and the
   display role is not one of them. .subhead keeps its own voice, so the two
   heading roles step around it rather than overriding a component. */
html[data-ds] body{font-size:15px;line-height:1.6}
html[data-ds] .pagetitle,html[data-ds] h2.pagetitle{font-family:var(--font-display);font-weight:600;font-size:46px;line-height:1.05;letter-spacing:.02em;text-transform:none;color:var(--ink);margin:0 0 12px}
html[data-ds] .ds-h2,html[data-ds] h2:not(.pagetitle):not(.subhead){font-family:var(--font-display);font-weight:600;font-size:26px;line-height:1.25;letter-spacing:-.01em;color:var(--ink);margin:0 0 12px}
html[data-ds] .ds-h3,html[data-ds] h3:not(.subhead){font-family:var(--font-display);font-weight:600;font-size:18px;line-height:1.35;letter-spacing:0;color:var(--ink);margin:0 0 8px}
html[data-ds] .ds-eyebrow{display:block;font:600 11px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--accent);margin:0 0 6px}
html[data-ds] .ds-small{font-size:13px;line-height:1.5;color:var(--mute)}
html[data-ds] .ds-data{font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums;color:var(--ink)}
/* SPEC LIST (ds spec COMPONENTS: label-value pairs, 11px uppercase tracked
   labels, values in body or mono, 1px separators). Both themes get the same
   shape because every colour in it is a theme token; on a phone the label
   stacks over its value rather than squeezing the value to nothing. */
html[data-ds] .ds-spec-list{display:block;margin:0;padding:0;border-top:1px solid var(--line)}
html[data-ds] .ds-spec-row{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;padding:10px 0;border-bottom:1px solid var(--line)}
html[data-ds] .ds-spec-label{flex:none;width:180px;font:600 11px/1.5 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--faint)}
html[data-ds] .ds-spec-value{flex:1;min-width:0;font-size:15px;line-height:1.6;color:var(--ink)}
html[data-ds] .ds-spec-value.ds-data{font:400 13px/1.6 var(--mono);font-variant-numeric:tabular-nums lining-nums}
@media(max-width:720px){html[data-ds] .ds-spec-row{gap:2px}html[data-ds] .ds-spec-label{width:100%}}
/* CARD (ds spec: surface, 1px line border, radius 8, padding 24). Borders over
   shadows: the shadow ceiling rides --ds-shadow, which is the spec's
   0 1px 2px rgba(10,14,26,.05) on light and nothing at all on dark. */
html[data-ds] .ds-card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:24px;box-shadow:var(--ds-shadow)}
/* BADGE (ds spec: pill, 24px, dot indicator). The dot is currentColor, so a
   variant states its colour once and the dot follows. */
html[data-ds] .ds-badge{display:inline-flex;align-items:center;gap:7px;height:24px;padding:0 11px;border-radius:999px;font:600 12px/1 var(--sans);white-space:nowrap;background:var(--ds-neutral-bg);border:1px solid var(--ds-neutral-line);color:var(--ds-neutral-ink)}
html[data-ds] .ds-badge::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:currentColor}
html[data-ds] .ds-badge.ok{background:var(--ds-ok-bg);border-color:var(--ds-ok-line);color:var(--ds-ok-ink)}
html[data-ds] .ds-badge.warn{background:var(--ds-warn-bg);border-color:var(--ds-warn-line);color:var(--ds-warn-ink)}
html[data-ds] .ds-badge.neutral{background:var(--ds-neutral-bg);border-color:var(--ds-neutral-line);color:var(--ds-neutral-ink)}
/* APP TILE, converged on the ds card metrics. Ground, border, radius and focus
   only: the inner layout (the app mark and its .nm label) and the tile's size
   are untouched, so no icon and no click target moves. */
html[data-ds] .tile.app{background:var(--card);border:1px solid var(--line);border-radius:8px}
html[data-ds] .tile.app:hover{border-color:var(--accent);background:var(--ds-tile-hover)}
html[data-ds] .tile.app:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* DENSITY (ds spec SPACING: section 64 desktop, 48 mobile; body measure 640). */
html[data-ds] .ds-section{margin:0 0 64px}
html[data-ds] .ds-section:last-child{margin-bottom:0}
html[data-ds] .pagedesc,html[data-ds] .ds-measure{max-width:640px}
@media(max-width:720px){html[data-ds] .ds-section{margin-bottom:48px}}
/* This layer introduces no animation. The sheets above already kill every
   transition under reduced motion with !important; the tile is named again
   here so the one hover this layer touches states its own answer. */
@media (prefers-reduced-motion:reduce){html[data-ds] .tile.app{transition:none}}
/* ROUND 130: the component vocabulary the account surface needs - buttons,
   form fields, data tables and the badge's bad state. APPENDED, never edited
   into the block above, so the round 129 sheet stays a byte-prefix of this one
   and a reader can still see round 129 whole. Everything below is scoped under
   html[data-ds] exactly as everything above it is, so a surface that has not
   opted in still paints what it painted before.
   One correction to the note four lines up: this round DOES introduce hover
   transitions, on the button and the input. The reduced-motion guard at the
   foot of this block names them, and the sheets above already kill every
   transition under reduced motion with !important, so the guard is a statement
   rather than a fix. */
/* New theme-dependent ds values, in the same three-block pattern the head of
   this layer uses: base carries DARK (this shell's default), the
   prefers-color-scheme:light block that excludes a forced dark theme carries
   LIGHT, and html[data-ds][data-theme="light"] carries it again so the toggle
   wins in both directions. Origins in the ds spec: TOKENS (--bad #B42318,
   --bad-bg #FEF3F2), the Slobal context's --primary-hover #0B5C73, and the
   DATA TABLE line ("hover row tint --paper", which is this shell's --bg).
   The shell carries a token for none of them. It does carry --danger-ink and
   --danger-line, but they are ONE salmon-on-maroon pair used in both themes:
   drawn for the dark ground, and #FF8B7D on white is nowhere near readable. So
   dark keeps the shell's pair and light takes the spec's red, which is what
   the ok and warn trios above already do. */
html[data-ds]{
  --ds-bad-ink:var(--danger-ink);--ds-bad-bg:transparent;--ds-bad-line:var(--danger-line);
  --ds-bad-edge:var(--danger-line);
  --ds-row-hover:var(--elev);
  --ds-input-bg:var(--elev);
  --ds-accent-hover:var(--accent2)}
@media (prefers-color-scheme:light){html[data-ds]:not([data-theme="dark"]){
  --ds-bad-ink:#B42318;--ds-bad-bg:#FEF3F2;--ds-bad-line:#FEF3F2;
  --ds-bad-edge:#B42318;
  --ds-row-hover:var(--bg);
  --ds-input-bg:var(--card);
  --ds-accent-hover:#0B5C73}}
html[data-ds][data-theme="light"]{
  --ds-bad-ink:#B42318;--ds-bad-bg:#FEF3F2;--ds-bad-line:#FEF3F2;
  --ds-bad-edge:#B42318;
  --ds-row-hover:var(--bg);
  --ds-input-bg:var(--card);
  --ds-accent-hover:#0B5C73}
/* BADGE, bad state (ds spec BADGES: "States: ok, warn, bad, neutral, brand").
   Same three properties the ok and warn variants take, so the family reads as
   one set: a tinted ground with dark ink in light, a transparent ground with a
   coloured border and coloured text in dark. */
html[data-ds] .ds-badge.bad{background:var(--ds-bad-bg);border-color:var(--ds-bad-line);color:var(--ds-bad-ink)}
/* BUTTONS (ds spec COMPONENTS: "primary (solid --primary, white text),
   secondary (surface, --primary text, line border, tint on hover), ghost.
   Heights 40 and 32", radius 6 from SPACING AND LAYOUT). Every variant keeps
   the same 1px box, so a row of mixed variants lines up on one baseline and
   nothing shifts when a button changes variant. The solid ground is
   var(--accent) over var(--on-accent), which resolves to teal with white text
   in light and cyan with near-black text in dark: the spec's ban on white on
   cyan is answered by the token pair itself, not by a second rule here. The
   ds law of one primary per view is the page's to keep, not the sheet's.
   Labels are the page's too: sentence case, stating the outcome. */
html[data-ds] .ds-btn,html[data-ds] .ds-btn-primary{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:40px;padding:0 16px;border:1px solid var(--accent);border-radius:6px;background:var(--accent);color:var(--on-accent);font:600 14px/1 var(--sans);text-align:center;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background .16s,border-color .16s,color .16s}
html[data-ds] .ds-btn:hover,html[data-ds] .ds-btn-primary:hover{background:var(--ds-accent-hover);border-color:var(--ds-accent-hover);text-decoration:none}
html[data-ds] .ds-btn-sm{height:32px;padding:0 12px;font-size:13px}
html[data-ds] .ds-btn-secondary{background:var(--card);border-color:var(--line);color:var(--accent)}
html[data-ds] .ds-btn-secondary:hover{background:var(--ds-tile-hover);border-color:var(--accent);color:var(--accent)}
html[data-ds] .ds-btn-ghost{background:transparent;border-color:transparent;color:var(--accent)}
html[data-ds] .ds-btn-ghost:hover{background:var(--ds-tile-hover);border-color:transparent;color:var(--accent)}
/* The destructive variant. Quiet at rest (a hairline and coloured text, no
   filled red shouting from the page) and solid only under the pointer, which
   is the last thing a person crosses on the way to a confirm. The account
   page's own arm-then-confirm mechanics are untouched: this is the skin, the
   two-step stays where it is written. */
html[data-ds] .ds-btn-danger{background:transparent;border-color:var(--ds-bad-edge);color:var(--ds-bad-ink)}
html[data-ds] .ds-btn-danger:hover{background:var(--ds-bad-ink);border-color:var(--ds-bad-ink);color:var(--bg)}
html[data-ds] .ds-btn:disabled,html[data-ds] .ds-btn[aria-disabled="true"]{opacity:.55;pointer-events:none}
/* FORM FIELD (ds spec COMPONENTS: "label above (13px, 600, --heading), input
   40px height, radius 6, line border, focus outline 2px --primary offset 2px,
   help text 13px --muted below. Error state: --bad border, help text states
   what happened and how to fix it."). The label sits above rather than beside,
   which is what the spec asks for and what a phone needs. The ground is --card
   in light and --elev in dark: on light the field sits ON a card and a --card
   ground would need the border to carry the whole shape, on dark a recessed
   ground is the thing that reads as an input at all.
   The shells above give every input outline:none and a 3px cyan ring on focus.
   These rules are more specific and later, so a ds input takes the spec's
   outline instead; the ring is cleared explicitly rather than left to fight. */
html[data-ds] .ds-field{display:flex;flex-direction:column;gap:6px;margin:0 0 16px}
html[data-ds] .ds-label{font:600 13px/1.4 var(--sans);color:var(--ink)}
html[data-ds] .ds-input{width:100%;height:40px;padding:0 12px;border:1px solid var(--line);border-radius:6px;background:var(--ds-input-bg);color:var(--ink);font:400 14px/1.4 var(--sans);transition:border-color .16s}
html[data-ds] select.ds-input{padding-right:8px}
html[data-ds] textarea.ds-input{height:auto;min-height:96px;padding:10px 12px;line-height:1.5;resize:vertical}
html[data-ds] .ds-input:focus{outline:2px solid var(--accent);outline-offset:2px;border-color:var(--accent);box-shadow:none}
html[data-ds] .ds-input::placeholder{color:var(--faint)}
html[data-ds] .ds-help{font:400 13px/1.5 var(--sans);color:var(--mute)}
html[data-ds] .ds-field-error>.ds-input{border-color:var(--ds-bad-ink)}
html[data-ds] .ds-field-error>.ds-help{color:var(--ds-bad-ink)}
/* DATA TABLE (ds spec COMPONENTS: "uppercase tracked 11px headers in --muted,
   1px row borders, hover row tint --paper, mono for identifiers, numbers right
   aligned in mono"). Two hooks for one treatment: .ds-table on a table the
   page writes fresh, and .ds-tablecard on a card that ALREADY holds a table,
   so the workspaces and orders tables on the account page adopt the ds skin by
   gaining one class on their wrapper rather than by having every row rewritten.
   The tracked headers and the mono cells are the two places the casing law
   allows uppercase and the type scale asks for the mono face. */
html[data-ds] .ds-table,html[data-ds] .ds-tablecard>table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums lining-nums}
html[data-ds] .ds-table th,html[data-ds] .ds-tablecard>table th{font:600 11px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--faint);text-align:left;padding:8px 10px;border-bottom:1px solid var(--line)}
html[data-ds] .ds-table td,html[data-ds] .ds-tablecard>table td{font-size:13px;line-height:1.5;color:var(--ink);text-align:left;padding:10px;border-bottom:1px solid var(--line);vertical-align:top}
html[data-ds] .ds-table tbody tr:hover,html[data-ds] .ds-tablecard>table tbody tr:hover{background:var(--ds-row-hover)}
html[data-ds] .ds-mono{font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums}
html[data-ds] .ds-num{text-align:right;font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums}
/* A cell rule beats a class rule on specificity, so the two cell roles are
   named again AS cells; without this a td.ds-mono would keep the body face. */
html[data-ds] .ds-table td.ds-mono,html[data-ds] .ds-tablecard>table td.ds-mono{font:400 13px/1.5 var(--mono)}
html[data-ds] .ds-table td.ds-num,html[data-ds] .ds-tablecard>table td.ds-num{text-align:right;font:400 13px/1.5 var(--mono)}
/* Touch. The shells above already raise their own controls on a coarse
   pointer; the ds controls answer the same law, and the 16px input keeps a
   phone browser from zooming the page on focus. */
@media(pointer:coarse){
  html[data-ds] .ds-btn{min-height:44px}
  html[data-ds] .ds-btn-sm{min-height:40px}
  html[data-ds] .ds-input{min-height:46px;font-size:16px}
  html[data-ds] textarea.ds-input{min-height:96px}}
/* The two hovers this round adds, named for the same reason the tile above is
   named: each states its own answer under reduced motion. */
@media (prefers-reduced-motion:reduce){html[data-ds] .ds-btn,html[data-ds] .ds-input{transition:none}}
/* ROUND 131 fit fix: the display face is wide, and a long one-word pagetitle
   (Customisation) at the 34px phone size is wider than a 390px viewport,
   which fails the relay-fit gate in hub CI (v3-boot-smoke renders every
   section view at 390). Below 720px the title scales with the viewport,
   clamped so it is 34px wherever 34px fits (anything wider than about 440px)
   and shrinks only on narrow phones; overflow-wrap is the safety net so no
   future title can widen the page whatever its length. */
@media (max-width:720px){
  html[data-ds] .pagetitle,html[data-ds] h2.pagetitle{font-size:clamp(26px,7.7vw,34px);overflow-wrap:anywhere}
}
/* ROUND 133 (estate sweep, tag 01): on a phone the topbar cannot hold the
   wordmark, a workspace name, the theme square, an avatar, a person's name
   and Sign Out; both names ellipsized to two characters ("No...", "Sa..."),
   which reads as noise. Below 480px the pagetitle already names the
   workspace and the avatar already names the person, so the topbar drops
   the two text copies and keeps the marks. */
@media (max-width:480px){
  html[data-ds] .brand .pname,html[data-ds] .brand .bsep{display:none}
  html[data-ds] .who .whoami .em{display:none}
}
/* ROUND 141 (finding TEN-001, decision G5): the PRODUCT CARD ROW on the three
   portal variants. Two cards side by side, each stating one product's state and
   the acts available on it, collapsing to one column when there is no room for
   two. APPENDED, so every sheet above stays a byte-prefix of this one.
   Layout only: not one colour, border or radius is declared here - the cards
   themselves are .ds-card and take all of that from the layer above. */
html[data-ds] .ds-cardrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin:0 0 20px;align-items:stretch}
html[data-ds] .ds-cardrow .ds-card{display:flex;flex-direction:column;gap:12px}
/* The acts on a card sit on one line and wrap rather than squeeze: a button
   narrower than its label is a button whose label is a guess. */
html[data-ds] .ds-cardacts{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
/* The card's heading row carries the product name and its state pill together,
   because a state that is not beside the thing it describes describes nothing. */
html[data-ds] .ds-cardhead{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between}
html[data-ds] .ds-cardhead .ds-h3{margin:0}
/* ROUND 141 (finding SLB-005): a COMMAND, on the install page. A command is
   data - it is typed character for character or it does not work - so it takes
   the mono role the design system reserves for data, and it scrolls inside its
   own box rather than widening the page. Every value here is a token. */
html[data-ds] .ds-pre{margin:0;padding:12px 14px;background:var(--elev);border:1px solid var(--line);border-radius:6px;overflow-x:auto;-webkit-overflow-scrolling:touch}
html[data-ds] .ds-pre code{font:400 13px/1.5 var(--mono);white-space:pre;color:var(--ink)}
/* ROUND 141, TWO CONTRAST REPAIRS the strict axe gate found the moment a spec
   list and a linked sentence were put in front of it. Both are pre-existing and
   neither is new to this round; what is new is a page that renders them where
   the gate could see them. Round 140's precedent is followed exactly: a light
   theme role that fails AA takes an existing readable token, no new colour is
   declared, and the committed baselines move with it.

   1. THE SPEC LABEL. --faint is #6B7385, which is 4.83:1 on the card ground and
      only 4.31:1 on the page ground (--bg #F5F4EF) - and these labels are 11px,
      so AA asks 4.5:1 of them. Every spec list on a portal sits on the page
      ground rather than in a card, so every one of them has been failing since
      round 129 with nothing looking at it. --mute (#374151) is the token the
      design system already carries for readable secondary ink in this theme;
      the label keeps its size, its tracking and its uppercase role.
   2. LINKS IN A SENTENCE. A link inside body copy distinguished only by colour
      fails 1.4.1, and the accent against --mute is 1.73:1. Underlining a link
      that sits inside a sentence is the fix, and it is scoped to running text:
      a .ds-btn is not a link in a text block and keeps its own shape. */
html[data-ds] .ds-spec-label{color:var(--mute)}
/* ROUND 141: a left-aligned reading column for a full public page. SHELL's own
   .wrap centres its text, which is right for a sign-in door (one mark, one
   sentence, two buttons) and wrong for a page carrying a stat row, a step rail
   and three sections: half the page would be centred and half left, which reads
   as a layout that broke rather than one that was chosen. The measure is the
   design system's 840 content column. Layout only, no colour. */
html[data-ds] .ds-doc{max-width:840px;margin:0 auto;padding:32px 20px 64px;text-align:left;display:flex;flex-direction:column;gap:24px}
/* SHELL_CSS centres its h1, because its h1 is a sign-in door's one word under a
   mark. A document's title sits at the head of its own column. */
html[data-ds] .ds-doc > h1{margin:0;text-align:left}
html[data-ds] .ds-doc > .ds-eyebrow{margin:0}
html[data-ds] .ds-small a:not(.ds-btn),html[data-ds] .ds-measure a:not(.ds-btn){text-decoration:underline}
/* ROUND 147 (fixlist 11, tag 11): a plain fact list.
   The workspace console priced its five plans in ONE sentence separated by
   middle dots - "Helmsly Local free, Helmsly Relay <three figures>, ..."
   - which is a table flattened into prose and unreadable at any width. This is
   the list that replaces it: no bullet glyph, one fact per row, the same 13px
   muted face the rest of the console's help copy uses. It is the smallest
   component that makes a set of facts a set rather than a paragraph, and the
   density law asks for exactly that.
   Appended at the END of this layer, so the previous round's sheet stays a
   byte-prefix of this one. */
html[data-ds] .ds-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px;font-size:13px;color:var(--mute)}
html[data-ds] .ds-list>li{margin:0}
/* ROUND 147 (fixlist 11, tag 05): A KPI NESTED INSIDE A PRODUCT CARD.
   The operator tagged the two portal cards as dead space. Measured at 1440 the
   AgnCred card's content came out 69px TALLER than the Helmsly card beside it
   once it had real counts to draw, and .ds-cardrow stretches both to the taller
   one, so the hole was under the shorter card and the cause was in the taller.
   The reason is structural: .ds-kpi is a CARD, with the layer's own 24px card
   padding and a 28px mono figure, and the stat row was drawing two of them
   INSIDE another card. A card in a card pays its padding twice.
   The design system's KPI scale is drawn for a stat row under a hero, which is
   where it stays. Nested in a product card it takes the compact scale: the
   figure keeps the mono data role and the label keeps its tracked uppercase,
   and only the box shrinks. Nothing here is a colour and nothing is new
   geometry - it is the same component at the size its container affords. */
   The design system's own words for the stat row are "a horizontal row of 3 to
   4 KPI cards, used directly under heroes and section headers" - so a KPI card
   inside a product card was never the shape this was drawn for. Nested, it
   drops the card entirely and keeps what a KPI IS: a tracked uppercase label
   over a figure in the mono data face. */
   The PHONE law is left alone: under 720px the library stacks a stat row into
   one column, and holding two columns there only wraps "Receipts submitted"
   onto two lines for the same total height. Measured both ways at 390: 211px
   either way, so the law wins. */
html[data-ds] .ds-card[data-product] .ds-stat-row{gap:20px}
html[data-ds] .ds-card[data-product] .ds-kpi{padding:0;gap:2px;border:0;background:transparent;box-shadow:none}
html[data-ds] .ds-card[data-product] .ds-kpi-value{font-size:20px;line-height:1.15}
/* packages/ui/ui-components.css - ROUND 134 (ADR-103): the shared component
   vocabulary the Q4 platform draws from. CANONICAL FILE. Byte-copies live at
   relay/lib/ui-components.css and app/ui/v3/ui-components.css and are written
   by packages/ui/publish.mjs; never edit a copy.

   Every rule is scoped under the [data-ds] ATTRIBUTE with no html tag in
   front, so both the relay shell (html[data-ds], ADR-102) and a panel
   container stamped data-ds match the same selector. The relay appends this
   file at the END of DS_CSS, which keeps the previous sheets a byte-prefix of
   the new ones; nothing above it is edited.

   Colour comes from the ADR-103 token contract and nothing else: the shell's
   --bg --card --elev --line --line2 --ink --mute --faint --accent --on-accent
   --ok --warn --danger-ink --danger-line --sans --mono --display, plus the
   [data-ds] layer's --ds-ok-* --ds-warn-* --ds-neutral-* --ds-bad-*
   --ds-shadow --ds-row-hover --ds-tile-hover. The four values this file needs
   that no token names directly are declared once, immediately below, in the
   same three-block pattern DS_CSS uses: base carries DARK (the shell's
   default), a prefers-color-scheme:light block that excludes a forced dark
   theme carries LIGHT, and [data-ds][data-theme="light"] carries it again so
   the toggle wins in both directions. UI v4 step 8: they are all token
   references now, and the file carries no raw colour at all.

   Shape comes from docs/HELMSLY-UI-v4.md section 4: the four radius tokens
   are declared here too, with the panel's values, because the relay shell
   does not declare them and a var() with no declaration paints a square
   corner. On the panel, tokens.css declares the same four values at :root. */
[data-ds]{
  --r-ctl:6px;--r-card:10px;--r-area:12px;--r-pill:999px;
  --ds-col-bg:var(--elev);
  --ds-mask-ink:var(--faint);
  --ds-stale-bg:transparent;
  --ds-stale-line:var(--line2)}
@media (prefers-color-scheme:light){[data-ds]:not([data-theme="dark"]){
  --ds-col-bg:var(--elev);
  --ds-mask-ink:var(--faint);
  --ds-stale-bg:var(--elev);
  --ds-stale-line:var(--line)}}
[data-ds][data-theme="light"]{
  --ds-col-bg:var(--elev);
  --ds-mask-ink:var(--faint);
  --ds-stale-bg:var(--elev);
  --ds-stale-line:var(--line)}

/* STATUS PILL (.ds-status, ADR-103 decision 6). Four states and no fifth. The
   dot is 8px and takes currentColor, so a state names its colour once. The
   state word rides data-state, so a gate reads the attribute rather than the
   paint. live takes the ok trio, waking the warn trio (it is progress, not a
   fault), last_seen the neutral trio (the value is remembered, not wrong) and
   unknown the bad trio (nothing is known at all). */
[data-ds] .ds-status{display:inline-flex;align-items:center;gap:7px;height:24px;padding:0 11px;border-radius:var(--r-pill);font:600 12px/1 var(--sans);white-space:nowrap;vertical-align:middle;background:var(--ds-neutral-bg);border:1px solid var(--ds-neutral-line);color:var(--ds-neutral-ink)}
[data-ds] .ds-status-dot{flex:none;width:8px;height:8px;border-radius:50%;background:currentColor}
[data-ds] .ds-status-text{min-width:0}
[data-ds] .ds-status-text time{font:inherit;color:inherit;text-decoration:none}
[data-ds] .ds-status-live{background:var(--ds-ok-bg);border-color:var(--ds-ok-line);color:var(--ds-ok-ink)}
[data-ds] .ds-status-waking{background:var(--ds-warn-bg);border-color:var(--ds-warn-line);color:var(--ds-warn-ink)}
[data-ds] .ds-status-last_seen{background:var(--ds-neutral-bg);border-color:var(--ds-neutral-line);color:var(--ds-neutral-ink)}
/* Pre-launch rule 5: red is reserved for destructive actions and errors.
   "Unknown" is the absence of information, not a failure - nothing has gone
   wrong, nobody has said anything yet - so it takes the neutral trio, the same
   ground as "last seen". A red pill here told a reader something was broken
   every time a heartbeat had simply not arrived. */
[data-ds] .ds-status-unknown{background:var(--ds-neutral-bg);border-color:var(--ds-neutral-line);color:var(--ds-neutral-ink)}
/* The one animation in this file: a waking dot breathes, because starting is
   the only state that is going to change on its own. */
@keyframes ds-waking-pulse{0%,100%{opacity:1}50%{opacity:.35}}
[data-ds] .ds-status-waking .ds-status-dot{animation:ds-waking-pulse 1.6s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){
  [data-ds] .ds-status-waking .ds-status-dot{animation:none}}

/* STAT ROW and KPI (.ds-stat-row, .ds-kpi, round 127A styleguide). The classes
   and the markup are the styleguide's; this file adds the state line. */
[data-ds] .ds-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px}
[data-ds] .ds-kpi{display:flex;flex-direction:column;gap:8px;min-width:0}
[data-ds] .ds-kpi-label{font:600 12px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.09em;color:var(--faint)}
[data-ds] .ds-kpi-value{font:500 28px/1.1 var(--mono);font-variant-numeric:tabular-nums lining-nums;color:var(--ink);overflow-wrap:anywhere}
[data-ds] .ds-kpi-value.ds-kpi-words{font:600 22px/1.2 var(--sans);letter-spacing:-.01em}
[data-ds] .ds-kpi-unit{font-size:12.5px;color:var(--mute);margin-left:.35em}
[data-ds] .ds-kpi-note{font:400 13px/1.5 var(--sans);color:var(--mute)}
[data-ds] .ds-kpi-state{display:block}

/* SPLIT SECTION (.ds-split, round 127A styleguide). Words on one side,
   evidence on the other; .ds-split-flip puts the evidence first. */
[data-ds] .ds-split{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center}
[data-ds] .ds-split-copy{min-width:0}
[data-ds] .ds-split-copy>:last-child{margin-bottom:0}
[data-ds] .ds-split-data{min-width:0}
[data-ds] .ds-split-flip .ds-split-data{order:-1}
[data-ds] .ds-split-body{margin:0 0 16px;font-size:14px;line-height:1.6;color:var(--mute);max-width:640px}
[data-ds] .ds-split-link{color:var(--accent);font:600 14px/1.5 var(--sans)}

/* STEP RAIL (.ds-steps, round 127A styleguide). Three to five steps, numbered
   markers joined by a line. data-done and data-current are the hooks a surface
   reads; the marker states its own progress so nothing depends on colour. */
[data-ds] .ds-steps{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:16px;margin:0;padding:0;list-style:none;counter-reset:ds-step}
[data-ds] .ds-step{position:relative;padding-top:40px;font-size:14px;line-height:1.5;color:var(--mute);counter-increment:ds-step;min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-step::before{content:counter(ds-step);position:absolute;top:0;left:0;display:grid;place-items:center;width:28px;height:28px;border:1px solid var(--accent);border-radius:50%;background:var(--card);color:var(--accent);font:400 13px/1 var(--mono);z-index:1}
[data-ds] .ds-step::after{content:"";position:absolute;top:14px;left:28px;right:-16px;height:1px;background:var(--line)}
[data-ds] .ds-step:last-child::after{display:none}
[data-ds] .ds-step[data-done="true"]{color:var(--ink)}
[data-ds] .ds-step[data-done="true"]::before{background:var(--accent);color:var(--on-accent)}
[data-ds] .ds-step[data-current="true"]{color:var(--ink);font-weight:600}
[data-ds] .ds-step[data-current="true"]::before{box-shadow:none}

/* SPEC LIST (.ds-spec-list). Round 129 drew this in DS_CSS, under the selector
   `html[data-ds]`, and round 134 left it there - "the list itself is drawn by
   DS_CSS and is not touched here" - because at that point the only host that
   stamped data-ds was the relay's own <html>.

   Round 135 (WS18) is where that stopped being true. The panel stamps data-ds
   on its STAGE, not on <html>, so `html[data-ds]` matches nothing there and a
   spec list on a panel surface arrived with no layout at all: label and value
   ran together as one string ("Enforced atThe next agent build"). Found by
   looking at the first Rules baseline.

   So the layout is stated here, in the file both estates share, under the
   file's own bare-attribute rule. The declarations are DS_CSS's, value for
   value. On the relay `html[data-ds] .ds-spec-row` is the more specific
   selector and still wins, with the same numbers, so nothing already on a
   relay surface moves by a pixel; on the panel these are the only rules there
   are. DS_CSS keeps its copy: this file is appended to it, and editing what is
   above would break the byte-prefix ADR-102 gates. */
[data-ds] .ds-spec-list{display:block;margin:0;padding:0;border-top:1px solid var(--line)}
[data-ds] .ds-spec-row{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;padding:10px 0;border-bottom:1px solid var(--line)}
[data-ds] .ds-spec-label{flex:none;width:180px;font:600 12px/1.5 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--faint)}
[data-ds] .ds-spec-value{flex:1;min-width:0;font-size:14px;line-height:1.6;color:var(--ink)}
[data-ds] .ds-spec-value.ds-data{font:400 13px/1.6 var(--mono);font-variant-numeric:tabular-nums lining-nums}
@media(max-width:720px){[data-ds] .ds-spec-row{gap:2px}[data-ds] .ds-spec-label{width:100%}}
/* The state line, which is this file's own from round 134. */
[data-ds] .ds-spec-state{display:inline-flex;margin-left:8px;vertical-align:middle}
/* A 64-character digest in a mono value has no break opportunity in it, and
   at 390 it carries the whole page 137px past the viewport. The rule is
   scoped to a row this LIBRARY drew (dsSpecList is the only thing that stamps
   data-state on a spec row), so every spec list already on a relay surface
   keeps the wrapping it has. */
[data-ds] .ds-spec-row[data-state] .ds-spec-value{min-width:0;overflow-wrap:anywhere}

/* KANBAN BOARD (.ds-kanban). Markup and hooks only: data-column and data-card
   are what phase 4 hangs drag on. No drag behaviour lives here. */
[data-ds] .ds-kanban{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(240px,1fr);gap:16px;align-items:start}
[data-ds] .ds-kanban-col{display:flex;flex-direction:column;gap:10px;min-width:0;padding:12px;border:1px solid var(--line);border-radius:var(--r-card);background:var(--ds-col-bg)}
[data-ds] .ds-kanban-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
[data-ds] .ds-kanban-title{font:600 12px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--faint);min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-kanban-count{flex:none;font:400 12px/1.4 var(--mono);font-variant-numeric:tabular-nums lining-nums;color:var(--mute)}
[data-ds] .ds-kanban-card{display:flex;flex-direction:column;gap:8px;min-width:0;padding:12px;border:1px solid var(--line);border-radius:var(--r-card);background:var(--card);box-shadow:none}
[data-ds] .ds-kanban-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
[data-ds] .ds-kanban-cardtitle{font:600 14px/1.35 var(--sans);color:var(--ink);overflow-wrap:anywhere}
[data-ds] .ds-kanban-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font:400 12px/1.4 var(--sans);color:var(--mute);min-width:0}
[data-ds] .ds-kanban-metatext{min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-avatar{flex:none;display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--elev);color:var(--ink);font:600 11.5px/1 var(--sans);letter-spacing:.02em}

/* DASHBOARD PANEL (.ds-panel, ADR-103 decision 7). A panel in a state other
   than live never draws its live body: the state sentence replaces it, and a
   remembered body follows the sentence inside .ds-panel-stale so a reader can
   see at a glance that what is under the sentence is memory. */
[data-ds] .ds-panel{display:flex;flex-direction:column;gap:16px;padding:24px;border:1px solid var(--line);border-radius:var(--r-card);background:var(--card);box-shadow:none;min-width:0}
[data-ds] .ds-panel-head{display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;min-width:0}
[data-ds] .ds-panel-heading{flex:1;min-width:0}
[data-ds] .ds-panel-title{margin:0;font:600 14.5px/1.35 var(--sans);color:var(--ink);overflow-wrap:anywhere}
[data-ds] .ds-panel-actions{flex:none;display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}
[data-ds] .ds-panel-body{min-width:0}
[data-ds] .ds-panel-state{margin:0;font-size:14px;line-height:1.6;color:var(--mute)}
[data-ds] .ds-panel-stale{margin-top:12px;padding-top:12px;border-top:1px solid var(--ds-stale-line);background:var(--ds-stale-bg);opacity:.72;min-width:0}

/* FILE BROWSER (.ds-files). A breadcrumb over a table of entries; the kind
   glyph is one outline path in currentColor with no fill, the family every
   icon in this estate belongs to. */
[data-ds] .ds-files-path{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:0 0 12px;font:400 13px/1.5 var(--sans);color:var(--mute);min-width:0}
[data-ds] .ds-files-crumb{min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-files-crumb:last-child{color:var(--ink);font-weight:600}
[data-ds] .ds-files-sep{flex:none;color:var(--faint)}
[data-ds] .ds-files{width:100%;table-layout:fixed;border-collapse:collapse;font-variant-numeric:tabular-nums lining-nums}
[data-ds] .ds-files th{font:600 12px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--faint);text-align:left;padding:8px 10px;border-bottom:1px solid var(--line)}
[data-ds] .ds-files td{font-size:13px;line-height:1.5;color:var(--ink);padding:10px;border-bottom:1px solid var(--line);vertical-align:top;overflow-wrap:anywhere}
[data-ds] .ds-files tbody tr:hover{background:var(--ds-row-hover)}
[data-ds] .ds-files-name{display:flex;align-items:center;gap:8px;min-width:0}
[data-ds] .ds-files-name a{color:var(--ink)}
[data-ds] .ds-files-glyph{flex:none;color:var(--faint)}
/* A cell rule beats a class rule on specificity, so the two data columns are
   named AS cells; without this the td rule above would keep the body face on a
   size that is meant to be mono. The same correction DS_CSS makes for
   td.ds-mono. The header keeps its own tracked face, so only the width is
   shared between th and td. */
[data-ds] .ds-files th.ds-files-size,[data-ds] .ds-files td.ds-files-size{width:96px;text-align:right}
[data-ds] .ds-files th.ds-files-when,[data-ds] .ds-files td.ds-files-when{width:140px}
[data-ds] .ds-files td.ds-files-size{font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums}
[data-ds] .ds-files td.ds-files-when{font:400 13px/1.5 var(--sans);color:var(--mute)}

/* FLOW CANVAS NODE (.ds-node). The n8n grammar design.md records: a glyph in a
   framed square with the title BELOW it, and a port marker on each side. Hooks
   only: data-node, data-kind, data-state, data-port. */
/* The box is 132px wide at rest (a 44px glyph over a title that wraps at
   112px), and grows only to hold a state pill: a corner pill that is out of
   flow sizes nothing, so "Last seen 4 Sep 14:02" hung 55px off the left edge
   of the frame and would sit over the next node on a canvas. The pill is a
   flowed item ordered to the top right instead, so the node states its own
   width and the instant stays whole. */
[data-ds] .ds-node{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;width:max-content;min-width:132px;max-width:100%;padding:14px 10px;border:1px solid var(--line);border-radius:var(--r-card);background:var(--card);box-shadow:none;text-align:center}
[data-ds] .ds-node:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
[data-ds] .ds-node[data-selected="true"]{border-color:var(--accent);box-shadow:none}
[data-ds] .ds-node-glyph{display:grid;place-items:center;width:44px;height:44px;border:1px solid var(--line2);border-radius:var(--r-card);background:var(--ds-col-bg);color:var(--accent)}
[data-ds] .ds-node-title{max-width:112px;font:600 13px/1.35 var(--sans);color:var(--ink);overflow-wrap:anywhere}
[data-ds] .ds-node-state{order:-1;align-self:flex-end;margin:-6px -4px -4px 0;max-width:100%}
[data-ds] .ds-node-state .ds-status{height:18px;padding:0 7px;font-size:11.5px;gap:5px}
[data-ds] .ds-node-state .ds-status-dot{width:6px;height:6px}
[data-ds] .ds-port{position:absolute;top:50%;width:9px;height:9px;margin-top:-4.5px;border:1px solid var(--line2);border-radius:50%;background:var(--card)}
[data-ds] .ds-port-in{left:-5px}
[data-ds] .ds-port-out{right:-5px}

/* VAULT ROW (.ds-vault-row). The masked value is CSS content and is never
   data: the renderer refuses a value or a secret property outright, so there
   is no path by which a secret reaches this markup. */
[data-ds] .ds-vault-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;min-width:0;padding:12px 0;border-bottom:1px solid var(--line)}
[data-ds] .ds-vault-name{font:400 13px/1.5 var(--mono);color:var(--ink);min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-vault-mask{flex:none;font:400 13px/1.5 var(--mono);letter-spacing:.18em;color:var(--ds-mask-ink)}
[data-ds] .ds-vault-mask::before{content:"\2022\2022\2022\2022\2022\2022\2022\2022"}
[data-ds] .ds-vault-rotated{font:400 13px/1.5 var(--sans);color:var(--mute)}
[data-ds] .ds-vault-consumers{flex-basis:100%;font:400 12px/1.5 var(--sans);color:var(--faint);min-width:0;overflow-wrap:anywhere}

/* QUEUE ROW (.ds-queue-row). L3 of the goal: a queued thing states its
   position AND the reason it is waiting, in full, on the row itself. */
[data-ds] .ds-queue-row{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;min-width:0;padding:12px 0;border-bottom:1px solid var(--line)}
[data-ds] .ds-queue-pos{flex:none;font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums;color:var(--mute)}
[data-ds] .ds-queue-main{flex:1;min-width:0}
[data-ds] .ds-queue-title{display:block;font:600 14px/1.4 var(--sans);color:var(--ink);overflow-wrap:anywhere}
[data-ds] .ds-queue-reason{display:block;font:400 13px/1.5 var(--sans);color:var(--mute);overflow-wrap:anywhere}
[data-ds] .ds-queue-since{flex:none;font:400 13px/1.5 var(--sans);color:var(--faint)}

/* PHONE (max-width 720px). Everything that is a row of columns on a desktop
   becomes one column: a board that scrolls sideways on a phone hides work, and
   a gate that measures overflow would fail it anyway. */
@media (max-width:720px){
  [data-ds] .ds-stat-row{grid-template-columns:1fr}
  [data-ds] .ds-split{grid-template-columns:1fr;gap:24px}
  [data-ds] .ds-split-flip .ds-split-data{order:0}
  [data-ds] .ds-steps{grid-auto-flow:row;gap:12px}
  [data-ds] .ds-step{padding-top:0;padding-left:40px;min-height:28px}
  [data-ds] .ds-step::after{top:28px;bottom:-12px;left:13px;right:auto;width:1px;height:auto}
  [data-ds] .ds-kanban{grid-auto-flow:row;grid-auto-columns:auto}
  [data-ds] .ds-files-size{width:72px}
  [data-ds] .ds-files-when{width:88px;font-size:12px}
  [data-ds] .ds-panel{padding:16px}
  /* flex:none on a desktop keeps the cluster on one line beside the title; on
     a phone that same rule refuses to shrink, so three nowrap controls push
     the page 137px past a 390 viewport. Below 720 the cluster takes its own
     full-width line and wraps inside itself. */
  [data-ds] .ds-panel-actions{flex:1 1 100%;min-width:0;margin-left:0}
  [data-ds] .ds-node{width:100%}
}

/* ===========================================================================
   THE ADR-103 BASE SHAPES (round 135, phase 5 integration).
   ===========================================================================
   The renderers above lean on a layer of base shapes - the type roles, the
   card, the badge, the button and the ds form field - that round 129 drew in
   the RELAY's DS_CSS (relay/lib/shell.mjs) under html[data-ds]. The panel
   never loads that sheet and never stamps the attribute on <html>, so four of
   the five phase 5 surface groups each arrived carrying their own copy of the
   block: dashboards.css, browsers.css, flows.css and WS19's ds-base.css.
   dashboards.css recorded the debt in the file itself ("fold this block into
   packages/ui/ui-components.css so one file draws them for both estates") and
   left it because five groups were in flight at once. They have landed, so it
   is taken here: the rules live ONCE, in the library both estates publish.

   DS_CSS STAYS THE ORIGIN. Every declaration below is that block value for
   value, with the two transforms ADR-103 decision 4 already requires:
   `html[data-ds] X` becomes `[data-ds] X`, and a compound selector that also
   names a bare element (`h2:not(.pagetitle):not(.subhead)`) keeps only its
   `.ds-*` half, because restyling every h2 in a panel view would repaint
   surfaces phase 5 has not migrated. Nothing is retyped and no colour is a raw
   hex. On the relay this file is appended INSIDE DS_CSS's layer, so DS_CSS's
   own `html[data-ds]` rules are both later and more specific and keep winning:
   no relay surface can move because of this section.

   The spec list is NOT repeated here - the library has drawn it since round
   134 (see the SPEC LIST section above), and the four lane copies of it were
   duplicates of this file, not of DS_CSS. */

/* TYPE ROLES. The class forms only. */
[data-ds] .ds-h2{font-family:var(--font-display,var(--sans));font-weight:600;font-size:28px;line-height:1.25;letter-spacing:-.01em;color:var(--ink);margin:0 0 12px}
[data-ds] .ds-h3{font-family:var(--font-display,var(--sans));font-weight:600;font-size:14.5px;line-height:1.35;letter-spacing:0;color:var(--ink);margin:0 0 8px}
[data-ds] .ds-eyebrow{display:block;font:600 12px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--accent);margin:0 0 6px}
[data-ds] .ds-small{font-size:13px;line-height:1.5;color:var(--mute)}
[data-ds] .ds-data{font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums;color:var(--ink)}
[data-ds] .ds-mono{font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums}

/* CARD. Surface, 1px line border, radius 8, padding 24. dsStatRow's KPI cells
   are `.ds-card .ds-kpi`, so without this rule a stat row has no ground. */
[data-ds] .ds-card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:24px;box-shadow:none}

/* BADGE. Pill, 24px, dot indicator in currentColor. */
[data-ds] .ds-badge{display:inline-flex;align-items:center;gap:7px;height:24px;padding:0 11px;border-radius:var(--r-pill);font:600 12px/1 var(--sans);white-space:nowrap;background:var(--ds-neutral-bg);border:1px solid var(--ds-neutral-line);color:var(--ds-neutral-ink)}
[data-ds] .ds-badge::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:currentColor}
[data-ds] .ds-badge.ok{background:var(--ds-ok-bg);border-color:var(--ds-ok-line);color:var(--ds-ok-ink)}
[data-ds] .ds-badge.warn{background:var(--ds-warn-bg);border-color:var(--ds-warn-line);color:var(--ds-warn-ink)}
[data-ds] .ds-badge.neutral{background:var(--ds-neutral-bg);border-color:var(--ds-neutral-line);color:var(--ds-neutral-ink)}
[data-ds] .ds-badge.bad{background:var(--ds-bad-bg);border-color:var(--ds-bad-line);color:var(--ds-bad-ink)}

/* BUTTONS. Heights 40 and 32, radius 6, one 1px box on every variant so a
   mixed row lines up. The law of one primary per view is the page's to keep. */
[data-ds] .ds-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:40px;padding:0 16px;border:1px solid var(--accent);border-radius:var(--r-ctl);background:var(--accent);color:var(--on-accent);font:600 14px/1 var(--sans);text-align:center;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background .16s,border-color .16s,color .16s}
[data-ds] .ds-btn:hover{background:var(--ds-accent-hover);border-color:var(--ds-accent-hover);text-decoration:none}
[data-ds] .ds-btn-sm{height:32px;padding:0 12px;font-size:13px}
[data-ds] .ds-btn-secondary{background:var(--card);border-color:var(--line);color:var(--accent)}
[data-ds] .ds-btn-secondary:hover{background:var(--ds-tile-hover);border-color:var(--accent);color:var(--accent)}
[data-ds] .ds-btn-ghost{background:transparent;border-color:transparent;color:var(--accent)}
[data-ds] .ds-btn-ghost:hover{background:var(--ds-tile-hover);border-color:transparent;color:var(--accent)}
[data-ds] .ds-btn-danger{background:transparent;border-color:var(--ds-bad-edge);color:var(--ds-bad-ink)}
[data-ds] .ds-btn-danger:hover{background:var(--ds-bad-ink);border-color:var(--ds-bad-ink);color:var(--bg)}
[data-ds] .ds-btn:disabled,[data-ds] .ds-btn[aria-disabled="true"]{opacity:.55;pointer-events:none}

/* FORM FIELD. Label above, 40px control, radius 6, help text under. */
[data-ds] .ds-field{display:flex;flex-direction:column;gap:6px;margin:0 0 16px}
[data-ds] .ds-label{font:600 13px/1.4 var(--sans);color:var(--ink)}
[data-ds] .ds-input{width:100%;height:40px;padding:0 12px;border:1px solid var(--line);border-radius:var(--r-ctl);background:var(--ds-input-bg);color:var(--ink);font:400 14px/1.4 var(--sans);transition:border-color .16s}
[data-ds] select.ds-input{padding-right:8px}
[data-ds] textarea.ds-input{height:auto;min-height:96px;padding:10px 12px;line-height:1.5;resize:vertical}
[data-ds] .ds-input:focus{outline:2px solid var(--accent);outline-offset:2px;border-color:var(--accent);box-shadow:none}
[data-ds] .ds-input::placeholder{color:var(--faint)}
[data-ds] .ds-help{font:400 13px/1.5 var(--sans);color:var(--mute)}
[data-ds] .ds-field-error>.ds-input{border-color:var(--ds-bad-ink)}
[data-ds] .ds-field-error>.ds-help{color:var(--ds-bad-ink)}
/* THE SHARED ERROR CARD (pre-launch section 1, item 5). One card, three layers:
   what the reader does next, the service state under it, and the diagnostic
   reference last. Only the card's edge carries the bad ink - the sentence a
   person acts on stays in the body colour, because a paragraph of red reads as
   an alarm rather than as an instruction. */
[data-ds] .ds-error-card{border-color:var(--ds-bad-ink);display:flex;flex-direction:column;gap:8px}
[data-ds] .ds-error-card>.ds-help:last-child{color:var(--faint);font-variant-numeric:tabular-nums}

/* THE PANEL BODY STACK. This one is the library's own, not a DS_CSS lift:
   dashboards.css wrote it because a run of evidence shapes inside one panel
   body (a stat row over a list, a spec list over a control cluster) carries no
   gap of its own, and the shape it spaces is `.ds-panel-body`, which this file
   draws. A surface that wants a different rhythm says so on its own root with
   one more class in the selector, the way market.css does. */
[data-ds] .ds-panel-body>*+*{margin-top:16px}

@media (max-width:720px){
  /* A 16px step between panels reads as a hairline of air on a 390px screen,
     where the panels are already edge to edge. */
  [data-ds] .ds-panel-body>*+*{margin-top:12px}
}
@media(pointer:coarse){
  [data-ds] .ds-btn{min-height:44px}
  [data-ds] .ds-btn-sm{min-height:40px}
  [data-ds] .ds-input{min-height:46px;font-size:16px}
  [data-ds] textarea.ds-input{min-height:96px}}
@media (prefers-reduced-motion:reduce){[data-ds] .ds-btn,[data-ds] .ds-input{transition:none}}

/* ===========================================================================
   ROUND 147. The shapes the four hub surface lanes build on.
   ===========================================================================
   Everything below is APPENDED. Not one byte above this line moves, which is
   what keeps every existing SHELL_CSS and ADMIN_CSS a byte-prefix of the new
   ones (ADR-102's gate) and every existing capture in qa/visual unmoved.

   Six shapes: the data table, the toolbar above it, the tree, the switch, the
   usage meter and the two-pane file split, plus the chips, due date, faces and
   add control the Kanban card gained. No new colour: every value is a token,
   and the four raw values at the head of this file are still the only ones. */

/* Visually hidden, for a name a reader does not need and a screen reader does:
   a table's caption, the label on a search field whose magnifier says what it
   is. clip-path rather than the deprecated clip, and white-space:nowrap so a
   long name cannot be read out with its words run together. */
[data-ds] .ds-vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* THE CELL CONTRACT (.ds-col-*). One description of a column, shared by
   .ds-table and dsFileBrowser's columns override, so the two cannot drift.
   Every rule names the CELL as well as the class: `[data-ds] .ds-table td` is
   a more specific selector than a bare class, so a class rule alone would lose
   the font to it. The same correction the file already makes for
   td.ds-files-size.

   A pinned width arrives as the custom property --ds-col-w rather than as an
   inline width, because the inline-style law (docs/ui-contract.md) fails a px
   width in markup and passes a custom property. */
[data-ds] .ds-table th.ds-col-right,[data-ds] .ds-table td.ds-col-right,[data-ds] .ds-files th.ds-col-right,[data-ds] .ds-files td.ds-col-right{text-align:right}
[data-ds] .ds-table td.ds-col-mono,[data-ds] .ds-files td.ds-col-mono{font:400 13px/1.5 var(--mono);font-variant-numeric:tabular-nums lining-nums}
[data-ds] .ds-table th.ds-col-w,[data-ds] .ds-table td.ds-col-w,[data-ds] .ds-files th.ds-col-w,[data-ds] .ds-files td.ds-col-w{width:var(--ds-col-w)}

/* DATA TABLE (.ds-table). The design system's table, said once: 11px uppercase
   tracked header in --faint, 1px row lines, hover tint, numbers right aligned
   in mono where the column says so. The row height is PINNED at 40 rather than
   derived from the line box, so two tables of different content line up.

   .ds-table-wrap is the reason a wide table never takes the page sideways with
   it: the scroll happens in the wrap, and the page's own overflow is untouched
   (a gate that measures document width would fail the alternative). */
[data-ds] .ds-table-wrap{max-width:100%;overflow-x:auto}
[data-ds] .ds-table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums lining-nums}
[data-ds] .ds-table th{height:32px;padding:0 12px;border-bottom:1px solid var(--line);font:600 12px/1.4 var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--faint);text-align:left;vertical-align:middle;white-space:nowrap}
[data-ds] .ds-table td{height:40px;padding:0 12px;border-bottom:1px solid var(--line);font-size:13px;line-height:1.5;color:var(--ink);vertical-align:middle;overflow-wrap:anywhere}
[data-ds] .ds-table tbody tr:hover{background:var(--ds-row-hover)}
[data-ds] .ds-table tbody tr.is-selected{background:var(--ds-tile-hover)}
[data-ds] .ds-table-empty td{color:var(--mute)}
[data-ds] .ds-table-empty:hover{background:transparent}
[data-ds] .ds-table th.ds-table-pickcol,[data-ds] .ds-table td.ds-table-pickcol{width:40px;padding:0 0 0 12px}
[data-ds] .ds-table-pick{width:16px;height:16px;margin:0;accent-color:var(--accent);cursor:pointer}
[data-ds] .ds-table-pick:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* TOOLBAR (.ds-toolbar). Actions left, search in the middle, actions right.
   The search control is 32 rather than the field's 40: it sits in a row of
   32px buttons and a 40px box in that row reads as a mistake. */
[data-ds] .ds-toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;min-width:0;margin:0 0 12px}
[data-ds] .ds-toolbar-left,[data-ds] .ds-toolbar-right{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}
[data-ds] .ds-toolbar-right{margin-left:auto}
[data-ds] .ds-search{position:relative;display:flex;align-items:center;flex:1 1 220px;min-width:0;max-width:360px}
[data-ds] .ds-search-glyph{position:absolute;left:10px;color:var(--faint);pointer-events:none}
[data-ds] .ds-search .ds-input{height:32px;padding:0 12px 0 32px;font-size:13px}

/* TREE (.ds-tree). Depth rides --ds-tree-depth on the item rather than nested
   padding on the lists, so the hover and selection tints run the full width of
   the column at every depth instead of stepping in with the text. */
[data-ds] .ds-tree,[data-ds] .ds-tree-group{margin:0;padding:0;list-style:none;min-width:0}
[data-ds] .ds-tree-row{display:flex;align-items:center;gap:8px;min-width:0;height:32px;padding-right:8px;padding-left:calc(8px + var(--ds-tree-depth,0) * 16px);border-radius:var(--r-ctl);font:400 13px/1.4 var(--sans);color:var(--ink)}
[data-ds] .ds-tree-row:hover{background:var(--ds-row-hover)}
[data-ds] .ds-tree-item.is-selected>.ds-tree-row{background:var(--ds-tile-hover);font-weight:600}
[data-ds] .ds-tree-toggle{flex:none;display:grid;place-items:center;width:20px;height:20px;padding:0;border:0;border-radius:var(--r-ctl);background:transparent;color:var(--faint);cursor:pointer;transition:transform .16s}
[data-ds] .ds-tree-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
[data-ds] .ds-tree-leaf{cursor:default}
[data-ds] .ds-tree-item[aria-expanded="true"]>.ds-tree-row>.ds-tree-toggle{transform:rotate(90deg)}
[data-ds] .ds-tree-glyph{flex:none;display:grid;place-items:center;color:var(--faint)}
[data-ds] .ds-tree-item[data-locked="true"]>.ds-tree-row>.ds-tree-glyph{color:var(--ds-warn-ink)}
[data-ds] .ds-tree-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
[data-ds] .ds-tree-count{flex:none;display:inline-flex;align-items:center;height:18px;padding:0 7px;border-radius:var(--r-pill);background:var(--ds-neutral-bg);color:var(--ds-neutral-ink);font:400 12px/1 var(--mono);font-variant-numeric:tabular-nums lining-nums}
[data-ds] .ds-tree-meta{flex:none;margin-left:auto;padding-left:8px;font:400 12px/1.5 var(--sans);color:var(--mute)}

/* SWITCH (.ds-switch). Track 36 by 20, thumb 16, travel 16. The input is the
   focusable thing and carries the checked state; the track is painted from it
   with a sibling selector, so the ring is driven by :focus-visible on a real
   control rather than by a class a script would have to keep in step. */
[data-ds] .ds-switch{position:relative;display:flex;align-items:center;gap:16px;min-width:0;padding:8px 0;cursor:pointer}
[data-ds] .ds-switch[data-disabled="true"]{opacity:.55;cursor:not-allowed}
[data-ds] .ds-switch-text{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}
[data-ds] .ds-switch-label{font:600 13px/1.4 var(--sans);color:var(--ink);overflow-wrap:anywhere}
[data-ds] .ds-switch-help{font:400 13px/1.5 var(--sans);color:var(--mute);overflow-wrap:anywhere}
[data-ds] .ds-switch input{position:absolute;width:1px;height:1px;margin:0;opacity:0;pointer-events:none}
[data-ds] .ds-switch-track{flex:none;position:relative;display:block;width:36px;height:20px;border:1px solid var(--line2);border-radius:var(--r-pill);background:var(--ds-neutral-bg);transition:background .16s,border-color .16s}
[data-ds] .ds-switch-thumb{position:absolute;top:1px;left:1px;width:16px;height:16px;border-radius:50%;background:var(--card);box-shadow:none;transition:transform .16s}
[data-ds] .ds-switch input:checked~.ds-switch-track{background:var(--accent);border-color:var(--accent)}
[data-ds] .ds-switch input:checked~.ds-switch-track .ds-switch-thumb{background:var(--on-accent);transform:translateX(16px)}
[data-ds] .ds-switch input:focus-visible~.ds-switch-track{outline:2px solid var(--accent);outline-offset:2px}

/* METER (.ds-meter). The figure is words first, because a bar on its own is a
   picture of a number nobody can read. The track is 8px and is hidden from
   assistive technology: it says the same thing the head and the legend say. */
[data-ds] .ds-meter{display:flex;flex-direction:column;gap:8px;min-width:0}
[data-ds] .ds-meter-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;min-width:0}
[data-ds] .ds-meter-label{font:600 13px/1.4 var(--sans);color:var(--ink);min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-meter-figure{flex:none;color:var(--mute)}
[data-ds] .ds-meter-track{display:flex;width:100%;height:8px;border-radius:var(--r-pill);background:var(--ds-neutral-bg);overflow:hidden}
[data-ds] .ds-meter-seg{display:block;width:var(--ds-seg,0%);min-width:0}
[data-ds] .ds-meter-legend{display:flex;flex-wrap:wrap;gap:8px 20px;margin:4px 0 0;padding:0;list-style:none;min-width:0}
[data-ds] .ds-meter-item{display:flex;align-items:center;gap:8px;min-width:0;font:400 13px/1.5 var(--sans);color:var(--mute)}
[data-ds] .ds-meter-swatch{flex:none;width:10px;height:10px;border-radius:var(--r-ctl)}
[data-ds] .ds-meter-name{min-width:0;overflow-wrap:anywhere}
[data-ds] .ds-meter-value{flex:none;color:var(--ink)}
[data-ds] .ds-meter-ok{background:var(--ds-ok-ink)}
[data-ds] .ds-meter-warn{background:var(--ds-warn-ink)}
[data-ds] .ds-meter-bad{background:var(--ds-bad-ink)}
[data-ds] .ds-meter-neutral{background:var(--ds-neutral-ink)}
[data-ds] .ds-meter-brand{background:var(--accent)}

/* KANBAN CARD, ENRICHED. Chips under the title, a due date and a face stack in
   the foot, an add control at the column's own foot. Every one of them is
   drawn only when the card or the column asks for it, so a board that passes
   none of the new options paints exactly what it painted before. */
[data-ds] .ds-kanban-chips{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
[data-ds] .ds-chip{display:inline-flex;align-items:center;height:22px;padding:0 9px;border:1px solid var(--ds-neutral-line);border-radius:var(--r-pill);background:var(--ds-neutral-bg);color:var(--ds-neutral-ink);font:600 11.5px/1 var(--sans);white-space:nowrap}
[data-ds] .ds-chip-ok{background:var(--ds-ok-bg);border-color:var(--ds-ok-line);color:var(--ds-ok-ink)}
[data-ds] .ds-chip-warn{background:var(--ds-warn-bg);border-color:var(--ds-warn-line);color:var(--ds-warn-ink)}
[data-ds] .ds-chip-bad{background:var(--ds-bad-bg);border-color:var(--ds-bad-line);color:var(--ds-bad-ink)}
[data-ds] .ds-chip-neutral{background:var(--ds-neutral-bg);border-color:var(--ds-neutral-line);color:var(--ds-neutral-ink)}
[data-ds] .ds-chip-brand{background:var(--ds-tile-hover);border-color:var(--accent);color:var(--accent)}
[data-ds] .ds-kanban-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}
[data-ds] .ds-kanban-due{display:inline-flex;align-items:center;gap:6px;min-width:0;font:400 12px/1.4 var(--mono);font-variant-numeric:tabular-nums lining-nums;color:var(--mute);overflow-wrap:anywhere}
[data-ds] .ds-kanban-due time{font:inherit;color:inherit;text-decoration:none}
[data-ds] .ds-kanban-dueglyph{flex:none}
[data-ds] .ds-kanban-faces{display:inline-flex;align-items:center;margin-left:auto;padding-left:8px}
[data-ds] .ds-kanban-face{flex:none;display:grid;place-items:center;width:22px;height:22px;margin-left:-6px;border:1px solid var(--card);border-radius:50%;background:var(--elev);color:var(--ink);font:600 11.5px/1 var(--sans);overflow:hidden}
[data-ds] .ds-kanban-face:first-child{margin-left:0}
[data-ds] .ds-kanban-face img{display:block;width:100%;height:100%;object-fit:cover}
[data-ds] .ds-kanban-add{display:flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border:1px dashed var(--line2);border-radius:var(--r-card);background:transparent;color:var(--mute);font:600 12px/1 var(--sans);cursor:pointer;transition:background .16s,border-color .16s,color .16s}
[data-ds] .ds-kanban-add:hover{border-color:var(--accent);background:var(--ds-tile-hover);color:var(--accent)}
[data-ds] .ds-kanban-add:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* FILE SPLIT (.ds-files-split). A 260px tree column, a 1px divider, and the
   browser in the rest. The state sentence, when the split is not live, spans
   both columns rather than sitting in the tree's 260. */
[data-ds] .ds-files-split{display:grid;grid-template-columns:260px minmax(0,1fr);min-width:0}
[data-ds] .ds-files-tree{min-width:0;padding:0 16px 0 0;border-right:1px solid var(--line)}
[data-ds] .ds-files-main{min-width:0;padding:0 0 0 16px}
[data-ds] .ds-files-split>.ds-panel-state{grid-column:1/-1}

/* The two column drops. 640 and 900 are the only two hideBelow answers, so
   they are the only two the sheet can be asked for. */
@media (max-width:899px){
  [data-ds] .ds-col-hide-900{display:none}
  [data-ds] .ds-files-split{grid-template-columns:minmax(0,1fr)}
  [data-ds] .ds-files-tree{padding:0 0 16px;border-right:0;border-bottom:1px solid var(--line)}
  [data-ds] .ds-files-main{padding:16px 0 0}
}
@media (max-width:639px){
  [data-ds] .ds-col-hide-640{display:none}
  /* The toolbar takes three lines below 640: the left cluster, the search
     field at full width, then the right cluster. The DOM is already in that
     order, so each part asking for a whole line is the whole rule. */
  [data-ds] .ds-toolbar{gap:8px}
  [data-ds] .ds-toolbar-left,[data-ds] .ds-toolbar-right{flex:1 1 100%}
  [data-ds] .ds-toolbar-right{margin-left:0}
  [data-ds] .ds-search{flex:1 1 100%;max-width:none}
}

/* The coarse-pointer floor (docs/ui-contract.md rule 6): the hit area grows,
   the painted face does not. A surface that puts one of these shapes inside
   its own responsive layer extends THESE selectors rather than restating the
   numbers: .ds-switch, .ds-tree-row, .ds-table td and .ds-table-pick. */
@media (pointer:coarse){
  [data-ds] .ds-switch{min-height:44px}
  [data-ds] .ds-tree-row{min-height:44px}
  [data-ds] .ds-table td{height:44px}
  [data-ds] .ds-table-pick{width:20px;height:20px}
  [data-ds] .ds-search .ds-input{min-height:40px;font-size:16px}
}
@media (prefers-reduced-motion:reduce){
  [data-ds] .ds-tree-toggle,[data-ds] .ds-switch-track,[data-ds] .ds-switch-thumb,[data-ds] .ds-kanban-add{transition:none}
}
