/* Reset, body shell, and the design tokens (:root) every other sheet uses. */
/* Hide Turbo's top-of-page progress bar. Almost every interaction here is
   a Turbo Stream response (chat, plug toggle, panel actions) that completes
   in well under the 500ms delay before the bar appears, OR is a same-page
   stream swap that doesn't need a navigation cue. The brief flash on slower
   responses reads as "page reloading" — exactly the impression we don't
   want when the company stays mounted. */
.turbo-progress-bar {
  display: none !important;
}

/* Self-hosted Inter from app/assets/fonts — Propshaft serves it digested, no
   build step and no third-party request on first paint. One face carries
   everything, headings included: a second, characterful cut for h1/h2 was
   dropped 2026-08-14, since the brand brief scales headings by size, weight and
   tracking and never named a display face. Subsets are unicode-range gated, so
   an English page fetches latin alone and the Cyrillic cut lands only when the
   content actually contains it — agent-written chat can be any language. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/inter-latin-496a588b.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: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/inter-latin-ext-5a6754fc.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/inter-cyrillic-a10feb6d.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* The italic cut is not optional decoration: the wordmark is bold italic text
   and chat renders italic prose. Without it the browser slants the upright face
   mechanically, which is exactly what the system font never did. Declared to
   900, as far as the files go: the wordmark asks for 800 and the invite card's
   mark is Black 900, and a range stopping at 700 quietly drew both at 700. */
@font-face {
  font-family: "Inter";
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/inter-italic-latin-80cac1ae.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: "Inter";
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/inter-italic-latin-ext-7db1c008.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/inter-italic-cyrillic-c6e5a17f.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* The chrome palette. Light is the only theme (2026-07-27); the dark ladder
   it replaced lives in git history, not in a selector nothing sets. SCREEN
   surfaces (terminal, noVNC display, desk-game canvases) are intentionally
   NOT themed — a monitor reads dark on any page — so their #0b0f19 / #06090f
   / #000 stay literal in the sheets. */
:root {
  /* White page (2026-08-24, was the pale blue #f4f7fb). Cards share the
     surface now, so separation is carried by hairlines and shadow, and
     the panel/deep steps below are the only tinted grounds left. */
  --bg: #ffffff;
  --bg-card: #ffffff;
  --bg-card-hover: #eef2f8;
  --bg-panel: #eef2f7;
  --text: #1e293b;
  --text-muted: #64748b;
  --accent: #566eb4;
  --accent-hover: #4659a8;
  --green: #16a34a;
  --amber: #d97706;
  --red: #dc2626;
  /* THE border of the company chrome; -strong is the emphasized variant.
     Slate ink at low alpha on light (was slate at low alpha on dark). Other
     alphas of the same slate stay literal — one-offs, not the system line. */
  --hairline: rgba(15, 23, 42, 0.1);
  --hairline-strong: rgba(15, 23, 42, 0.16);
  /* Fill ladders for blocks sitting on the page surface. On light these are
     DARK (slate-ink) overlays — the dark theme's white overlays would vanish.
     Neutral: soft = resting, raised = hover/focus/chip. Cool slate/accent:
     soft = whisper fill, chip = badges/pills, active = the selected row.
     Pick a step; don't mint a sixth. */
  --fill-soft: rgba(15, 23, 42, 0.035);
  --fill-raised: rgba(15, 23, 42, 0.06);
  /* What stands where something is still loading — a slide, a picture. Far
     under soft: a placeholder is a white page about to arrive, and at 3.5%
     it read as a grey block that flashed on every reload. */
  --fill-wait: rgba(15, 23, 42, 0.015);
  --tint-soft: rgba(15, 23, 42, 0.04);
  --tint-chip: rgba(86, 110, 180, 0.08);
  --tint-active: rgba(86, 110, 180, 0.1);
  /* The warn tint, the amber sibling of the two above: the ground a handback
     mark sits on, so the ⏸ chip is a different OBJECT from a receipt and not
     just the same one in another colour. */
  --tint-warn: rgba(217, 119, 6, 0.1);
  /* Spacing scale — step N is N × 4px. Every new padding, gap and margin picks
     a step. Off-grid one-offs (0.35rem, 0.85rem, 0.95rem, 1.1rem) are most of
     what makes a layout read hand-assembled rather than designed; the existing
     ones migrate when their own rule is next touched, not in a sweep. */
  /* Interface face everywhere; the display face is scoped to public headings
     in 10_marketing.css. Both fall back to the system stack that used to be
     the whole answer, so a font that fails to load costs nothing. */
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --radius: 1rem;
  --radius-sm: 0.5rem;
  --radius-pill: 999px;
  /* An overlay's widest: 94% of the screen less a landscape iPhone's notch
     on each side (viewport-fit=cover hands those edges to the page). */
  --overlay-w: calc(94vw - env(safe-area-inset-left) - env(safe-area-inset-right));
  /* The home indicator's clearance, for whatever is pinned to the bottom. */
  --safe-bottom: env(safe-area-inset-bottom);
  --safe-top: env(safe-area-inset-top);
  /* An overlay's tallest: the screen less half a rem at the head and at the
     foot, the same breath it keeps at its sides on a phone. */
  --overlay-h: calc(100dvh - 2 * var(--space-2) - var(--safe-top) - var(--safe-bottom));
  /* Motion scale — three steps, the same way spacing picks a --space-N.
     fast: hover, focus, colour and border. base: things that move or appear.
     slow: context shifts (overlays, drawers). The house curve is the easing
     for anything that should feel expensive; entering decelerates. */
  --dur-fast: 120ms;
  --dur: 220ms;
  --dur-slow: 400ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* A drawer's slide, iOS's own sheet curve: it leaves at a finite speed.
     The house curve covered a quarter of a phone's width in the first frame
     of the rail's 400ms (93px of 390), and read as a jerk with a crawl
     after it (2026-10-17). */
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --topbar-height: 3.5rem;
  /* Company right-rail track width — the live surfaces, over the key
     metric. It was sized when the rail also carried the accounts panel;
     that moved to the left column (2026-08-18) and the chat took the slack.
     Both widths are rounded whole, the vw middle included: only the ends
     were, and between them the columns split on a fraction (2026-10-14). */
  --rail-width: round(clamp(19em, 24vw, 29em), 1px);
  /* The left column: the agent header, the task list, and the spotlight
     panels that undock over them. The floor is what the header's energy row
     needs on ONE line (amount + top-up button beside the avatar gutter) —
     below it the row has to wrap, and the header stops reading as two floors.
     Still narrower than the rail, so the chat keeps the middle. */
  --stage-width: round(clamp(17em, 19vw, 26em), 1px);
  /* The cover art, named once: the landing's fold and the doorway pages
     (sign-in, the invite gate) are the same picture, and `rake hero_image`
     writes a new one under a new name. */
  --cover-art: url("/assets/marketing/hero-cover-drive-day-86ab1813.webp");
}
/* A phone keeps half a rem each side of an overlay, not 3vw: the screen is
   the scarce part there (owner, 2026-10-15). */
@media (max-width: 40em) {
  :root { --overlay-w: calc(100vw - 1rem - env(safe-area-inset-left) - env(safe-area-inset-right)); }
}

/* ===== The drawn marks =====
   One set, one geometry, everywhere the interface needs a glyph. They replace
   platform emoji, which drew themselves differently on every machine, brought
   their own colour, and sat at their own optical size next to our type.
   Masked rather than <img>, so the ink is `currentColor` (the mark is as quiet
   as the text it rides with) and the size is in em. Markup, not a ::before, so
   the geometry is written once and every surface points at the same class.

   One drawing system, so a row of them reads as one set (2026-09-15 sweep,
   which found six stroke weights and ink from 10 to 20 units wide):
     - 24-square viewBox, ink inside 4..20 — a circle or a square fills it, a
       cross or a chevron sits a shade inside, which is what makes them look
       the same size rather than measure the same;
     - stroke 1.8, round caps and joins, no fill. The exceptions are written
       where they are taken: `send` (reversed out of a disc), the two solid
       glyphs (`paused`, `caret`) and `energy`, which is the currency mark and
       keeps its own box;
     - radius 2 on a rectangle's corners, and nothing drawn so finely that it
       survives only above 22px — every mark is checked at 16. */
:root {
  --mark-model: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4 19 8v8l-7 4-7-4V8z'/%3E%3C/g%3E%3C/svg%3E");
  --mark-notes: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 6.3A1.8 1.8 0 0 1 8.8 4.5h6.4A1.8 1.8 0 0 1 17 6.3V19.6L12 16.2l-5 3.4z'/%3E%3C/g%3E%3C/svg%3E");
  --mark-clip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 11v4.6a4.2 4.2 0 0 1-8.4 0V7.8a2.8 2.8 0 0 1 5.6 0v7.6a1.4 1.4 0 0 1-2.8 0V8.4'/%3E%3C/g%3E%3C/svg%3E");
  /* Pause bars are drawn CHUNKY and solid: the thin full-height outline pair
     this replaced read as chopsticks at 1rem. Narrower than the outline marks
     around them because a filled shape carries more ink at the same width. */
  --mark-paused: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='6.4' y='5' width='4.2' height='14' rx='1.6'/%3E%3Crect x='13.4' y='5' width='4.2' height='14' rx='1.6'/%3E%3C/svg%3E");
  /* A keyboard: the state that wants the reader to type. The pause bars said
     "stopped", the ringed question mark read as confusion, and the reply
     arrow that followed pointed the wrong way — back at the agent
     (2026-09-25). Four short keys over a spacebar with a key at each end,
     all pills and none round: three dots over a bar was a face, four dots
     over a bar a paw, and two rows of keys blurred into one at the size it
     is drawn. */
  --mark-keyboard: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.6' y='5.6' width='18.8' height='12.8' rx='2.2'/%3E%3Cpath d='M5.8 9.8h.8M9.67 9.8h.8M13.53 9.8h.8M17.4 9.8h.8M5.8 14.2h.8M9.6 14.2h4.8M17.4 14.2h.8'/%3E%3C/g%3E%3C/svg%3E");
  --mark-waiting: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4a8 8 0 1 1 0 16 8 8 0 0 1 0-16z'/%3E%3Cpath d='M12 7.4V12l3.2 1.9'/%3E%3C/g%3E%3C/svg%3E");
  --mark-quote: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 18v-5c0-4 2-7 6-8l1 2c-2 1-3 2-3 4h3v7zm9 0v-5c0-4 2-7 6-8l1 2c-2 1-3 2-3 4h3v7z'/%3E%3C/svg%3E");
  --mark-alarm: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5a7 7 0 1 1 0 14 7 7 0 0 1 0-14z'/%3E%3Cpath d='M12 8.6V12l2.2 1.4'/%3E%3Cpath d='M4 5.8 6.8 3.4M20 5.8l-2.8-2.4'/%3E%3Cpath d='M7.4 17.6 6 19.4M16.6 17.6l1.4 1.8'/%3E%3C/g%3E%3C/svg%3E");
  --mark-lock: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5.6' y='10.4' width='12.8' height='9.4' rx='2'/%3E%3Cpath d='M8 10.4V8a4 4 0 0 1 8 0v2.4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-hidden: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cdefs%3E%3Cmask id='m'%3E%3Crect width='24' height='24' fill='%23fff'/%3E%3Cpath d='M4 4 20 20' stroke='%23000' stroke-width='5' stroke-linecap='round'/%3E%3C/mask%3E%3C/defs%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cg mask='url(%23m)'%3E%3Cpath d='M2.6 12s3.4-6.4 9.4-6.4 9.4 6.4 9.4 6.4-3.4 6.4-9.4 6.4S2.6 12 2.6 12z'/%3E%3Ccircle cx='12' cy='12' r='2.9'/%3E%3C/g%3E%3Cpath d='M4 4 20 20'/%3E%3C/g%3E%3C/svg%3E");
  --mark-download: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v10.4M7.8 10.4 12 14.6l4.2-4.2M4.8 19.4h14.4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-gear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg transform='translate(2.4 2.4) scale(.8)' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4zM19 12a7 7 0 0 0-.1-1.2l2-1.5-2-3.4-2.3 1a7 7 0 0 0-2-1.2L14.2 3H9.8l-.4 2.7a7 7 0 0 0-2 1.2l-2.3-1-2 3.4 2 1.5A7 7 0 0 0 5 12a7 7 0 0 0 .1 1.2l-2 1.5 2 3.4 2.3-1a7 7 0 0 0 2 1.2l.4 2.7h4.4l.4-2.7a7 7 0 0 0 2-1.2l2.3 1 2-3.4-2-1.5A7 7 0 0 0 19 12z'/%3E%3C/g%3E%3C/svg%3E");
  --mark-card: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='6' width='16' height='12' rx='2'/%3E%3Cpath d='M4 10.2h16M7.4 14.4h3.4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' d='M6.6 6.6 17.4 17.4M17.4 6.6 6.6 17.4'/%3E%3C/svg%3E");
  --mark-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='6.3'/%3E%3Cpath d='m15.3 15.3 5.2 5.2'/%3E%3C/g%3E%3C/svg%3E");
  /* Drawn solid, like the pause bars and for the same reason: on the Up next
     floor the triangle is 0.72rem, and at that size a 1.8 outline with round
     joins is more join than triangle. Narrower than the outline marks, and the
     ink is nudged right of centre — a triangle's optical centre is behind its
     tip. */
  --mark-play: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath stroke='%23000' stroke-width='2.2' stroke-linejoin='round' d='M9 7.2 16.6 12 9 16.8z'/%3E%3C/svg%3E");
  --mark-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' d='M12 5.6v12.8M5.6 12h12.8'/%3E%3C/svg%3E");
  --mark-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5.2 12.4 9.8 17 18.8 7.4'/%3E%3C/g%3E%3C/svg%3E");
  /* The one stroke off the family's 1.8: this arrow is reversed out of a
     filled accent disc, and a light stroke on a dark ground optically thins
     until it reads as a hairline. */
  --mark-send: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19.6V5.2M6.4 10.8 12 5.2l5.6 5.6'/%3E%3C/g%3E%3C/svg%3E");
  /* The way back to the chat's end: a chevron, not the send arrow turned
     over — the two stood one above the other pointing at each other. */
  --mark-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round' d='M6 9.2l6 6 6-6'/%3E%3C/svg%3E");
  /* The swipe-to-reply stage over the composer, and the mark that grows out
     of a bubble under the swipe. */
  --mark-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='8.4' y='8.4' width='11.2' height='11.2' rx='2.2'/%3E%3Cpath d='M15.6 5.6a1.8 1.8 0 0 0-1.8-1.6H6.2A2.2 2.2 0 0 0 4 6.2v7.6a1.8 1.8 0 0 0 1.6 1.8'/%3E%3C/g%3E%3C/svg%3E");
  --mark-retry: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19.2 12a7.2 7.2 0 1 1-2.1-5.1M19.2 4.8v4.4h-4.4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-open: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.6 4.8h5.6v5.6M19.2 4.8l-8 8M17.2 13.6v4a1.6 1.6 0 0 1-1.6 1.6H6.4a1.6 1.6 0 0 1-1.6-1.6V8.4a1.6 1.6 0 0 1 1.6-1.6h4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-reply: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19.6 17.6v-1.4a4.4 4.4 0 0 0-4.4-4.4H4.6M9.2 7.2 4.4 12l4.8 4.8'/%3E%3C/g%3E%3C/svg%3E");
  /* A list of places, for the head that opens them (chats/_head). The set had
     no such glyph: `notes` is a document and `caret` is a disclosure, and
     neither says "the other chats". Same 24-square, same 1.8 stroke as the
     rest, so it sits at cap height with them. */
  /* A box with its lid on: how a chat ends (chats.md rule 3) — put away,
     not finished. */
  /* An arrow, not a chevron: a chevron says "there is more this way", an
     arrow says "you are going there" — and the row it sits on does move you
     (2026-09-17). Not the caret either: that one is a filled triangle and
     means a menu drops. */
  --mark-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' d='M5 12h13M12.2 5.8 18.5 12l-6.3 6.2'/%3E%3C/svg%3E");
  /* A window with its right column set off: the rail, the side panel a narrow
     page opens from the right. */
  --mark-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath stroke='%23000' stroke-width='1.6' stroke-linejoin='round' d='M6.4 9.6h11.2L12 16.4z'/%3E%3C/svg%3E");
  /* The alarm strip's glyph. Drawn as a triangle rather than a circle so it
     reads as a warning at 1em without any colour of its own. */
  --mark-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4.6 20.4 19.4H3.6z'/%3E%3Cpath d='M12 10v4M12 17.2h.01'/%3E%3C/g%3E%3C/svg%3E");
  /* A bell: the menu row that turns push on in this browser. */
  --mark-bell: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 10a5 5 0 0 1 10 0v3.6l1.6 2.6H5.4L7 13.6z'/%3E%3Cpath d='M10.2 19a1.9 1.9 0 0 0 3.6 0'/%3E%3C/g%3E%3C/svg%3E");
  /* …filled and struck through: a muted place, in the list's last seat.
     Filled, since at the seat's size an outline beside the bold count read
     as a smudge. The cut each side of the stroke is 2.8 of 24: at the
     seat's 14px the 1.6 it was came to under a pixel and blurred shut. */
  --mark-muted: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cdefs%3E%3Cmask id='m'%3E%3Crect width='24' height='24' fill='%23fff'/%3E%3Cpath d='M4 4 20 20' stroke='%23000' stroke-width='8' stroke-linecap='round'/%3E%3C/mask%3E%3C/defs%3E%3Cg mask='url(%23m)'%3E%3Cpath d='M12 3.6a6 6 0 0 0-6 6v3.8l-1.8 2.8c-.4.6 0 1.4.8 1.4h14c.8 0 1.2-.8.8-1.4L18 13.4V9.6a6 6 0 0 0-6-6z'/%3E%3Cpath d='M9.6 19.2h4.8a2.4 2.4 0 0 1-4.8 0z'/%3E%3C/g%3E%3Cpath d='M4 4 20 20' stroke='%23000' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E");
  --mark-mail: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='5.8' width='16' height='12.4' rx='2'/%3E%3Cpath d='m4.6 7 7.4 5.4 7.4-5.4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-compose: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11.4 5H6.6a2 2 0 0 0-2 2v10.4a2 2 0 0 0 2 2H17a2 2 0 0 0 2-2v-4.8'/%3E%3Cpath d='M17.6 4.2 19.8 6.4l-8.4 8.4-3 .8.8-3z'/%3E%3C/g%3E%3C/svg%3E");
  --mark-stop: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='6' y='6' width='12' height='12' rx='1'/%3E%3C/svg%3E");
  /* A sheet with a dog-ear and two lines of writing on it: the corner alone
     drew a blank page, and what the mark names is a document. Radiused like
     the mail and phone rects — the square-cut version read as a notch at 16px. */
  /* A folder: the tab on the left, the body under it, at the page's stroke. */
  --mark-folder: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 7.2a1.8 1.8 0 0 1 1.8-1.8h4.2l2.2 2.4h6.8a1.8 1.8 0 0 1 1.8 1.8v8.6a1.8 1.8 0 0 1-1.8 1.8H5.3a1.8 1.8 0 0 1-1.8-1.8z'/%3E%3C/g%3E%3C/svg%3E");
  /* A cloud: what was moved off the box to the platform's store. */
  --mark-cloud: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.2 18.5a4 4 0 0 1-.6-7.95A5.2 5.2 0 0 1 16.7 9.3a3.6 3.6 0 0 1 .6 7.2z'/%3E%3C/g%3E%3C/svg%3E");
  --mark-page: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 5.2a1.8 1.8 0 0 1 1.8-1.8h5.4L18 8.2v10.6a1.8 1.8 0 0 1-1.8 1.8H7.8A1.8 1.8 0 0 1 6 18.8z'/%3E%3Cpath d='M13.2 3.4v3.2a1.6 1.6 0 0 0 1.6 1.6H18'/%3E%3Cpath d='M9.4 13.4h5.2M9.4 17h3.2'/%3E%3C/g%3E%3C/svg%3E");
  /* Angle brackets around a slash — code, as every editor draws it. Ink kept
     inside 5..19 so it reads at the same weight as the globe beside it. */
  --mark-globe: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8'/%3E%3Cpath d='M4 12h16M12 4c-2.8 3.4-2.8 12.6 0 16 2.8-3.4 2.8-12.6 0-16z'/%3E%3C/g%3E%3C/svg%3E");
  --mark-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.4 13.6a3.4 3.4 0 0 0 4.8 0l2.4-2.4a3.4 3.4 0 0 0-4.8-4.8l-1.2 1.2'/%3E%3Cpath d='M13.6 10.4a3.4 3.4 0 0 0-4.8 0l-2.4 2.4a3.4 3.4 0 0 0 4.8 4.8l1.2-1.2'/%3E%3C/g%3E%3C/svg%3E");
  /* Thumb, in the row's own ink. One drawing for both: a thumbs-down is a
     thumbs-up turned 180°, which is what the down button does to it. */
  --mark-thumb: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 10v12'/%3E%3Cpath d='M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z'/%3E%3C/g%3E%3C/svg%3E");
  /* The same thumb, pressed: a rating has to read as a state from across the
     row, and a 1.5px outline under the meta strip's muted grey does not. */
  --mark-thumb-fill: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M2 21h4V9H2v12zM23 10a2 2 0 0 0-2-2h-6.31l.95-4.57.03-.32a1.5 1.5 0 0 0-.44-1.06L14.17 1 7.59 7.59A2 2 0 0 0 7 9v10a2 2 0 0 0 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z'/%3E%3C/svg%3E");
  --mark-energy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13.2 19.6'%3E%3Cpath d='M9.4 0 0 11.7h6L3.8 19.6 13.2 7.9H7.2z'/%3E%3C/svg%3E");
  --mark-phone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='7.2' y='4' width='9.6' height='16' rx='2.2'/%3E%3Cpath d='M10.8 16.8h2.4'/%3E%3C/g%3E%3C/svg%3E");
  --mark-trash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5.5 7h13'/%3E%3Cpath d='M10 7V5.5h4V7'/%3E%3Cpath d='M7.2 7l.7 11.5h8.2L16.8 7'/%3E%3Cpath d='M10.4 10.6v5M13.6 10.6v5'/%3E%3C/g%3E%3C/svg%3E");
  --mark-dots: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='%23000'%3E%3Ccircle cx='12' cy='5.6' r='1.7'/%3E%3Ccircle cx='12' cy='12' r='1.7'/%3E%3Ccircle cx='12' cy='18.4' r='1.7'/%3E%3C/g%3E%3C/svg%3E");
  --energy-ink: var(--amber);
}

.mark,
.energy-mark {
  display: inline-block;
  /* Sized so the INK lands at cap height, not so the boxes match: every
     outline mark is drawn inset in its 24-square (ink ≈ 0.66 of the box), so
     a 1em box drew a glyph noticeably shorter than the letters beside it. */
  width: round(1.15em, 1px);
  height: round(1.15em, 1px);
  vertical-align: round(-0.24em, 1px);
  /* Own gap, so an inline call site doesn't have to remember one. */
  margin-right: round(0.28em, 1px);
  background-color: currentColor;
  mask: var(--mark) center / contain no-repeat;
  /* Never squeezed by a flex row it rides in. */
  flex: 0 0 auto;
}

/* …and zeroed wherever the mark is a flex child of a row that already owns a
   gap, or sits alone in a button, where the trailing margin either doubled
   the space or pushed the glyph off centre. */
.chat-attach .mark,
.chat-send .mark,
.chat-message-file > .mark,
.wallet__topup > .mark,
.task-panel__state > .mark,
.page-lightbox__close > .mark {
  margin-right: 0;
  /* Whole pixels: at the inherited em size the glyph box came out 15.33px,
     which put a 1.2px stroke on half a pixel and made the ✕ read smudged. */
  width: 1rem;
  height: 1rem;
}

/* The notice in place of the input (view only, archived, closed) is one line
   of muted small text: its mark reads at 1.25rem, where the inherited 1rem
   stood as a dot beside the words (owner, 2026-10-16). */
.chat-readonly-notice > .mark {
  margin-right: 0;
  width: 1.25rem;
  height: 1.25rem;
}

/* The paperclip is alone in a 2.5rem button, borderless, so the glyph itself
   is the affordance — it carries the whole button and is drawn accordingly.
   An even size: 27px centred in the 40px button stood on a half (2026-10-14). */
.chat-attach .mark {
  width: 1.75rem;
  height: 1.75rem;
}

/* A rolling count (odometer_controller): each digit a reel of 0–9 behind a
   window one line tall, the strip slid by tenths of its own height. The
   reel's sizer is a figure in the flow, so the count keeps its width and its
   baseline in whatever row it rides; clip-path cuts the window without
   moving the baseline, which overflow would. Same-width figures, or a reel
   would be the width of whichever digit stood in its sizer. */
.odometer { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Every reel holds ten figures: selected, a count copied as "25,6790123…". */
.odometer__digits { user-select: none; -webkit-user-select: none; }
.odometer__reel { position: relative; display: inline-block; clip-path: inset(0 round(-0.1em, 1px)); }
.odometer__sizer { visibility: hidden; }
.odometer__strip {
  position: absolute;
  inset: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform 500ms var(--ease-out);
}
/* Each figure a whole number of pixels tall, so every stop of the strip is
   too: at a fractional line (21.75px) a reel that stopped on 5 sat half a
   pixel off one that stopped on 2, and Chromium drew it a pixel higher. */
.odometer__strip > * { height: round(1lh, 1px); }
.odometer__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Energy is the one mark that keeps a colour of its own: it stands for money,
   and it is the only figure on these surfaces the eye should be able to find
   without reading — as currentColor it read as one more grey number in a grey
   row. Its box is cropped to the bolt, so unlike the others the ink IS the box
   — hence its own pair of numbers for the same drawn height. Narrow, because a
   bolt is a narrow shape, but not so narrow it disappears at the task list's
   0.72rem: the amount beside it is the smallest type on the page. */
.energy-amount { white-space: nowrap; }

.energy-mark {
  --mark: var(--mark-energy);
  background-color: var(--energy-ink);
  width: round(0.62em, 1px);
  height: round(0.92em, 1px);
  vertical-align: round(-0.11em, 1px);
}

.mark--model   { --mark: var(--mark-model); }
.mark--notes   { --mark: var(--mark-notes); }
.mark--arrow   { --mark: var(--mark-arrow); }
.mark--clip    { --mark: var(--mark-clip); }
.mark--paused  { --mark: var(--mark-paused); }
.mark--waiting { --mark: var(--mark-waiting); }
.mark--alarm   { --mark: var(--mark-alarm); }
.mark--keyboard { --mark: var(--mark-keyboard); }
.mark--reply   { --mark: var(--mark-reply); }
.mark--lock     { --mark: var(--mark-lock); }
.mark--hidden   { --mark: var(--mark-hidden); }
.mark--download { --mark: var(--mark-download); }
.mark--gear     { --mark: var(--mark-gear); }
.mark--card     { --mark: var(--mark-card); }
.mark--play     { --mark: var(--mark-play); }
.mark--plus     { --mark: var(--mark-plus); }
.mark--close    { --mark: var(--mark-close); }
.mark--search   { --mark: var(--mark-search); }
.mark--check    { --mark: var(--mark-check); }
.mark--send     { --mark: var(--mark-send); }
.mark--down     { --mark: var(--mark-down); }
.mark--caret    { --mark: var(--mark-caret); }
.mark--alert    { --mark: var(--mark-alert); }
/* The chat chips' receipts: the object the agent touched (Message::CHIPS). */
.mark--mail     { --mark: var(--mark-mail); }
.mark--page     { --mark: var(--mark-page); }
.mark--folder   { --mark: var(--mark-folder); }
.mark--cloud    { --mark: var(--mark-cloud); }
.mark--compose  { --mark: var(--mark-compose); }
.mark--stop     { --mark: var(--mark-stop); }
.mark--globe    { --mark: var(--mark-globe); }
.mark--link     { --mark: var(--mark-link); }
.mark--copy     { --mark: var(--mark-copy); }
.mark--open     { --mark: var(--mark-open); }
.mark--retry    { --mark: var(--mark-retry); }
.mark--thumb    { --mark: var(--mark-thumb); }
/* The bolt as a plain mark: same drawing as .energy-mark, but in the row's own
   ink — a menu label is not a figure to find without reading. */
.mark--energy   { --mark: var(--mark-energy); }
.mark--trash   { --mark: var(--mark-trash); }
.mark--dots    { --mark: var(--mark-dots); }
.mark--phone    { --mark: var(--mark-phone); }
.mark--bell     { --mark: var(--mark-bell); }
.mark--muted    { --mark: var(--mark-muted); }

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Line height on whole pixels. A rule says how loose its lines are as a
   number (--lh, inherited like line-height's own number), and every element
   draws its line at that number times its own type, rounded: a bare 1.3 on
   14px type was an 18.2px line, every row under it stood on a fraction and a
   chat row moved a pixel when the browser settled it (bin/chat-jumps,
   2026-10-14). A rule that sets line-height itself, in rem or normal, still
   wins. */
@property --lh { syntax: "<number>"; inherits: true; initial-value: 1.2; }
*, *::before, *::after { line-height: round(calc(var(--lh) * 1em), 1px); }

/* Controls take the page's type: left to the browser they drew in Arial at
   13.33px, and every glyph sized in em inside an icon button came out at a
   third of a pixel (2026-10-14). Their line height is set, not `normal`:
   normal is the face's own, so a button's height changed when Inter swapped
   in after the first paint and the chat row holding it moved (bin/chat-jumps);
   rounded, so no control stands on a fraction of a pixel. */
button, input, select, textarea { font-family: inherit; font-size: inherit; --lh: 1.2; }

/* The `hidden` attribute wins over a class. The browser's own rule is a bare
   `[hidden] { display: none }`, which ANY class setting `display` outranks —
   so every element some JS hides has to be found out the hard way, one at a
   time, and it was: eight of these had accumulated across five sheets by
   2026-09-16, each a separate rediscovery of the same trap (a grid head that
   would not hide, a flex scroller that stayed under the list it was replaced
   by). One rule instead, so `el.hidden = true` simply works. */
[hidden] { display: none !important; }

/* A link iOS draws over text it took for a number, a date or an address
   (format-detection, layouts, should keep it from drawing one at all): if
   it still does, it reads and presses as the text it was — the rail's
   "941-410-8705", a Google Ads account, stood as a call button (owner,
   2026-10-18). */
a[x-apple-data-detectors] {
  color: inherit !important;
  text-decoration: none !important;
  font: inherit !important;
  pointer-events: none;
}

/* The night every modal opens on. It comes down over the page rather than
   dropping on it — from clear on the open, and back to clear on the close,
   where dialog_fade holds the dialog open for the step under `is-closing`.
   One rule, so the page lightbox (09) and the chat's image lightbox (08)
   read as one family; each sheet animates its own sheet. */
/* …and the same night laid over the page's white, by name, for the phone
   browser's own bars while a dialog is up (night_bars): read off the two at
   run time it came back white on an iPhone, whose Safari gives no computed
   style for ::backdrop (2026-10-16). Moves with the two above. */
:root { --night-bar: #525866; }
dialog::backdrop {
  background: rgba(15, 23, 42, 0.72);
  /* Opacity, not the colour: it runs on the compositor, so a dialog that
     starts heavy work as it opens (the letter's frame parsing its document)
     cannot stall it. A background-color fade did stall there and landed dark
     in one frame. */
  transition: opacity var(--dur) ease-out;
}
dialog.is-closing[open]::backdrop { opacity: 0; }
@starting-style {
  dialog[open]::backdrop { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  dialog::backdrop { transition: none; }
}

/* No grey box flashed over what a finger taps: iOS draws one over every
   link and button by default, the full screen over the drawers' scrim. A
   control shows its own pressed state, or none. */
html { -webkit-tap-highlight-color: transparent; }

/* No pull-to-refresh: an overscroll at the top of a chat reloaded the page
   mid-read. Live updates arrive over the cable, so the gesture has no job.
   The admin keeps it: its pages are read, not chatted in, and a sweep's
   numbers are what a pull is for. */
html:not(.admin) { overscroll-behavior-y: none; }
/* Nor a sideways one on a phone, in the workspace: Chrome on Android takes a
   swipe in from the edge as back, and there the swipe opens a drawer
   (drawer_controller). Wide, there are no drawers, and a trackpad's
   two-finger back stays; the landing, legal pages and admin keep it too. */
@media (max-width: 56.25em) {
  html:has([data-controller~="drawer"]), html:has([data-controller~="drawer"]) body { overscroll-behavior-x: none; }
}

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  --lh: 1.6;
  /* Flex column + min-height: 100vh + `margin-top: auto` on a footer
     (.marketing-footer) pins it to the bottom of the viewport when the page
     content is short. Layouts that want to fill the viewport themselves
     (company, signin-page) set their own height via calc(100vh - topbar) so
     this change is transparent to them. The dynamic unit after it: on iOS
     100vh is the viewport with Safari's bars put away, taller than the one
     on screen, so every page scrolled by that difference. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  /* viewport-fit=cover (the app layout) hands the screen's edges to the
     page, so it keeps its content out of a landscape iPhone's notch itself.
     What is pinned to the bottom pads by --safe-bottom on its own. */
  padding-inline: env(safe-area-inset-left) env(safe-area-inset-right);
}

/* The app opened from an iPhone's home screen. iOS reported no bottom inset
   there on a cold launch (2026-10-04), and the composer's corners sat under
   the screen's rounded ones; 34px is the home indicator's inset on every
   iPhone that has one. corner-cut: a home-button iPhone gets the band too.
   And iOS 27 blurs the top edge of such an app over the page (Liquid Glass)
   unless something fixed is painted there: .edge-paint is that something,
   painted with its fill clipped to text it has none of, so nothing shows. */
@supports (-webkit-touch-callout: none) {
  @media (display-mode: standalone) {
    :root { --safe-bottom: max(env(safe-area-inset-bottom), 34px); }
    .edge-paint {
      position: fixed;
      inset: 0 0 auto;
      height: 11px;
      background: var(--bg);
      background-clip: text;
      -webkit-background-clip: text;
      pointer-events: none;
    }
  }
}

/* Sticky-footer side-effect repair: in a flex-column container, a child
   with `margin: 0 auto + max-width` (the standard "centered page wrapper"
   pattern used by .tasks-page) collapses to
   content-width because auto cross-axis margins
   disable align-items: stretch. Forcing width: 100% restores the expected
   block-element behavior — the child fills cross-axis up to max-width,
   then auto margins center it. Excluded: position-fixed elements like the
   connection-status pill, the selection menu and the edge paint, which would expand to viewport width. */
body > *:not(.connection-status-pill):not(.edge-paint):not(.flash-toast):not(.selection-menu) {
  width: 100%;
}

/* ===== Flash toast =====
   The redirect feedback surface (shared/_flash) — alerts and notices land
   here on the next render. Transient: enters on the house curve, holds
   long enough to read, fades itself out; pointer-events off so the ghost
   never blocks the page underneath. */
.flash-toast {
  position: fixed;
  top: 1rem;
  left: 50%;
  z-index: 100;
  max-width: min(34rem, calc(100vw - 2rem));
  padding: 0.5625rem 1rem;
  border-radius: var(--radius-pill);
  font-size: 0.875rem;
  font-weight: 500;
  background: var(--bg-card);
  border: 1px solid var(--hairline-strong);
  color: var(--text);
  box-shadow: 0 0.5rem 1.5rem rgba(15, 23, 42, 0.2);
  pointer-events: none;
  transform: translateX(-50%);
  animation: flash-toast-life 6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.flash-toast--alert {
  border-color: var(--red);
  color: var(--red);
}

@keyframes flash-toast-life {
  from { opacity: 0; transform: translateX(-50%) translateY(-0.5rem); }
  4%   { opacity: 1; transform: translateX(-50%) translateY(0); }
  92%  { opacity: 1; }
  to   { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .flash-toast { animation: flash-toast-hold 6s step-end forwards; }
  @keyframes flash-toast-hold {
    from { opacity: 1; }
    to   { opacity: 0; }
  }
}

/* Under 16px iOS Safari zooms the page to the focused field and leaves it
   zoomed — on the composer, the two places the product is judged. Every
   sheet sizes its own controls below that (0.9rem is the house size), so this
   is the one rule that has to win over all of them; maximum-scale is not the
   fix (it takes pinch-zoom from everyone). Chromium never auto-zooms, so
   bin/shots cannot show it; a phone can. */
@media (pointer: coarse) {
  input, textarea, select { font-size: 1rem !important; }
}

/* A control's hotkey, shown as a keycap while the modifier is held (the
   hotkey controller marks the root): a cream cap with a darker foot — the
   keyboard's own plastic, the one colour here off the palette, so it reads
   as a key and not as another white chip — astride the control's left
   edge, on its middle line: most of it outside, so it takes the control's
   padding and leaves its words alone (wholly inside it lay over the
   label's first letters, 2026-10-04). The cap is glass: what it does cross
   shows through it, blurred, so nothing is taken off the control to make
   room for its key (hiding the label left an empty frame with a cap in
   it, same day). Dense glass, and one density everywhere: thin, a cap over
   a black mark or a filled button lost its key, and a denser one only
   where that happened made the caps look like different keys. A control
   too small to hold one beside
   its glyph places its own (the search, 07_stage). Any element says its
   key in data-cap. It waits a second before showing (the beat is
   hotkey#held's, not a transition delay: a cap that moves to another
   row meanwhile is a fresh pseudo-element, and a fresh one skips its
   transition — walked at speed, the caps came up at once, 2026-10-09),
   so a shortcut pressed at speed flashes nothing — only a hand that
   rests on the modifier asks for the caps — and leaves at once; a key
   pressed with the modifier held puts them away until it is let go
   (hotkey#held, "used"). Only
   where there is a cursor, a keyboard to go with it, and the wide page. */
@media (hover: hover) and (pointer: fine) and (min-width: 56.26em) {
  [data-cap] { position: relative; }
  [data-cap]::after {
    content: attr(data-cap);
    position: absolute;
    top: 50%;
    left: 0;
    transform: translate(-75%, -50%);
    padding: 0.1875rem var(--space-1);
    border: 1px solid #d6cbb0;
    border-bottom-width: 3px;
    border-radius: 0.375rem;
    background: rgba(246, 239, 220, 0.78);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    box-shadow: 0 2px 6px rgba(15, 23, 42, 0.12);
    color: var(--text);
    font-size: 0.6875rem;
    font-weight: 600;
    --lh: 1;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
  }
  /* Once they are up, a cap that moves to another control is there at
     once: a fresh pseudo-element has nothing to fade from. */
  :root[data-keys="on"] [data-cap]::after {
    opacity: 1;
    transition: opacity 120ms ease-out;
  }
}
