/* The design tokens every tonk surface is built from.
 *
 * Page-level on purpose. These lived inside `account.css`, scoped to
 * `:is(tonk-account, tonk-activate)`, so any new surface had to either
 * widen that selector or invent its own values — and inventing them is
 * how the registration cluster came up ringed in near-white while
 * nothing else in the app was.
 *
 * Aliases over the Web Awesome theme, not a palette of their own: the
 * stone-ink material in `:where(.wa-theme-default)` below is the app's
 * one colorset, and these short names re-express it for the surfaces
 * built on the fabb studies (the ceremony cluster, the account pages,
 * the inspector). Restating the values here is how the login ceremony
 * drifted greenish while everything else read eggplant. Deriving keeps
 * both themes in step — `.wa-dark` swaps the `--wa-*` values and
 * everything here follows, so there is no dark block for the derived
 * names. The `var()` fallbacks are the light values, for the beat
 * before the theme class lands. `--ring` is translucent ink rather
 * than a grey border token, which is what gives every block its edge.
 */
:root {
    /* Both schemes are real: the UA (and every nested guest) must know it.
       A sealed guest is an opaque-origin iframe, and a cross-origin frame's
       `prefers-color-scheme` comes from its EMBEDDER'S used color-scheme —
       without this declaration the OS preference dies at the first frame
       boundary and everything below it wakes up light. */
    color-scheme: light dark;
    --page: var(--wa-color-surface-default, #e8e6e4);
    --ink: var(--wa-color-text-normal, #38182a);
    --on-ink: var(--wa-color-neutral-on-loud, #f7f6f5);
    --soft: var(--wa-color-text-quiet, #5b4953);
    --ring: color-mix(in srgb, var(--ink) 85%, transparent);
    --sep: var(--wa-color-neutral-border-normal, rgb(56 24 42 / 28%));
    --frost-solid: var(--wa-color-neutral-on-loud, #f7f6f5);
    --panel: var(--wa-color-neutral-fill-normal, #d0ccc8);
    --wash: color-mix(in srgb, var(--ink) 6%, transparent);
    --wash-2: color-mix(in srgb, var(--ink) 12%, transparent);
    --wash-p: color-mix(in srgb, var(--on-ink) 16%, transparent);
    --card: var(--wa-color-surface-raised, #fcfbfb);
    --dim: color-mix(in srgb, var(--ink) 32%, transparent);
    /* the current role — a place you are in, split from the CTA register:
       near-ink, one step toward the page, so a tab never outshouts an
       action. DESIGN.md's core palette pins both twins — the colour pass
       tuned these, so they are literals, not derivations. */
    --cur: #552e44;
    /* real glass for genuinely overlaying surfaces; everything on the flat
       page wears --frost-solid pre-composited instead (DESIGN.md's frost
       recipe) */
    --frost: rgb(253 252 252 / 72%);
    --card-hover: color-mix(in srgb, var(--ink) 6%, var(--card));
    --canvas: var(--wa-color-surface-lowered, #e4e2e0);
    --veil: color-mix(in srgb, var(--page) 90%, transparent);
    --track: color-mix(in srgb, var(--ink) 22%, transparent);
    --stub-ink: #9f968e;
    --cond: "IBM Plex Sans Condensed", "Bahnschrift", "Arial Narrow", sans-serif;
    --sans: "IBM Plex Sans", Helvetica, Arial, sans-serif;
    --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;
    --ease: cubic-bezier(0.25, 0.46, 0.45, 0.94);

    /* The FABB's public skin API — DESIGN.md's core palette, LIGHT column,
       pinned: the bar keeps the same bright frost over a dark space that it
       wears over a light page, so its whole twin is the light one and it
       never follows the page's dark swap. The values ARE the app theme's
       light values (one colorset), so text on a bright surface reads
       identically in the chrome and in the app. */
    --fabb-ink: #38182a;
    --fabb-cur: #552e44;
    --fabb-ink-soft: #5b4953;
    --fabb-on-ink: #f7f6f5;
    --fabb-sep: rgb(56 24 42 / 28%);
    --fabb-hover: rgb(56 24 42 / 6%);
    --fabb-press: rgb(56 24 42 / 12%);
    --fabb-bg: rgb(253 252 252 / 72%);
    --fabb-panel: rgb(247 246 245 / 92%);
    --fabb-ring: rgb(56 24 42 / 85%);
}

/* Chrome surfaces pan without a scrollbar (the fabb studies' L10):
   the page and the chrome guests keep scrolling, the bar itself never
   shows. Space CONTENT renders in its own inner frame with its own
   stylesheet, so app documents keep native scrollbars. */
html { scrollbar-width: none; }
html::-webkit-scrollbar { width: 0; height: 0; display: none; }


/* Only the ratios that differ in the dark theme; the colors themselves
   follow the `--wa-*` swap. The dim goes to true black — translucent
   dark ink over a dark page separates nothing. */
:root.wa-dark {
    --ring: color-mix(in srgb, var(--ink) 55%, transparent);
    --wash: color-mix(in srgb, var(--ink) 9%, transparent);
    --wash-2: color-mix(in srgb, var(--ink) 15%, transparent);
    --wash-p: color-mix(in srgb, var(--on-ink) 14%, transparent);
    --dim: rgb(0 0 0 / 45%);
    /* in the dark the veil is the page itself — translucency ghosts false
       edges into the seams, so the layers behind step back fully */
    --veil: var(--page);
    --track: color-mix(in srgb, var(--ink) 25%, transparent);
    --stub-ink: #736265;
    --cur: #cdc5c9;
    --frost: rgb(29 24 25 / 78%);
}

/* `hidden` has to keep hiding things. Web Awesome skins native form
   controls inside `@layer wa-native`, and its rule for
   `button, input[type=button|reset|submit|file], a.wa-button` nests
   `&:not(input[type="file"]) { display: inline-flex }` — a selector matching
   every `<button>`. An author `display` declaration beats the UA's
   `[hidden] { display: none }` whatever its specificity or layer, so the
   attribute silently stops working on a bare button wherever that sheet
   loads. A sealed `<tonk-portal>` guest concatenates it ahead of this file
   (`tonk-portal`'s `build_inject_payload`), which is where it first bit: a
   `hidden` bare button rendered anyway, and hiding it again was equally
   inert.

   `!important` rather than a bare `display: none`. Unlayered declarations
   already outrank layered ones, so this beats `@layer wa-native` on its own;
   the `!important` additionally survives any unlayered author rule of the
   same 0-1-0 specificity that happens to load after this sheet, which is a
   tie decided by order and so by build configuration. Anything that wants to
   show a `hidden` element has to drop the attribute, which is what the
   attribute is for. */
[hidden] { display: none !important; }

/* Web Awesome FOUC cloak. The WA loader registers each `<wa-*>`
   element lazily, importing its chunk on demand, so until a component
   is upgraded it renders as an undefined element: a `<wa-button>` shows
   as bare text, and a `<wa-dialog>` dumps its slotted form inline
   instead of hiding in a modal. Hide them until `:defined` so they
   appear already styled rather than flashing. Tonk's own custom
   elements register synchronously at boot, so they are not affected. */
wa-button:not(:defined),
wa-button-group:not(:defined),
wa-badge:not(:defined),
wa-callout:not(:defined),
wa-card:not(:defined),
wa-carousel:not(:defined),
wa-carousel-item:not(:defined),
wa-comparison:not(:defined),
wa-copy-button:not(:defined),
wa-details:not(:defined),
wa-dialog:not(:defined),
wa-icon:not(:defined),
wa-input:not(:defined),
wa-page:not(:defined),
wa-spinner:not(:defined),
wa-tab:not(:defined),
wa-tab-group:not(:defined),
wa-tab-panel:not(:defined),
wa-tag:not(:defined),
wa-tree:not(:defined),
wa-tree-item:not(:defined) {
    visibility: hidden;
}

/* Self-hosted interface fonts: IBM Plex Sans (body), IBM Plex Sans
   Condensed (heading), and IBM Plex Mono (code). Files
   live under `/fonts/` (copied into the dist by Trunk's
   `copy-dir` on `./assets/fonts`). Self-hosting means no
   cross-origin font fetch and the files flow through the SW
   cache automatically once the offline-support milestone lands.
   `font-display: swap` keeps the page legible (fallback face)
   if the .woff2 hasn't downloaded yet. */
@font-face {
    font-family: "IBM Plex Sans Condensed";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-condensed-latin-400-normal.woff2") format("woff2");
}
@font-face {
    font-family: "IBM Plex Sans Condensed";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-condensed-latin-500-normal.woff2") format("woff2");
}
@font-face {
    font-family: "IBM Plex Sans Condensed";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-condensed-latin-600-normal.woff2") format("woff2");
}
@font-face {
    font-family: "IBM Plex Sans Condensed";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-condensed-latin-700-normal.woff2") format("woff2");
}
/* Control Panel launcher faces — self-hosted like the others. Gestalte
   (display) is shipped as OpenType; IBM Plex Sans/Mono as woff2. */
@font-face {
    font-family: "Gestalte";
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url("/fonts/gestalte-300.otf") format("opentype");
}
@font-face {
    font-family: "Gestalte";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/gestalte-400.otf") format("opentype");
}
@font-face {
    font-family: "Gestalte";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/gestalte-700.otf") format("opentype");
}
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-400-normal.woff2") format("woff2");
}
@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-mono-400-normal.woff2") format("woff2");
}

/* Brand accent → yellow-green, matching the control-panel wireframe
   (`#DEFF1A`). The shoelace palette ships a blue brand; remap the brand
   semantic tokens (fills, borders, on-colors) to a chartreuse scale built
   around `#DEFF1A` so accents read yellow-green in both light and dark. The
   brand is a bright lime, so `on-loud` is DARK ink (not white) to stay
   legible on the fill; quiet/normal are pale tints of the same hue. */
:root,
.wa-light,
.wa-dark {
    --wa-color-brand-fill-quiet: #f9fddd;
    --wa-color-brand-fill-normal: #eef99c;
    --wa-color-brand-fill-loud: #deff1a;
    --wa-color-brand-border-quiet: #eef99c;
    --wa-color-brand-border-normal: #d3e84a;
    --wa-color-brand-border-loud: #deff1a;
    --wa-color-brand-on-quiet: #5c6608;
    --wa-color-brand-on-normal: #41490a;
    --wa-color-brand-on-loud: #2a3005;
}
.wa-dark {
    --wa-color-brand-fill-quiet: #2e3406;
    --wa-color-brand-fill-normal: #4a530d;
    --wa-color-brand-fill-loud: #deff1a;
    --wa-color-brand-on-quiet: #eef99c;
}

html,
body {
    padding: 0;
    margin: 0;
    overscroll-behavior: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    font-family: var(--wa-font-body);
    background-color: var(--wa-color-surface-default);
    color: var(--wa-color-text-normal);
}

/* Keep Web Awesome's default theme mechanics while giving old and new
   surfaces one type system and the stone-ink material selected for Tonk.
   Drop-shadows are zeroed so cards and panels read as flat blocks. */
:where(.wa-theme-default) {
    --wa-font-family-body: "IBM Plex Sans", Helvetica, Arial, sans-serif;
    --wa-font-family-heading: "IBM Plex Sans Condensed", sans-serif;
    --wa-font-family-code: "IBM Plex Mono", ui-monospace, Menlo, monospace;

    --wa-color-surface-raised: #fcfbfb;
    --wa-color-surface-default: #e8e6e4;
    --wa-color-surface-lowered: #e4e2e0;
    --wa-color-surface-border: rgb(56 24 42 / 28%);
    --wa-color-text-normal: #38182a;
    --wa-color-text-quiet: #5b4953;
    --wa-color-neutral-fill-quiet: #eeedec;
    --wa-color-neutral-fill-normal: #d0ccc8;
    --wa-color-neutral-fill-loud: #38182a;
    --wa-color-neutral-border-quiet: rgb(56 24 42 / 18%);
    --wa-color-neutral-border-normal: rgb(56 24 42 / 28%);
    --wa-color-neutral-border-loud: rgb(56 24 42 / 55%);
    --wa-color-neutral-on-quiet: #5b4953;
    --wa-color-neutral-on-normal: #38182a;
    --wa-color-neutral-on-loud: #f7f6f5;

    --wa-border-radius-scale: 0;

    /* tldraw's 11-color palette (minus black and white), solid
       values from the editor's light-mode `DEFAULT_THEME`. Named
       with `--tl-*` so they're easy to reference; swap any of them
       into `--wa-color-text-normal` / `--wa-color-neutral-fill-quiet`
       to re-tint the UI. */
    --tl-blue: #4465e9;
    --tl-green: #099268;
    --tl-grey: #9fa8b2;
    --tl-light-blue: #4ba1f1;
    --tl-light-green: #4cb05e;
    --tl-light-red: #f87777;
    --tl-light-violet: #e085f4;
    --tl-orange: #e16919;
    --tl-red: #e03131;
    --tl-violet: #ae3ec9;
    --tl-yellow: #f1ac4b;

    /* Sync-state palette (from the wireframes). Semantic tokens the sync
       chip — and anything else reflecting sync state — references, so the
       colours live in one place. `idle`=synced (green), `pending`=in flight
       (amber, pulses), `paused`/`local`=muted (grey), `offline`=warn. */
    --sync-idle: #00a050;
    --sync-pending: #e8a100;
    --sync-offline: #ff0000;
    --sync-local: #888888;
    /* Paused reads as muted/inactive — same quiet ink as the "all repos"
       crumb, dimmer than the other status colours. */
    --sync-paused: var(--wa-color-text-quiet);
}

:where(.wa-theme-default.wa-dark) {
    --wa-color-surface-raised: #261f20;
    --wa-color-surface-default: #161313;
    --wa-color-surface-lowered: #1c1718;
    --wa-color-surface-border: rgb(226 223 221 / 28%);
    --wa-color-text-normal: #e2dfdd;
    --wa-color-text-quiet: #c8c3bf;
    --wa-color-neutral-fill-quiet: #261f20;
    --wa-color-neutral-fill-normal: #3c3335;
    --wa-color-neutral-fill-loud: #e2dfdd;
    --wa-color-neutral-border-quiet: rgb(226 223 221 / 18%);
    --wa-color-neutral-border-normal: rgb(226 223 221 / 28%);
    --wa-color-neutral-border-loud: rgb(226 223 221 / 55%);
    --wa-color-neutral-on-quiet: #c8c3bf;
    --wa-color-neutral-on-normal: #e2dfdd;
    --wa-color-neutral-on-loud: #221c1d;
}

/* One loading signal everywhere: the supplied reference grows from a small,
   solid dot into a larger, softer one over roughly two seconds. */
.tonk-pulse {
    display: inline-block;
    width: 18px;
    height: 18px;
    flex: none;
    border-radius: 50%;
    background: currentcolor;
    transform-origin: center;
    animation: tonk-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes tonk-pulse {
    0%, 100% {
        opacity: 1;
        scale: 0.33;
    }
    50% {
        opacity: 0.35;
        scale: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .tonk-pulse {
        animation: none;
        opacity: 1;
        scale: 0.5;
    }
}

/* Cards and callouts: transparent fill so the dot-grid shows
   through, no shadow so they read as flat outlined boxes. The
   default top/bottom *and* left/right borders are kept so cards
   look like proper containers. `appearance="plain"` opts out
   entirely. Status callouts (`danger`, `warning`) are exempt — they
   keep the loud filled appearance Web Awesome's theme ships per
   variant so an alert reads as an alert, not just another bordered
   card. */
wa-card:not([appearance="plain"]),
wa-callout:not([appearance="plain"]):not([variant="danger"]):not([variant="warning"]) {
    background: transparent;
    box-shadow: none;
}

/* `<tonk-display>` mounts dumb-renderer slides inside a
   `<wa-carousel>`. The renderers are custom elements that default
   to inline display, so they collapse to zero size unless we ask
   them to fill the slide. Block + full width/height keeps the
   slot occupied; the WA carousel handles its own layout
   internals (aspect ratio, scroll-snap, gap). */
tonk-display {
    display: block;
}
tonk-view,
tonk-notation {
    display: block;
    width: 100%;
}

/* `<tonk-component>` is a loader, not a visual: it executes its module
   into the realm and renders nothing. Hide it (and its inert child
   script holders) so mounting a component directory in a shell view
   leaves no box behind. */
tonk-component {
    display: none;
}

/* `<tonk-site>` is the ROUTING element: it wires up a per-tab site +
   sealed iframe but imposes no visual box or constraints.
   `display: contents` makes it transparent so its iframe participates
   in the surrounding layout directly and the CONTENT (the iframe,
   sized by the element, and the guest's own view) decides height. */
tonk-site {
    display: contents;
}

/* Pre-stamp placeholder for a `<tonk-site>` guest display. Between the
   sealed iframe booting and the SW stamping the tab's `tonk:site` (route
   match + stamp, ~1s) the site entity carries none of its required
   attributes, so the display projects its slotted `loading` / `no-entity`
   child (the shared pulse) rather than flashing the loud concept-mismatch dump.
   `<tonk-display>` toggles the `hidden` attribute to show/hide the slot, so
   this only centres the child while it is shown — `[hidden]`'s `display:
   none` must still win when it is not. */
tonk-display > .site-pending:not([hidden]) {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100dvh;
}

/* Bare display route layout. The mount slot fills the viewport at
   minimum (`min-height: 100dvh`, viewport-relative so it needs no
   definite-height ancestor) and is a flex column — the PAGE is the
   scroll container, so a view taller than the screen (the inspector)
   scrolls the page. The page view root gets the same viewport-min
   height so a view that wants to fill (the workspace pinning its tab
   strip to the bottom) does — but ONLY here, as the route's own page;
   the SAME view mounted elsewhere (a board tile) stays content-sized.
   Only `min-height` is set on the view root, never `display` /
   `flex-direction`, so the view keeps its own layout mode (e.g. the
   board strip's `flex-direction: row`) and isn't clobbered.

   The selector anchors on `.display-view-slot` — the route's mount
   slot, which appears ONLY as a route page-view container (never inside
   a board tile or carousel) — and spells out its mount chain
   (`> tonk-display > tonk-view > *`) deliberately: it must reach EXACTLY
   the route's page view root and not any nested `<tonk-display>`/
   `<tonk-view>` deeper in the tree (carousel slides, board tiles), which
   own their own sizing. A looser descendant selector would catch those
   and impose a viewport height on content that should stay
   content-sized. Anchoring on the slot keeps the layout independent of
   the routing context, so it holds wherever the slot is mounted —
   including inside the sealed `/space` guest. */
.display-view-slot {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
}
.display-view-slot > tonk-display,
.display-view-slot > tonk-display > tonk-view {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
/* Route indirection pass-through: the `tonk:site` view nests into the matched
   route view, which nests into the page model, so the page-view root sits a few
   displays below the slot. Each level is a bare `<tonk-display>` rendered
   directly inside a `<tonk-view>` (a pure pass-through, no wrapper) — flex-fill
   those so the slot's height reaches the real page view. This targets ONLY the
   pass-through chain: a content slide (carousel slide, board tile) is a NAMED
   element under `tonk-view`, never a bare nested `tonk-display`, so its own
   sizing is untouched. No viewport height is imposed here — the height flows by
   flex from the slot, and each page-view root sets its own `100dvh` below, so
   nothing stacks per indirection level. */
.display-view-slot tonk-view > tonk-display,
.display-view-slot tonk-view > tonk-display > tonk-view {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

.display-view-slot:has(.workspace) {
    height: 100dvh;
}
.display-view-slot .workspace {
    height: 100dvh;
    min-height: 0;
}

/* The tree inspector is a two-pane explorer whose panes scroll
   INDEPENDENTLY, so it needs a definite height to scroll within — with
   the slot's default `min-height: 100dvh` the panes grow to fit their
   content and the whole page scrolls instead, pushing the right pane
   past the bottom edge. Same shape as the workspace rule above: swap
   the slot to a hard `height` when it contains the element, so the
   height is fixed at the viewport rather than floored by it. Keyed on
   the element (not on `tonk-view > *`, which would clobber sibling
   views' layout). */
.display-view-slot:has(tonk-tree) {
    height: 100dvh;
}
.display-view-slot:has(tonk-tree) tonk-tree {
    min-height: 0;
}

/* Tonk Hub page (`/`). A centered column: a header row with the
   "New repo" button and the "Repositories" title, then the space
   cards (the `space` directory view). The hub here owns its own
   page chrome, so the directory view inside it should size to its
   content rather than claim the full viewport height. */
.hub-route {
    display: block;
    box-sizing: border-box;
    max-inline-size: 56rem;
    margin-inline: auto;
    padding: var(--wa-space-2xl) var(--wa-space-xl);
}
/* Title on the left, "New space" trigger button on the right — one row
   above the cards. Lives inside the directory view now (rendered once
   as chrome, before the repeated cards). */
.hub-header {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--wa-space-l);
    margin-block-end: var(--wa-space-xl);
}
/* The create form lives in the dialog, laid out as a simple stack. */
.hub-form {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-s);
}
/* The default-service button (`<tonk-default-remote>` renders this) in
   the workspace topbar's enable-sync form — fills the URL field with
   this server. In the global stylesheet so it is theme-consistent
   wherever the element is used. A quiet inline button. */
.workspace__default-remote {
    align-self: flex-start;
    padding: var(--wa-space-2xs) var(--wa-space-s);
    font: inherit;
    font-size: var(--wa-font-size-s);
    color: var(--wa-color-text-quiet);
    background: transparent;
    border: var(--wa-border-width-s) solid var(--wa-color-surface-border);
    border-radius: var(--wa-border-radius-s);
    cursor: pointer;
}
.workspace__default-remote:hover {
    color: var(--wa-color-text-normal);
    background: var(--wa-color-neutral-fill-quiet);
}

/* Space topbar chrome. Hoisted out of the per-instance workspace view so
   it renders on every space — including empty ones with no workspace
   instance — at the space (directory) level. The `<tonk-repo-name>`,
   `<tonk-sync-toggle>`, and `<tonk-share>` elements
   render into these classes and resolve their repo from the nearest
   `with="branch@repo"` ancestor, so the bar works wherever it sits.
   In the global stylesheet because the directory view that hosts the bar
   always renders, while the per-instance view's <style> does not. */
.workspace__topbar {
    display: flex;
    align-items: center;
    gap: var(--wa-space-m);
    flex: none;
    height: 52px;
    padding: 0 var(--wa-space-l);
    background: var(--wa-color-surface-raised);
    border-bottom: var(--wa-border-width-s) solid var(--wa-color-surface-border);
    font-size: var(--wa-font-size-m);
}
.workspace__brand {
    font-weight: var(--wa-font-weight-bold);
}
.workspace__crumb {
    color: var(--wa-color-text-quiet);
    font-family: var(--wa-font-family-code);
    font-size: var(--wa-font-size-s);
}
.workspace__title {
    font-weight: var(--wa-font-weight-semibold);
}
/* The repository-name banner is a `<inline-editable>` (contenteditable)
   styled to read as plain title text until focused. Borderless and
   transparent so it sits in the topbar like the old static title; on
   focus it reveals a subtle field affordance so the rename is
   discoverable. The display → view wrappers are `display: contents` so
   the editable is laid out by the `.workspace__title` span directly. */
.workspace__title tonk-display,
.workspace__title tonk-view {
    display: contents;
}
.workspace__title-input {
    display: inline-block;
    font: inherit;
    font-weight: var(--wa-font-weight-semibold);
    color: inherit;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--wa-border-radius-s);
    padding: var(--wa-space-3xs) var(--wa-space-2xs);
    margin: calc(-1 * var(--wa-space-3xs)) calc(-1 * var(--wa-space-2xs));
    min-width: 8ch;
    white-space: nowrap;
    cursor: text;
}
.workspace__title-input:focus {
    outline: none;
    border-color: var(--wa-color-brand-fill-loud);
    background: var(--wa-color-surface-default);
}
/* The topbar chips (identity, sync) render through a `<tonk-display>` →
   `<tonk-view>` pair whose line boxes would otherwise sit them above the
   topbar's centerline. Collapse the wrappers (as `.workspace__title`
   does) so each chip is laid out — and vertically centered — directly by
   the `.workspace__actions` flex row. */
.workspace__actions tonk-display,
.workspace__actions tonk-view {
    display: contents;
}
.workspace__spacer {
    flex: 1;
}
.workspace__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--wa-space-m);
    color: var(--wa-color-text-quiet);
}
/* Self-identity chip: the signed-in member's sigil + their editable
   display name, in the topbar actions row. The name reads as plain text
   until hovered/focused, mirroring the repository-title banner
   (`.workspace__title-input`); the avatar is a black-ringed circle
   cropping the square sigil. */
.workspace__identity {
    display: inline-flex;
    align-items: center;
    gap: var(--wa-space-2xs);
    color: var(--wa-color-text-normal, inherit);
}
.workspace__identity-avatar {
    width: 1.5rem;
    height: 1.5rem;
    box-sizing: border-box;
    overflow: hidden;
    border-radius: 50%;
    border: 1.5px solid var(--wa-color-text-normal);
    background: var(--wa-color-surface-default);
    color: var(--wa-color-text-normal);
    flex: none;
}
.workspace__identity-name {
    font: inherit;
    color: inherit;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--wa-border-radius-s);
    padding: var(--wa-space-3xs) var(--wa-space-2xs);
    margin: calc(-1 * var(--wa-space-3xs)) calc(-1 * var(--wa-space-2xs));
    min-width: 6ch;
    white-space: nowrap;
    cursor: text;
}
.workspace__identity-name:hover {
    border-color: var(--wa-color-surface-border);
}
.workspace__identity-name:focus {
    outline: none;
    border-color: var(--wa-color-brand-fill-loud);
    background: var(--wa-color-surface-default);
}
/* The share (`<tonk-share>`) and pause/resume (`<tonk-sync-toggle>`)
   controls: icon-only ghost buttons that brighten on hover. */
.workspace__share,
.workspace__sync-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    flex: none;
    color: inherit;
    background: transparent;
    border: none;
    border-radius: var(--wa-border-radius-s);
    cursor: pointer;
}
.workspace__share:hover,
.workspace__sync-toggle:hover {
    color: var(--wa-color-text-normal);
    background: var(--wa-color-neutral-fill-quiet);
}
.workspace__share-glyph,
.workspace__sync-toggle-glyph {
    width: 18px;
    height: 18px;
}
/* The Hub title is a plain heading like the mock, independent of the
   explicit headline-cover treatment used by other shell chrome. */
.hub-title {
    display: block;
    margin: 0;
    padding: 0;
    background: none;
    color: var(--wa-color-text-normal);
    font-family: var(--wa-font-family-heading);
    font-size: var(--wa-font-size-3xl);
    font-weight: var(--wa-font-weight-bold);
    letter-spacing: normal;
}
/* Inside the hub page the directory view is content-sized, not a
   full-height display route. Undo the viewport-min the generic
   `.display-route` rule imposes. */
.hub-route .display-view-slot {
    min-height: 0;
}
.hub-route .display-view-slot > tonk-display > tonk-view > * {
    min-height: 0;
}

/* The space cards (the `space` directory view in profile.yaml). The
   view template carries only markup; its styling lives here so it is
   ready on first paint rather than flashing unstyled while the seed
   loads and the view renders its own <style>. */
.hub {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-m);
    /* The Hub is now the route's own page (it renders inside the sealed
       `<tonk-site>` guest, with no `.hub-route` shell wrapper). So it carries
       its own page frame — a centered, max-width column with viewport padding —
       the way the old Leptos shell's `.hub-route` did. */
    box-sizing: border-box;
    inline-size: 100%;
    max-inline-size: 56rem;
    margin-inline: auto;
    padding: var(--wa-space-2xl) var(--wa-space-xl);
}
/* One repository row: a framed card linking to its space. The sigil is
   a square swatch on the left; the name and URL stack to its right. */
.hub-card {
    display: flex;
    align-items: stretch;
    gap: var(--wa-space-l);
    padding: 0;
    text-decoration: none;
    color: inherit;
    border: var(--wa-border-width-s) solid var(--wa-color-neutral-fill-loud);
    border-radius: var(--wa-border-radius-m);
    background: transparent;
    overflow: hidden;
    /* Anchor for the absolutely-positioned sync badge (and the blank
       card's progress overlay below). */
    position: relative;
}
.hub-card:hover {
    border-color: var(--wa-color-brand-fill-loud);
}
/* The profile's own self-replica rides in on the same query; hide its
   card so the Hub lists only real spaces. */
.hub-card[data-kind="tonk:profile"] {
    display: none;
}
.hub-card__sigil {
    flex: none;
    inline-size: 8.5rem;
    align-self: stretch;
    display: grid;
    place-items: center;
    background: var(--wa-color-surface-lowered);
}
.hub-card__sigil tonk-sigil {
    inline-size: 100%;
    block-size: 100%;
    display: block;
}
.hub-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-2xs);
    padding: var(--wa-space-m) var(--wa-space-l);
    min-inline-size: 0;
}
.hub-card__name {
    font-family: var(--wa-font-family-heading);
    font-size: var(--wa-font-size-l);
    font-weight: var(--wa-font-weight-semibold);
    /* Reserve one line of height so the slot stays put whether the name
       has resolved, is loading, or is empty — the card never reflows as
       the async name arrives. */
    display: block;
    min-height: 1.4em;
    line-height: 1.4;
}
/* The name comes from the space's own repo (a nested
   `<tonk-display model=tonk:repository>`), which resolves
   asynchronously and may not have synced yet. The display projects the
   `slot="<state>"` child matching its current `data-state`, so the
   "Untitled" placeholder spans authored on the display show while it is
   loading / no-model / no-entity and hide once it is `ready`. Style the
   placeholder spans muted. */
.hub-card__name .hub-card__untitled {
    color: var(--wa-color-text-quiet);
    font-style: italic;
}
.hub-card__url {
    font-family: var(--wa-font-family-code);
    font-size: var(--wa-font-size-s);
    color: var(--wa-color-text-quiet);
}

/* Install state. A freshly created space is `status: tonk:blank`
   while its content branch seeds in the background; the worker flips
   it to `tonk:initialized` when done, which arrives over the Hub's
   subscription and settles the card. While blank: the card is not yet
   clickable, the "/space/..." URL is replaced by an "Installing"
   label, and a thin progress line sweeps across the bottom of the card
   (after the loader in interactivate-dat) reading like the space is
   downloading. */
.hub-card__status {
    display: none;
    font-family: var(--wa-font-family-code);
    font-size: var(--wa-font-size-s);
    color: var(--wa-color-text-quiet);
}

/* Read-only sync status badge (`<tonk-sync-badge>`), pinned to the
   card's top-right corner — clear of the space name and the long,
   wrapping `/space/...` URL below it. Aligned with the name row's top
   so it reads as a header-row status. Hidden by default and shown only
   for real, initialized repositories: the profile self-card never lists
   (its whole card is hidden above) and a `tonk:blank` space is still
   seeding, so neither has a meaningful sync state to show. */
.hub-card__sync {
    display: none;
    position: absolute;
    inset-block-start: var(--wa-space-m);
    inset-inline-end: var(--wa-space-l);
}
.hub-card[data-kind="tonk:repository"]:not([data-status="tonk:blank"]) .hub-card__sync {
    display: flex;
    align-items: center;
    /* The disc is `1em`, so `font-size` sizes it: 24px on the Hub cards (the
       wireframe's `.home-sync`), larger than the FAB's 14px ring. */
    font-size: 24px;
    color: var(--wa-color-text-quiet);
}
/* The sync status disc — the wireframe indicator (Tonk Control Panel MVP),
   rendered by `<ui-sync-status>` and reused by the Hub cards AND the FAB ring.
   These rules live in the app stylesheet (injected into every guest), so the
   disc is host chrome: identical everywhere the element mounts regardless of
   what a space asserts.

   MONOCHROME, like the wireframe: the disc is a single INK color that inverts
   with color mode (`--sync-ink` = the theme's normal text ink, so dark on a
   light surface, light on a dark surface). The five states differ by SHAPE,
   not color — solid / pulsing / hollow ring / half — set by the state MODIFIER
   CLASS the component writes (`sync--synced` etc.). The disc is `1em`, so a
   caller sizes it via `font-size` on `.sync` (the FAB ring is larger than the
   Hub card disc). */
.sync {
    --sync-ink: var(--wa-color-text-normal);
    inline-size: 1em;
    block-size: 1em;
    border-radius: 50%;
    flex: none;
    display: grid;
    place-items: center;
}
.sync .disc {
    inline-size: 1em;
    block-size: 1em;
    border-radius: 50%;
    box-sizing: border-box;
    /* Default (offline / local / unknown): hollow ink ring. */
    background: transparent;
    border: 2px solid var(--sync-ink);
}
/* Synced: solid filled disc. */
.sync--synced .disc {
    background: var(--sync-ink);
    border: 0;
}
/* Syncing: solid disc, pulsing. */
.sync--syncing .disc {
    background: var(--sync-ink);
    border: 0;
    animation: sync-disc-pulse 1.05s ease infinite;
}
/* Offline and local both use the default hollow ring — distinct component
   states, styled the same per the design. (`.sync--offline` / `.sync--local`
   inherit the base `.disc` rule; listed here for intent.) */
.sync--offline .disc,
.sync--local .disc {
    background: transparent;
    border: 2px solid var(--sync-ink);
}
/* Paused: half-filled disc. */
.sync--paused .disc {
    background: linear-gradient(135deg, var(--sync-ink) 0 50%, transparent 50% 100%);
    border: 1.5px solid var(--sync-ink);
}
/* Typed failures stay visually distinct from browser-offline/local. */
.sync--revoked .disc {
    background: linear-gradient(45deg, transparent 42%, var(--sync-ink) 43% 57%, transparent 58%),
                linear-gradient(-45deg, transparent 42%, var(--sync-ink) 43% 57%, transparent 58%);
    border: 1.5px solid var(--sync-ink);
}
.sync--conflict .disc {
    background: linear-gradient(90deg, var(--sync-ink) 0 38%, transparent 38% 62%, var(--sync-ink) 62%);
    border: 1.5px solid var(--sync-ink);
}
.sync--unavailable .disc {
    border: 2px dashed var(--sync-ink);
}
.sync--unknown .disc {
    border: 2px dotted var(--sync-ink);
}
@keyframes sync-disc-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.3; }
}
.hub-card[data-status="tonk:blank"] {
    pointer-events: none;
    position: relative;
}
/* The progress line is an overlay pseudo-element pinned to the bottom
   edge: the card's children carry opaque backgrounds that would paint
   over a card background, and `overflow: hidden` keeps the line tucked
   inside the rounded corners. It grows from the left, then slides off
   to the right, looping — an indeterminate sweep while the seed runs
   for an unknown length of time. */
.hub-card[data-status="tonk:blank"]::after {
    content: "";
    position: absolute;
    inset-block-end: 0;
    inset-inline-start: 0;
    inline-size: 35%;
    block-size: 2px;
    background: var(--wa-color-brand-fill-loud);
    pointer-events: none;
    animation: hub-progress 1.6s ease-in-out infinite;
}
.hub-card[data-status="tonk:blank"] .hub-card__url {
    display: none;
}
.hub-card[data-status="tonk:blank"] .hub-card__status {
    display: block;
}
.hub-card[data-status="tonk:blank"] .hub-card__status::before {
    content: "Installing\2026";
}
/* While installing, the name has nothing to show yet — the
   "Installing…" label + progress line already say what's happening, so
   suppress the "Untitled" placeholder rather than reading like the space
   failed to get a name. Once `tonk:initialized`, the name resolves (or
   falls back to "Untitled" if it is still syncing). */
.hub-card[data-status="tonk:blank"] .hub-card__name .hub-card__untitled {
    display: none;
}
/* The line is 35% of the card wide; it travels from just off the left
   edge to just off the right (its own width is 100% of `translateX`'s
   reference, so -100% hides it left and ~286% clears the right). */
@keyframes hub-progress {
    0% {
        transform: translateX(-100%);
    }
    100% {
        transform: translateX(286%);
    }
}

/* ------------------------------------------------------------------ */
/* Ghost loading skins.

   `<tonk-display>` reflects its lifecycle onto the host as
   `data-state` (loading / ready / empty / error — see
   `tonk-display/src/state.rs`); these rules skin the `loading` phase
   so a slow load-in (resolving views, first subscription frame, a
   freshly invite-claimed repo still syncing) paints a ghost of the
   incoming layout instead of a blank page. Everything here is
   pseudo-element-only: while loading the host has no children, so
   the ghosts can't collide with real content, and the moment the
   state flips off `loading` every rule stops matching.

   Two shared animations: `ghost-appear` holds the ghost invisible
   for a beat so a fast load never flashes a skeleton, then fades it
   in; `ghost-pulse` is the gentle idle breathing. The pulse rides on
   the container (host opacity composites the whole ghost), so each
   pseudo-element doesn't need its own animation. */
@keyframes ghost-appear {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
@keyframes ghost-pulse {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.45;
    }
}
.display-view-slot:empty,
.display-view-slot:has(> tonk-display[data-state="loading"]),
.workspace-directory > tonk-display[data-state="loading"],
.workspace__binder > tonk-display[data-state="loading"],
.workspace__binder tonk-sheet > tonk-display[data-state="loading"] {
    animation:
        ghost-pulse 2s ease-in-out 0.4s infinite,
        ghost-appear 0.25s ease-out 0.15s backwards;
}
@media (prefers-reduced-motion: reduce) {
    .display-view-slot:empty,
    .display-view-slot:has(> tonk-display[data-state="loading"]),
    .workspace-directory > tonk-display[data-state="loading"],
    .workspace__binder > tonk-display[data-state="loading"],
    .workspace__binder tonk-sheet > tonk-display[data-state="loading"] {
        /* Keep the anti-flash fade-in; drop the idle pulse. */
        animation: ghost-appear 0.25s ease-out 0.15s backwards;
    }
}

/* Space page ghost (`/space/{name}`): the route's own display is
   still loading (or the mount slot hasn't mounted it yet — the
   `:empty` arm), so nothing of the workspace exists. Sketch its
   three bands: topbar (host ::before), sheet canvas (host ::after),
   and the bottom tab strip (slot ::after — the host only has two
   pseudo-elements, and the slot is the route's own flex column, so
   its ::after lands after the display exactly where the strip goes).
   The slot/host are already flex columns via the `.display-route`
   layout rules above. The Hub wraps the same `.display-route` chain;
   its higher-specificity rules below repaint all of these as cards. */
.display-view-slot:empty::before,
.display-view-slot > tonk-display[data-state="loading"]::before {
    content: "";
    flex: none;
    height: 52px;
    box-sizing: border-box;
    background-color: var(--wa-color-surface-raised);
    border-bottom: var(--wa-border-width-s) solid var(--wa-color-surface-border);
    /* Topbar chips: brand, crumb, title on the left; sync chip on
       the right. One gradient layer per chip. */
    background-image:
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal));
    background-repeat: no-repeat;
    background-size:
        2.75rem 1rem,
        5rem 0.8rem,
        7rem 0.9rem,
        5rem 1.4rem;
    background-position:
        var(--wa-space-l) 50%,
        6rem 50%,
        12.5rem 50%,
        right var(--wa-space-l) center;
}
.display-view-slot:empty::after,
.display-view-slot > tonk-display[data-state="loading"]::after,
.workspace-directory > tonk-display[data-state="loading"]::before,
.workspace__binder:not(:has(tonk-sheet)) > tonk-display[data-state="loading"]:first-of-type::before {
    /* The sheet-canvas ghost: the bordered card frame with its
       header strip and a couple of content lines. Shared by the
       page-level ghost (::after), the sheets-directory ghost (the
       topbar is already real there), and the first sheet panel's
       ghost while its `<tonk-sheet>` hasn't mounted. */
    content: "";
    flex: 1 1 auto;
    min-height: 0;
    margin: var(--wa-space-l);
    box-sizing: border-box;
    border: var(--wa-border-width-s) solid var(--wa-color-surface-border);
    /* Content lines use the `normal` fill — the `quiet` one is a
       near-match for the page's paper background and disappears. */
    background-image:
        linear-gradient(var(--wa-color-surface-raised), var(--wa-color-surface-raised)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal));
    background-repeat: no-repeat;
    background-size:
        100% 2.4rem,
        14rem 0.8rem,
        18rem 0.8rem;
    background-position:
        0 0,
        var(--wa-space-l) 4.2rem,
        var(--wa-space-l) 5.8rem;
}
.display-view-slot:has(> tonk-display[data-state="loading"])::after,
.workspace-directory > tonk-display[data-state="loading"]::after {
    /* The bottom tab strip ghost: the strip's raised band with two
       tab-label bars and the small "+" square. The canvas ghost's
       own margin supplies the gap above, matching the real panel
       padding. */
    content: "";
    flex: none;
    height: 48px;
    box-sizing: border-box;
    background-color: var(--wa-color-surface-raised);
    border-top: var(--wa-border-width-s) solid var(--wa-color-surface-border);
    background-image:
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal));
    background-repeat: no-repeat;
    background-size:
        5.5rem 1rem,
        5.5rem 1rem,
        1.25rem 1.25rem;
    background-position:
        var(--wa-space-l) 50%,
        9rem 50%,
        16.5rem 50%;
}

/* Sheet panels mid-load. The workspace view mounts each sheet
   through a `<tonk-display>` wrapper which the view's own stylesheet
   hides until its `<tonk-sheet>` exists; while every panel is still
   loading that leaves the binder bare and (worse) flags it empty, so
   the "create your first artifact" launchpad flashes over data that
   is seconds from landing. Suppress the launchpad while any panel is
   loading, and let exactly one loading panel through to paint the
   canvas ghost (the `::before` shared above). */
.workspace__binder:has(> tonk-display[data-state="loading"]) > [slot="empty"] {
    display: none;
}
.workspace__binder:not(:has(tonk-sheet)) > tonk-display[data-state="loading"]:first-of-type {
    display: flex;
}
.workspace__binder:not(:has(tonk-sheet)) > tonk-display[data-state="loading"]:first-of-type::before {
    margin: 0;
}

/* A mounted sheet whose content display is still loading: ghost
   text lines inside the card body, under the (real) projected
   header. */
.workspace__binder tonk-sheet > tonk-display[data-state="loading"]::before {
    content: "";
    display: block;
    height: 7rem;
    margin: var(--wa-space-l);
    background-image:
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal));
    background-repeat: no-repeat;
    background-size:
        16rem 0.8rem,
        22rem 0.8rem,
        12rem 0.8rem;
    background-position:
        0 0,
        0 1.6rem,
        0 3.2rem;
}

/* Hub ghost (`/`): repaint the generic space-page ghost above as the
   repo list — a header row (slot ::before) and three ghost cards
   (host ::before / ::after plus slot ::after). Selectors spell out
   the full `.hub-route .display-route …` chain to outweigh the
   generic page-ghost rules, the same way the Hub's min-height
   overrides do. */
.hub-route .display-view-slot:has(> tonk-display[data-state="loading"])::before {
    content: "";
    flex: none;
    height: 2.4rem;
    margin-block-end: var(--wa-space-xl);
    border: none;
    background-color: transparent;
    /* Page title bar on the left, "New space" button block on the
       right — matching `.hub-header`. */
    background-image:
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal));
    background-repeat: no-repeat;
    background-size:
        9rem 2rem,
        7.5rem 2.4rem;
    background-position:
        0 50%,
        100% 50%;
}
.hub-route .display-view-slot > tonk-display[data-state="loading"] {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-m);
}
.hub-route .display-view-slot > tonk-display[data-state="loading"]::before,
.hub-route .display-view-slot > tonk-display[data-state="loading"]::after,
.hub-route .display-view-slot:has(> tonk-display[data-state="loading"])::after {
    /* One ghost repo card: the sigil swatch on the left, name and
       URL bars to its right — matching `.hub-card` geometry. */
    content: "";
    flex: none;
    height: 5.75rem;
    margin: 0;
    box-sizing: border-box;
    border: var(--wa-border-width-s) solid var(--wa-color-surface-border);
    border-radius: var(--wa-border-radius-m);
    background-color: transparent;
    background-image:
        linear-gradient(var(--wa-color-surface-lowered), var(--wa-color-surface-lowered)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal)),
        linear-gradient(var(--wa-color-neutral-fill-normal), var(--wa-color-neutral-fill-normal));
    background-repeat: no-repeat;
    background-size:
        8.5rem 100%,
        11rem 1.1rem,
        16rem 0.8rem;
    background-position:
        0 0,
        calc(8.5rem + var(--wa-space-l)) 1.5rem,
        calc(8.5rem + var(--wa-space-l)) 3.3rem;
}
/* The slot has no flex gap (the header spaces itself with a margin),
   so the third card — the slot's ::after, laid out right after the
   display's two — carries the card gap itself. */
.hub-route .display-view-slot:has(> tonk-display[data-state="loading"])::after {
    margin-block-start: var(--wa-space-m);
}

/* When a view or notation slide lives inside the carousel
   fallback (no explicit `view` attribute on `<tonk-display>`),
   it has to fill the carousel-item's slot for the carousel's
   aspect ratio to work. Outside that context — e.g. a single-mode
   `<tonk-display view=…>` mounted inside a host template's `<li>`
   — the elements flow at content size, which lets nested
   `<tonk-display>` instances stack naturally without each one
   claiming a full slide's worth of vertical space. */
wa-carousel-item tonk-view,
wa-carousel-item tonk-notation {
    height: 100%;
}

/* `wa-carousel.seamless` is an opt-in carousel whose chrome takes no
   visual space: a slide fills the whole element and the pagination dots
   float on the right edge as a vertical switcher, so the rendered
   instance looks exactly as it would without the carousel wrapping it.
   The directory view uses it to present a model's instances as a stack
   of full-bleed slides. A plain `<wa-carousel>` keeps Web Awesome's
   default layout (bottom dots, gap, aspect ratio).

   Web Awesome lays its `part="base"` out as a grid with a row gap that
   reserves space for the pagination beneath the scroll container. We
   flatten that to a single full-size cell and lift the dots out of flow
   with `position: absolute`, so they overlay the slide instead of
   consuming layout. The scroll container then occupies 100% and the
   slide fills it; the aspect-ratio constraint is dropped so the content
   sizes to the available space rather than a 16/9 letterbox. */
wa-carousel.seamless {
    --aspect-ratio: auto;
    display: block;
    height: 100%;
    position: relative;
}
wa-carousel.seamless::part(base) {
    display: block;
    height: 100%;
    gap: 0;
    padding: 0;
}
/* Stretch slides to fill the scroll container instead of centering them
   at content height — the default grid `align-items: center` is what
   letterboxed a full-height view into a short, centered band. */
wa-carousel.seamless::part(scroll-container) {
    height: 100%;
    align-items: stretch;
}
/* Each slide, and the nested `<tonk-display>` chain inside it, fills the
   carousel-item so a directory instance occupies the whole viewport just
   like a bare single-entity route. The bare route gets a full-height
   chain from the `.display-route > … > tonk-view` rule above, but a
   carousel-nested display is not a direct child of that chain, so it
   would otherwise collapse to its content size (a cramped column). */
wa-carousel.seamless > wa-carousel-item {
    height: 100%;
}
wa-carousel.seamless > wa-carousel-item > tonk-display,
wa-carousel.seamless > wa-carousel-item > tonk-display > tonk-view,
wa-carousel.seamless > wa-carousel-item > tonk-display > tonk-notation {
    flex: 1 1 auto;
    width: 100%;
    height: 100%;
    min-height: 0;
}
/* Pagination dots: overlay on the right edge, centered vertically, in a
   column so they read as a vertical slide switcher. Out of flow so they
   take no layout space. */
wa-carousel.seamless::part(pagination) {
    position: absolute;
    inset-block: 0;
    inset-inline-end: var(--wa-space-s);
    margin: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

/* `<tonk-notation>`'s rendered `<pre>` reuses the dialog-yaml
   editor palette so the read-only carousel slide visually
   matches what users see when authoring the same notation in a
   `<tonk-code>` cell. Class names mirror the editor's decoration
   names; colors come from the `--tonk-code-*` palette declared
   further down. The block fills the carousel slide and scrolls
   on overflow so long entities don't spill into adjacent
   slides. */
tonk-notation .tonk-notation-pre {
    margin: 0;
    padding: var(--wa-space-m);
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    overflow: auto;
    background: transparent;
    color: var(--tonk-code-fg);
    font-family: var(--tonk-code-font);
    font-size: var(--tonk-code-font-size);
    line-height: 1.5;
    white-space: pre;
}
/* Mirror the typography rules in `dialog-yaml.ts`'s `dialectTheme`
   so the read-only render matches the editor character-for-character.
   Anything changed in that file (bold/italic/underline thickness)
   should be reflected here. */
tonk-notation .tonk-cm-key {
    color: var(--tonk-code-key);
}
tonk-notation .tonk-cm-effect {
    color: var(--tonk-code-effect);
    font-weight: bold;
}
tonk-notation .tonk-cm-name-sigil {
    color: var(--tonk-code-name-sigil);
}
tonk-notation .tonk-cm-name {
    color: var(--tonk-code-name);
}
tonk-notation .tonk-cm-entity {
    color: var(--tonk-code-entity);
    text-decoration: underline;
    text-decoration-color: var(--tonk-code-entity);
    text-decoration-style: solid;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}
tonk-notation .tonk-cm-variable {
    color: var(--tonk-code-variable);
    font-style: italic;
}
/* A value flagged as a missing required attribute by the no-entity
   diagnostic: a red wavy underline (the editor's "this is wrong" cue),
   wrapped in a `<wa-tooltip>` that names the absent attribute on hover. */
tonk-notation .tonk-notation-error {
    text-decoration: underline wavy var(--wa-color-danger-fill-loud);
    text-decoration-skip-ink: none;
    text-underline-offset: 3px;
    cursor: help;
}

/* Evaluate results — two views over the same data:
   `.query-tree` (grouped `<wa-tree>`, concept → entity → field →
   value) and `.query-notation` (listed, notation-shaped records).
   Both reuse the dialog-yaml `tonk-cm-*` decoration palette and
   the editor typography (from the shared `tonk-notation,
   .query-tree, .query-notation` palette block above) so the two
   views and the editor read identically. */
.query-tree,
.query-notation {
    font-family: var(--tonk-code-font);
    font-size: var(--tonk-code-font-size);
    line-height: 1.5;
    color: var(--tonk-code-fg);
}
.query-tree {
    --indent-guide-width: 1px;
}
/* Tree labels are not interactive content — let the user select
   and copy entity URIs and values out of the result. */
.query-tree wa-tree-item::part(label) {
    user-select: text;
}

/* `tonk-cm-*` decoration classes — shared by both result views.
   Mirror the typography in `dialog-yaml.ts`'s `dialectTheme`. */
.query-tree .tonk-cm-key,
.query-notation .tonk-cm-key {
    color: var(--tonk-code-key);
}
.query-tree .tonk-cm-effect,
.query-notation .tonk-cm-effect {
    color: var(--tonk-code-effect);
    font-weight: bold;
}
.query-tree .tonk-cm-name,
.query-notation .tonk-cm-name {
    color: var(--tonk-code-name);
}
.query-tree .tonk-cm-entity,
.query-notation .tonk-cm-entity {
    color: var(--tonk-code-entity);
    text-decoration: underline;
    text-decoration-color: var(--tonk-code-entity);
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}
.query-tree .tonk-cm-variable,
.query-notation .tonk-cm-variable {
    color: var(--tonk-code-variable);
    font-style: italic;
}
.query-tree .tonk-cm-string,
.query-notation .tonk-cm-string {
    color: var(--tonk-code-string);
}
.query-tree .tonk-cm-number,
.query-notation .tonk-cm-number {
    color: var(--tonk-code-number);
}
/* Plain — separators and arrays/objects shown as compact JSON. */
.query-tree .tonk-cm-plain,
.query-notation .tonk-cm-plain {
    color: var(--tonk-code-fg-muted);
}

/* Listed view — each result is a `notation-record`; every row
   (head, field, value line) is its own block so lines stay
   independently selectable. Rows are plain blocks with inline
   children — not flex — so a selection copied out of the result
   reads as one line per row. Indentation is a literal-space
   `.notation-indent` span the renderer prepends, not CSS padding,
   so the copied text keeps its structure when pasted elsewhere. */
.notation-record + .notation-record {
    margin-block-start: var(--wa-space-s);
}
.notation-row {
    user-select: text;
}
/* Field/value rows wrap on overflow rather than truncating, so a
   long value stays fully visible and fully copyable. */
.notation-field,
.notation-value-line {
    overflow-wrap: anywhere;
}
/* The indent prefix carries real spaces; `pre` keeps them from
   collapsing so each row's content starts at a consistent column,
   on screen and in copied text. */
.notation-indent {
    white-space: pre;
}

/* A `<wa-tree-item>`'s `label` part is a flex item; its default
   `min-width: auto` lets it grow to the value's intrinsic width,
   so a long value would run the row off-screen. `min-width: 0`
   lets the label shrink to its flex container, and the value
   (rendered uncollapsed in the tree) wraps within it. */
.query-tree wa-tree-item::part(label) {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Banner header: the space name as a large display title, sized
   to match the sidebar column. Transparent background so the banner
   and the main body read as one continuous surface — the name
   alone carries the weight. The share affordance sits to the right;
   `gap` separates it from the title so they don't collide. */
.space-banner {
    background: transparent;
    color: var(--wa-color-text-normal);
    display: flex;
    align-items: center;
    /* Title left, share affordance right. When this banner is
       slotted into wa-page's `main-header`, wa-page's part
       styling already does this — but when the inspector is
       mounted somewhere without that slot (e.g. inside a board
       tile), the local rule keeps the layout consistent. */
    justify-content: space-between;
    gap: var(--wa-space-m);
    height: 8rem;
    padding-inline: var(--wa-space-xl);
}
/* Banner actions (auto-sync toggle + share) cluster on the right;
   the title takes the remaining space on the left. */
.space-banner-actions {
    display: flex;
    align-items: center;
    gap: var(--wa-space-xs);
}
/* Tachyons' headline-cover treatment for explicit shell chrome: a dark
   filled box hugging the text. The
   heading goes `display: inline-block` so the box shrinks to
   the text width instead of stretching across its container,
   `line-height: 1.5` provides the vertical breathing room
   that keeps the text from looking cramped at the box edges,
   and `box-decoration-break: clone` makes every wrapped line
   render as a complete padded box (rather than a sliced one).
   System-font stack matches Tachyons; tracking is tightened
   per the original example. Authored headings are deliberately
   outside this selector and retain the guest theme's native treatment. */
.tonk-shell-heading,
.space-banner :where(h1, h2, h3, h4, h5, h6) {
    display: inline-block;
    background: var(--wa-color-text-normal);
    color: var(--wa-color-neutral-fill-quiet);
    font-family:
        -apple-system, BlinkMacSystemFont, "avenir next", avenir, helvetica,
        "helvetica neue", ubuntu, roboto, noto, "segoe ui", arial, sans-serif;
    font-weight: 600;
    line-height: 1.5;
    letter-spacing: -0.05em;
    padding: var(--wa-space-2xs) var(--wa-space-xs);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

.space-banner h1 {
    margin: 0;
    font-size: var(--wa-font-size-3xl);
}

/* Space view padding; WA's `wa-stack` handles vertical gaps
   and `wa-grid` handles the card layout. A couple of local
   tweaks: uppercase tiny section labels, and a two-column
   floor for the branch grid via `--min-column-size`. */
.space-view {
    padding: var(--wa-space-m) var(--wa-space-xl);
    padding-block-end: var(--wa-space-2xl);
    gap: var(--wa-space-l);
}
.space-section-title {
    margin: 0;
    font-size: var(--wa-font-size-s);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    opacity: 0.7;
}

/* Viewer page: drop the inner padding so the iframe fills the
   available main area edge-to-edge; the banner above keeps the
   page chrome consistent with the regular space view. */
.space-viewer {
    padding: 0;
    gap: 0;
    flex: 1;
    min-height: 0;
}

.space-viewer-frame {
    border: 0;
    width: 100%;
    height: 100%;
    min-height: 60vh;
    background: transparent;
}

/* Branches as collapsible rows. Each row's `<wa-details>`
   summary is a single horizontal line: icon + name + version +
   sync icon-buttons + status chip, all packed tight via gap.
   Body opens to a stack of upstream/tree details and the
   per-branch claim-query form. */
.branch-list {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-2xs);
}
.branch-row {
    /* WA's <wa-details> default is a filled card on neutral
       background. We want a list row that blends with the page;
       drop the background and keep just a light bottom hairline
       to separate adjacent rows. */
    --wa-color-surface-default: transparent;
    --wa-color-surface-raised: transparent;
    background: transparent;
    border-block-end: 1px solid var(--wa-color-surface-border);
}
.branch-row::part(base) {
    background: transparent;
    border: 0;
    box-shadow: none;
}
.branch-row::part(summary) {
    /* WA's <wa-details> default-pads the summary heavily; tighten
       so the row reads as a list item rather than a card. */
    padding-block: var(--wa-space-2xs);
    padding-inline: var(--wa-space-s);
}
.branch-row::part(content) {
    background: transparent;
    border-block-start: 1px dashed var(--wa-color-surface-border);
}
.branch-summary {
    /* Slotted into `<wa-details>`'s shadow, where the
       `*` border-box reset wa-page applies in light DOM
       doesn't reach. Without this, descendants inherit
       `content-box` (the CSS spec default) and `<wa-button>`
       widths blow out past their declared `width: 38px`
       because their padding+border get added on top. */
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: var(--wa-space-s);
    width: 100%;
}
.branch-summary > * {
    box-sizing: border-box;
}
/* Branch label + revision badges sit side-by-side touching;
   the wrapper inline-flexes them with zero gap so their
   borders meet directly. WA badges size relative to their
   font-size, so the wrapper sets that for both at once.
   Both badges carry identifier-shaped text (branch name,
   short tree hash), so they share the monospace font that
   `<code>` blocks use elsewhere. Icons aren't affected — they
   live in the icon font via `<wa-icon>`. */
.branch-summary-pair {
    display: inline-flex;
    align-items: center;
    font-size: var(--wa-font-size-l);
    font-family: var(--wa-font-family-code);
}
.branch-summary-pair > wa-badge:first-of-type {
    font-weight: 700;
}

/* Push the action buttons (and the trailing sync chip) to the
   far right of the summary line. The first `<wa-button>` in
   the row gets the auto-margin; everything after it follows
   it across the gap. Targeting the button rather than a
   wrapper keeps the markup flat (we removed
   `.branch-summary-actions` earlier when wa-button-group was
   dropped). */
.branch-summary > wa-button:first-of-type {
    margin-inline-start: auto;
}
.branch-summary-empty {
    color: var(--wa-color-text-quiet);
    font-size: var(--wa-font-size-s);
}
.branch-body {
    padding: var(--wa-space-s);
}
.branch-detail {
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: var(--wa-space-s);
    row-gap: var(--wa-space-3xs);
    margin: 0;
}
.branch-detail dt {
    font-size: var(--wa-font-size-s);
    color: var(--wa-color-text-quiet);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.branch-detail dd {
    margin: 0;
}
.branch-claims {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-s);
}

/* Compact remote tiles. Sigil + name only; full address /
   subject DID surface via the `title` attribute today and
   will graduate to a click-to-edit panel later. The grid
   floor keeps tiles from stretching to absurd widths on
   wide viewports. */
.remote-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: var(--wa-space-s);
}
.remote-tile {
    --spacing: var(--wa-space-2xs);
}
.remote-tile tonk-sigil[slot="media"] {
    width: 2.5rem;
    height: 2.5rem;
    flex: none;
}
.remote-tile-name {
    font-size: var(--wa-font-size-s);
    font-weight: 600;
}
.member-list {
    display: flex;
    flex-direction: column;
    gap: var(--wa-space-xs);
}
.member-row {
    display: flex;
    align-items: center;
    gap: var(--wa-space-s);
}
.member-sigil {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
}
.member-row__text {
    display: flex;
    align-items: baseline;
    gap: var(--wa-space-xs);
}
.member-invited-by {
    color: var(--wa-color-text-quiet);
    font-size: var(--wa-font-size-s);
}

/* Sidebar uses wa-page's standard `navigation` slot. wa-page
   handles everything: rail-style column on desktop, drawer +
   built-in hamburger toggle on mobile (`mobile-breakpoint`,
   default 768px). `navigation-placement="end"` on the host
   places the *mobile drawer* on the inline-end side; the
   desktop column placement is governed by wa-page's body
   grid (default `'menu main aside'`), which we override below
   so the rail and the drawer agree on which side they live. */
wa-page {
    --menu-width: 4rem;
    /* Let the `<body>`'s dot-grid show through. wa-page's host
       and inner parts default to `var(--wa-color-surface-default)`
       (an opaque surface) which would cover the grid otherwise. */
    background: transparent;
}
wa-page::part(base),
wa-page::part(body),
wa-page::part(main),
wa-page::part(main-content) {
    background: transparent;
}

/* Swap the body grid so the menu column lives at the inline-end
   side. Track widths follow column order, so we also reorder
   `grid-template-columns` to keep `--main-width` (1fr) in the
   middle slot and `--menu-width` (4rem) on the end. */
wa-page::part(body) {
    grid-template-areas: "main menu aside";
    grid-template-columns:
        minmax(0, var(--main-width))
        minmax(0, var(--menu-width))
        minmax(0, var(--aside-width));
}

/* Mobile drawer width. wa-page renders the navigation as a
   `<wa-drawer>` below the breakpoint; its default `--size`
   is 25rem (a full nav-panel width), but our content is an
   icon rail — match the desktop column width so the drawer
   reads as the same rail sliding in. `--spacing: 0` zeroes
   the drawer body's default 24px inset so the rail's tiles
   sit flush against the panel edges (otherwise the body's
   padding eats most of a 4rem panel and leaves dead space
   on the inline-end). */
wa-page::part(drawer) {
    --size: 4rem;
    --spacing: 0;
}

/* Hide the drawer's built-in close button. The hamburger
   toggle and backdrop light-dismiss already cover closing —
   a redundant X eats space in a 4rem-wide panel. */
wa-page::part(drawer__close-button) {
    display: none;
}

/* The drawer panel ships with `box-shadow: var(--wa-shadow-l)`
   to lift it off the page. We're going for a flat brutalist
   treatment — drop it. */
wa-page::part(drawer__dialog) {
    box-shadow: none;
}

/* Paint the rail background on every part the navigation
   content lives inside — desktop is one `<nav part="navigation">`,
   mobile splits across the drawer's header/body/footer parts
   (label/body/footer of `<wa-drawer>`). Painting all of them
   with the same color avoids visible seams between sections. */
wa-page::part(navigation),
wa-page::part(drawer__body),
wa-page::part(drawer__header),
wa-page::part(drawer__footer) {
    background: var(--wa-color-neutral-fill-quiet);
}
wa-page::part(navigation),
wa-page::part(drawer) {
    border-inline-start: 1px solid var(--wa-color-neutral-border-quiet);
}

/* Wrapper that holds the rail tiles in `slot="navigation"`.
   wa-page's `::slotted([slot*='navigation'])` rule gives this
   `display: flex; flex-direction: column; gap; padding`. Zero
   the padding so tiles span the full column width — the column
   is only 4rem, and `.sidebar-space`'s 50% inset already
   provides the inner gutter. */
.sidebar-rail {
    padding: 0;
    /* Zero the gap wa-page's slotted styles add so tiles stack
       flush against each other. */
    gap: 0;
    background: transparent;
}

/* Space switcher: square tile inset to 50% of the rail so the
   yellow gutter shows around it. Active tile breaks out to
   100% to read as "open". Tiles hug the inline-start (page-
   facing) side so the gutter sits between them and the page
   edge — `margin-inline: 0 auto` consumes the trailing space. */
.sidebar-space {
    display: block;
    width: 50%;
    aspect-ratio: 1;
    margin: 0 auto 0 0;
    /* Zero wa-page's `::slotted([slot*='navigation'])` default
       padding (16px) — its design assumes nav items are large
       text rows, but our tiles are square sigils that need to
       sit flush in the rail. */
    padding: 0;
    /* wa-page's generic `slot[name]::slotted(*)` rule paints
       every slotted item with `background: var(--wa-color-surface-default)`
       (white). For our tiles we want the rail's color to show
       through — the active-state hover/sigil contrast handles
       the visual signal, no per-tile bg needed. */
    background: transparent;
    transition:
        width var(--wa-transition-normal) var(--wa-transition-easing),
        margin var(--wa-transition-normal) var(--wa-transition-easing),
        transform var(--wa-transition-normal) var(--wa-transition-easing);
}

.sidebar-space.is-active,
.sidebar-space--full {
    width: 100%;
    margin: 0;
}

/* Tighten every `--wa-space-*` token in one shot on narrow
   viewports. WA defines all of them as `calc(var(--wa-space-scale)
   * Nrem)`, so scaling the knob globally pulls in every
   padding/gap/margin that uses the tokens — no need to
   override rules one by one. 0.65 lands `--wa-space-m` at
   ~10px (down from 16px). */
@media screen and (width < 768px) {
    :root {
        --wa-space-scale: 0.65;
    }

    /* Reclaim the remaining inline padding — even after the
       global scale-down, `.space-view` still keeps a gutter
       that's wasted on a tight viewport. */
    .space-view {
        padding-inline: 0;
    }

    /* Below the breakpoint wa-page moves the navigation into
       a `<wa-drawer>` overlay, but the body grid still reserves
       `--menu-width` for the (now-empty) menu column — leaving
       a strip of dead space on the inline-end. Collapse the
       column to 0 so the main content fills the viewport. The
       drawer is positioned over the page, not inside the grid,
       so it's unaffected by this. */
    wa-page {
        --menu-width: 0;
    }

    /* wa-page renders the hamburger inside `[part~='header']`,
       a sticky grid row at the top. With no other header
       content, the row is mostly empty padding above the body —
       wasted at narrow widths. Float the toggle button itself
       so it overlays the page corner instead of forcing the
       header row open. The `header` grid row collapses to its
       intrinsic min-height (which is 0 once empty), reclaiming
       the strip for content. */
    wa-page::part(navigation-toggle) {
        position: fixed;
        inset-block-start: var(--wa-space-xs);
        inset-inline-end: var(--wa-space-xs);
        z-index: 6;
    }
}

.sidebar-space::part(base) {
    width: 100%;
    height: 100%;
    padding: 0;
    border-radius: 0;
    border: 0;
    background: transparent;
    color: var(--wa-color-text-normal);
}
/* Sigils live in an externally-referenced SVG, where
   `currentColor` resolves against that SVG's own color
   context rather than the host's. Propagate the tile's
   color into the sigil via `--sigil-fg` so hover inversion
   actually re-tints the glyphs. */
.sidebar-sigil {
    --sigil-fg: var(--wa-color-text-normal);
}

/* Hover/focus on inactive tiles: rotate the sigil. Color stays
   the same — only the glyph orientation changes, which is
   enough of a signal without recoloring the tile. */
/* Rotate the whole tile on hover. Rotating the host element
   (rather than the inner `.sidebar-sigil`) keeps the effect
   reliable in Safari, where `:hover` propagation into custom-
   element light-DOM children is inconsistent. */
.sidebar-space:is(:hover, :focus-within) {
    transform: rotate(90deg);
}

/* Active space: full-bleed tile (width handled above). The
   tile background stays transparent so the body's dot-grid
   shows through — without that, even though the panel color
   matches the body bg in light mode, the active tile reads as
   a solid block instead of as a continuous part of the page. */
.sidebar-space.is-active::part(base) {
    background: transparent;
}

.sidebar-space::part(label) {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
}

.sidebar-sigil {
    display: block;
    /* Width-only sizing so `aspect-ratio: 1/1` from the
       baseline derives the height. Setting both `width` and
       `height` to 100% caused a FOUC ribbon: before
       `<wa-page>` finished laying out its navigation slot,
       the parent had no width *or* height yet, so the sigil
       had no constraint and stretched into the page flow.
       Width-only collapses gracefully — it stays 0 until
       the parent has a width, then snaps to a square. */
    width: 100%;
}

/* `+` tile: same footprint as the sigil tiles but flipped to
   the opposite edge (page-edge side) so it reads as a companion
   action distinct from the spaces. */
.sidebar-space--add {
    margin-inline: auto 0;
}

/* Profile tile sits in `slot="navigation-footer"` directly;
   add a bottom margin so it doesn't kiss the rail's bottom
   edge. Matches the top breathing room above the `+` tile. */
.sidebar-space--profile {
    margin-block-end: var(--wa-space-m);
}

.sidebar-add-glyph {
    display: block;
    width: 100%;
    height: 100%;
}

/* `+` tile: at rest, a dark square with a light `+`. On hover,
   a light disc blooms behind the `+` and the `+` flips dark,
   reading as "the new slot opens". */
.sidebar-space--add::part(base) {
    background: var(--wa-color-text-normal);
}
.sidebar-add-disc {
    fill: transparent;
}
.sidebar-add-cross {
    stroke: var(--wa-color-neutral-fill-quiet);
    stroke-width: 4;
    stroke-linecap: square;
    fill: none;
}
.sidebar-space--add:is(:hover, :focus-within) .sidebar-add-disc {
    fill: var(--wa-color-neutral-fill-quiet);
}
.sidebar-space--add:is(:hover, :focus-within) .sidebar-add-cross {
    stroke: var(--wa-color-text-normal);
}

/* Join page — used by the `/join` route. The hero block sits at
   the top of the card and visualizes the space the recipient is
   considering: a sigil derived from the subject DID alongside
   any explanatory copy. The sigil baseline (see tonk-sigil/web.rs)
   defaults to `inline-block` with no explicit dimensions, so we
   give it an explicit size here — same pattern as `.remote-card
   tonk-sigil[slot="media"]` does in the space view. */
.join-target tonk-sigil {
    width: 6rem;
    height: 6rem;
    flex: none;
}

.join-subject {
    /* Long DIDs would otherwise blow out the card. Show the
       full value via the `title` attribute on hover. */
    word-break: break-all;
    font-size: 0.85rem;
}

/* Invite dialog's link row: read-only URL field on the left
   (grows to fill), copy button on the right (shrinks to its
   natural square). Without explicit flex sizing, WA components
   each render `display: block` and end up half-and-half — we
   force the input to grow and the button to stay compact. */
/* Invite dialog body: hero sigil on the left, prose + link
   row stacked on the right. The sigil identifies the space
   the link targets so the user has a visual confirmation of
   which space they're sharing. */
.invite-body {
    display: flex;
    align-items: stretch;
    gap: var(--wa-space-l);
}
.invite-body-sigil {
    flex: 0 0 auto;
    width: 8rem;
    align-self: stretch;
    /* Override the sigil baseline's `aspect-ratio: 1/1` so the
       hero stretches to the body's height instead of forcing a
       fixed square that leaves blank space below. */
    aspect-ratio: auto;
}
.invite-body-stack {
    flex: 1 1 auto;
    min-width: 0;
}

/* Invite link row inside the dialog body. Read-only URL field
   on the left (grows), copy button on the right (natural size).
   Without explicit flex sizing, WA components each render
   `display: block` and end up half-and-half. */
.invite-link {
    display: flex;
    align-items: end;
    gap: var(--wa-space-xs);
}
.invite-link-input {
    flex: 1 1 auto;
    min-width: 0;
}
.invite-link-copy {
    flex: 0 0 auto;
}

/* ── <account-settings> — the account settings panel. One element, two
   seats: the Hub's account tab
   embeds it in-column, and the space route raises it in a <tonk-dialog>
   from the FAB's settings row — so its styling lives here, where both
   documents receive it. */
account-settings { display: block; }
account-settings .s-body { min-width: 0; min-height: min(408px, 55vh);
    max-height: 60vh; overflow: auto; padding: 8px 18px 18px;
    background: var(--panel); border: 1px solid var(--ring);
    font-family: var(--sans); font-weight: 400; font-size: 13.5px;
    line-height: 1.4; text-transform: none; color: var(--soft); }
/* the account page's menu is rows under the bar, not a panel: no box */
account-settings[data-pane="menu"] .s-body { min-height: 0; max-height: none;
    overflow: visible; padding: 0; background: none; border: 0;
    font: inherit; text-transform: inherit; color: inherit; }
account-settings .pane[hidden] { display: none; }
account-settings .srowd { display: flex; justify-content: space-between;
    align-items: baseline; gap: 16px; padding: 9px 0; }
account-settings .srowd b { font-family: var(--cond); font-weight: 600;
    font-size: 13.5px; color: var(--ink); text-align: right;
    white-space: nowrap; }
account-settings .srowd b.lft { min-width: 0; text-align: left;
    white-space: normal; overflow-wrap: anywhere; }
account-settings .sect { margin-top: 18px; padding-bottom: 7px;
    font: 600 13px/1 var(--cond); letter-spacing: 0.02em;
    text-transform: lowercase; color: var(--ink);
    border-bottom: 1px solid var(--sep); }
account-settings .expl { margin: 6px 0 0; font-size: 13px;
    line-height: 1.55; max-width: 460px; }
/* the underline IS the link */
account-settings .cta { font: 600 13px/1 var(--cond);
    letter-spacing: 0.02em; color: var(--ink); text-transform: lowercase;
    white-space: nowrap; display: inline-block; cursor: pointer;
    text-decoration: underline; text-underline-offset: 3px; }
account-settings .cta:hover { text-decoration-thickness: 2px; }
/* a verb is a button for the keyboard's sake; it wears the word alone */
account-settings button.cta { appearance: none; -webkit-appearance: none;
    background: none; border: 0; margin: 0; padding: 0; text-align: right; }
account-settings .srowd > .cta { flex: none; }
account-settings :is([data-agent-connections], [data-terminal-connections])[hidden] { display: none; }
account-settings :is([data-agent-connections], [data-terminal-connections]) { margin-top: 18px; }
account-settings :is([data-connections-list], [data-terminal-connections-list]) { display: grid; gap: 7px; margin-top: 12px; }
account-settings .connection-record { min-width: 0; padding: 12px;
    background: var(--card); box-shadow: 0 0 0 1px var(--ring); }
account-settings .connection-record .expl { overflow-wrap: anywhere; }
account-settings :is([data-agent-connections], [data-terminal-connections]) .srowd { align-items: center; flex-wrap: wrap;
    padding: 0; gap: 8px; }
account-settings :is([data-agent-connections], [data-terminal-connections]) button.cta { min-width: 44px; min-height: 44px; }
account-settings :is([data-agent-connections], [data-terminal-connections]) button.cta:disabled { opacity: .5; cursor: default; }
account-settings :is([data-agent-connections], [data-terminal-connections]) :focus-visible { outline: 2px solid var(--ink);
    outline-offset: 3px; }
/* A real input with a stable field surface. Keep the fill bounded to the
   editable value so the name reads as a control rather than floating at the
   end of the row. The unlayered rules also beat Web Awesome's
   `@layer wa-native` skin. */
account-settings .sname-seat {
    flex: 1; min-width: 0;
    display: flex; align-items: center; justify-content: flex-end; }
account-settings .sname {
    appearance: none; -webkit-appearance: none;
    box-sizing: border-box; background: var(--sep);
    border: 0; border-radius: 0;
    outline: none; margin: 0; padding: 6px 8px 7px;
    font-family: var(--cond); font-weight: 600; font-size: 13.5px;
    line-height: 1;
    color: var(--ink); caret-color: var(--ink); text-align: right;
    cursor: text; flex: 0 1 28ch; min-width: 6ch; max-width: 100%; }
/* The native caret is the active-edit affordance; keep the resting surface
   stable instead of adding a ring around it. There is deliberately no
   resting block cursor: an unfocused field must read as settled, not as
   though it is still taking input. */
account-settings .sname:focus-visible { box-shadow: none; }
/* the confirm clusters — the page dims, the surface never. the body is
   set in the reading face at weight 600, ink on card, like the hub's
   space-removal confirm; the arming row is the gate: type the displayed
   phrase, and the solid verb stays off until it matches. */
/* the confirm is a solid card in the page's own scheme, like the mock's
   modal: the page dims, the surface never, and nothing reads through.
   The cluster element wears the FABB skin, pinned light, so its tokens
   are re-pointed at the page's here. */
account-settings tonk-dialog {
    --fabb-panel: var(--card); --fabb-ink: var(--ink);
    --fabb-ink-soft: var(--soft); --fabb-on-ink: var(--on-ink);
    --fabb-ring: var(--ring); --fabb-sep: var(--sep);
    --fabb-hover: var(--wash); --fabb-press: var(--wash);
    --fabb-dim: var(--dim); }
account-settings .confirm-body { margin: 0; min-width: 0; overflow-wrap: anywhere; font-family: var(--sans);
    font-weight: 600; font-size: 13.5px; line-height: 1.6;
    text-transform: none; color: var(--ink); }
account-settings .confirm-body > span { display: block; margin-bottom: 10px; }
account-settings .confirm-body > span:empty { display: none; }
account-settings .armrow { margin-top: 14px; font: 600 13px/1.6 var(--cond);
    letter-spacing: 0.02em; color: var(--soft);
    display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
account-settings .armrow b { color: var(--ink); font-weight: 600;
    text-transform: none; }
account-settings .armfield { appearance: none; -webkit-appearance: none;
    display: inline-block; min-width: 14ch; max-width: 100%;
    margin: 0; padding: 0 1px 2px; border: 0;
    border-bottom: 1.5px solid var(--ink); border-radius: 0;
    outline: none; background: transparent; caret-color: var(--ink); color: var(--ink);
    font: inherit; text-transform: none; white-space: nowrap; }
account-settings .armfield:focus-visible { box-shadow: none; }
/* an unarmed verb stays visibly off, not merely inert */
account-settings .m-go:disabled { opacity: 0.4; cursor: not-allowed; }
account-settings .m-go:disabled:hover { background: var(--ink); }
/* the link pane's answer is a fused action run, like a confirm's foot */
account-settings .link-foot { display: flex; justify-content: flex-end;
    gap: 0; margin-top: 18px; }
account-settings .link-foot .m-cancel { box-shadow: 0 0 0 1px var(--ring); }
/* where a ceremony got to, under the rows that asked for it */
account-settings [data-ceremony-status] { color: var(--ink); }
account-settings [data-ceremony-status][hidden] { display: none; }

/* CLI approval follows the Hub's row stack rather than the settings sheet. */
account-settings .s-body:has([data-link-approval]:not([hidden])) {
    min-height: 0; max-height: none; overflow: visible; padding: 0;
    background: none; border: 0; position: relative;
}
account-settings [data-link-approval]:not([hidden]) {
    display: flex; flex-direction: column; gap: 7px;
}
account-settings [data-link-approval] > :is(.sect, .srowd, .expl),
account-settings .s-body:has([data-link-approval]:not([hidden])) > [data-ceremony-status]:not([hidden]) {
    box-sizing: border-box; margin: 0; padding: 10px 16px;
    min-height: 36px; max-width: none; background: var(--card);
    border: 0; box-shadow: 0 0 0 1px var(--ring);
}
account-settings [data-link-approval] .srowd b {
    min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: right;
}
account-settings [data-link-approval] .link-foot { margin: 0; }
account-settings [data-link-approval] .link-foot button {
    font: 600 13px/1 var(--cond); letter-spacing: 0.02em;
}
account-settings .s-body:has([data-link-approval]:not([hidden])) > [data-ceremony-status]:not([hidden]) {
    margin-top: 7px;
}
account-settings [data-custody-seat] { display: none; }
account-settings[data-passkey-screen] [data-link-approval],
account-settings[data-passkey-screen] .s-body:has([data-link-approval]:not([hidden])) > [data-ceremony-status]:not([hidden]) { display: none; }
account-settings[data-passkey-requested] [data-custody-seat] {
    display: block; position: absolute; top: 0; left: 0; width: 100%; height: 180px;
    pointer-events: none;
}
account-settings[data-passkey-screen] [data-custody-seat] {
    display: block; position: static; height: 180px;
}

/* Map our Web Awesome theme into the `<tonk-code>` element's
   public CSS variables. The element ships GitHub-flavored defaults
   that work standalone in light/dark; this block plugs it into our
   own design system so the editor surface picks up the same
   surface, border, brand, and typography tokens as the rest of
   the UI. The element doesn't depend on Web Awesome directly —
   the variables below are the contract.

   Web Awesome already toggles `wa-light` / `wa-dark` classes
   based on `prefers-color-scheme`, and its semantic tokens
   redefine themselves under each mode, so a single mapping is
   sufficient — no per-mode override needed here. */
/* `tonk-notation` shares this palette — it renders a read-only
   view of dialog-yaml notation with the same class-tagged spans
   the editor uses, so the colors have to come from the same
   variables. Listing it here (rather than re-declaring the
   palette on `:root`) keeps the contract tight: the palette
   lives where the editor expects it, and consumers opt in by
   adding their selector to this group. */
tonk-code,
tonk-notation,
.query-tree,
.query-notation {
    /* Both evaluate-result views (listed `.query-notation` and
       grouped `.query-tree`) and the `<tonk-notation>` inspector
       slide share the dialog-yaml editor palette so they read
       identically to each other and to the editor. Match
       `wa-input` so the surface reads as a form field rather than
       a panel. The form-control tokens are the right semantic —
       they describe "what an input surface looks like" and stay
       aligned with the rest of the form theme if Web Awesome ever
       retunes them. */
    --tonk-code-bg: var(--wa-form-control-background-color);
    --tonk-code-fg: var(--wa-color-text-normal);
    --tonk-code-fg-muted: var(--wa-color-text-quiet);
    /* No frame in this embedding — the editor sits inside a
       `<wa-details>` body that already provides the visual
       grouping, so a second border doubles up. The element's
       resting and focused borders both go transparent; focus
       state is signalled by the cursor alone. */
    --tonk-code-border: transparent;
    /* Active-line stripe — visible but quiet. The wa neutral
       fill is too saturated against the form-control background;
       a low-alpha tint of the foreground reads as a subtle band
       on both light and dark themes. */
    --tonk-code-active-line: color-mix(
        in srgb,
        var(--wa-color-text-normal) 2%,
        transparent
    );
    --tonk-code-gutter-bg: color-mix(
        in srgb,
        var(--wa-color-text-normal) 4%,
        transparent
    );
    --tonk-code-cursor: var(--wa-color-brand-fill-loud);
    /* Selection / bracket match — color-mixed alpha tints rather
       than raw `--wa-color-brand-fill-quiet`, because wa-details
       overrides several `--wa-color-*-fill-*` tokens to
       `transparent` in its body slot. The brand-fill-loud token
       (a brand color, not a surface) is unaffected, so we mix
       against it for predictable visibility regardless of
       ancestor wa-component scoping. */
    --tonk-code-selection: color-mix(
        in srgb,
        var(--wa-color-brand-fill-loud) 25%,
        transparent
    );
    --tonk-code-bracket-match-bg: color-mix(
        in srgb,
        var(--wa-color-brand-fill-loud) 18%,
        transparent
    );
    --tonk-code-bracket-match-border: var(--wa-color-brand-border-loud);
    /* Suppress the element's default focus halo. wa-input doesn't
       paint one either, and rendering it inside a wa-details body
       made the editor look like it was demanding attention even
       when the user wasn't focused on it. The cursor itself
       already signals focus. */
    --tonk-code-focus-ring: transparent;

    /* Lint / hover tooltip surfaces. We deliberately avoid
       `--wa-color-surface-raised` here: `wa-details` (and a few
       other wa components) override it to `transparent` inside
       their slot scopes, which would make our tooltips
       see-through. A color-mix against the page foreground gives
       a solid, raised-looking surface that survives any wa
       ancestor scoping. */
    --tonk-code-tooltip-bg: color-mix(
        in srgb,
        var(--wa-color-text-normal) 12%,
        var(--wa-form-control-background-color)
    );
    --tonk-code-tooltip-fg: var(--wa-color-text-normal);
    --tonk-code-tooltip-border: color-mix(
        in srgb,
        var(--wa-color-text-normal) 18%,
        transparent
    );

    /* Syntax — muted Bauhaus palette.
       Kandinsky's triad (red→square, yellow→triangle, blue→circle)
       reduced to mid-chroma, mid-lightness values that hold up
       against both `#ffffff` and `#101113` surfaces. Picked
       deliberately so each color clears WCAG AA for body text on
       both extremes; saturated Bauhaus pigments don't.

       The named slots below are the *palette* — pure color, no
       semantics. The code-role and dialect-role assignments
       further down map roles onto the palette so reassigning a
       role (e.g. point variables at red instead of blue) is a
       one-line change. */
    /* The Bauhaus palette, named by Kandinsky's color→shape pairing so
       the vocabulary carries the meaning, not just the hue:
         - triangle (yellow) → sharp, active, structural, eye-catching
         - square   (red)    → grounded, literal, material substance
         - circle   (blue)   → abstract, calm, receding
         - closure  (black)  → terminal/neutral ground (Kandinsky's
                                musical "closure")
       Roles below map onto these, so re-pointing a role at a different
       shape is a one-line change. */
    --tonk-triangle: #c89a2b; /* muted ochre */
    --tonk-square: #b94a3d; /* muted brick */
    --tonk-circle: #3d6da8; /* muted slate-blue */
    --tonk-closure: #7a7268; /* warm neutral ground */
    --tonk-alarm: #a8302a; /* hotter than the square red */

    /* Color-named aliases (legacy; prefer the shape names above). */
    --tonk-bauhaus-yellow: var(--tonk-triangle);
    --tonk-bauhaus-red: var(--tonk-square);
    --tonk-bauhaus-blue: var(--tonk-circle);
    --tonk-bauhaus-grey: var(--tonk-closure);
    --tonk-bauhaus-alarm: var(--tonk-alarm);

    /* Code-role assignments. The Bauhaus school's own logic for
       the primaries:
         - yellow / triangle → keys (loud, structural, eye-catching)
         - red    / square   → strings (grounded, literal substance)
         - blue   / circle   → numbers (abstract, receding) */
    --tonk-code-key: var(--tonk-bauhaus-yellow);
    --tonk-code-string: var(--tonk-bauhaus-red);
    --tonk-code-number: var(--tonk-bauhaus-blue);
    --tonk-code-comment: var(--tonk-bauhaus-grey);
    --tonk-code-punctuation: var(--wa-color-text-normal);
    --tonk-code-error: var(--tonk-bauhaus-alarm);

    /* Dialect decorations (dialog-yaml). Choices follow the
       Kandinsky logic: variables (abstract holes) → blue;
       bookmark names (structural references to a defined thing)
       → yellow, same as keys; effect marker (consequential, "this
       will change state") → alarm red. Entity URIs and the `.`
       sigil ride a quiet text color to read as "reference" without
       competing with the role color of the surrounding token. */
    --tonk-code-variable: var(--tonk-bauhaus-blue);
    --tonk-code-entity: var(--wa-color-text-quiet);
    --tonk-code-name: var(--tonk-bauhaus-yellow);
    --tonk-code-name-sigil: var(--wa-color-text-quiet);
    --tonk-code-effect: var(--tonk-bauhaus-alarm);

    /* Typography */
    --tonk-code-font: var(--wa-font-family-code);
    --tonk-code-font-size: var(--wa-font-size-s);
    --tonk-code-radius: 0; /* match the brutalist palette's square corners */
}
/* The element's baseline repaints the host border on
   `:focus-within` (border-color → `--tonk-code-cursor`). Since
   we've zeroed the resting border, the focus-state repaint would
   suddenly draw a blue frame on focus that isn't there at rest —
   visually jarring. Pin it transparent at both states. The text
   cursor itself still uses `--tonk-code-cursor` for its color. */
tonk-code:focus-within {
    border-color: transparent;
}

/* Editor + floating play button. The button sits half-overlapping
   the editor's bottom edge (Observable-style) so it reads as
   attached to the editor without taking up its own row. The
   `is-visible` class is toggled by Leptos when the buffer parses
   cleanly; we animate opacity so the button doesn't pop in/out
   abruptly while the user types. */
/* Notebook cells live in a vertical stack. Each cell is its
   own `<form>` (`.branch-yaml-query`); sealed cells dim back
   so the eye lands on the active one at the bottom — same idea
   as Observable's notebook layout. */
.inspector-debug {
    width: 100%;
    color: var(--ink);
    font-family: var(--sans);
}

.inspector-debug__disclosure {
    background: var(--frost-solid);
    box-shadow: 0 0 0 1px var(--ring);
}

.inspector-debug__summary {
    min-height: 44px;
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr) auto 18px;
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    cursor: pointer;
    list-style: none;
    transition-property: background-color;
    transition-duration: 150ms;
    transition-timing-function: var(--ease);
}

.inspector-debug__summary:hover {
    background: var(--wash);
}

.inspector-debug__summary:focus-visible {
    outline: 0;
    box-shadow: inset 0 0 0 2px var(--ring);
}

.inspector-debug__summary::-webkit-details-marker {
    display: none;
}

.inspector-debug__summary::after {
    content: "⌄";
    width: 18px;
    justify-self: center;
    color: var(--soft);
    font: 18px/1 var(--sans);
    text-align: center;
}

.inspector-debug__disclosure[open] > .inspector-debug__summary::after {
    content: "⌃";
}

.inspector-debug__summary span,
.inspector-debug__summary strong,
.inspector-debug__summary small,
.inspector-debug__actions button,
.inspector-debug__copy,
.inspector-debug__row dt,
.inspector-debug__probe-error dt {
    font-family: var(--cond);
    font-size: 13px;
    line-height: 1;
    letter-spacing: 0.02em;
    text-transform: lowercase;
}

.inspector-debug__summary span,
.inspector-debug__summary small {
    color: var(--soft);
    font-weight: 500;
}

.inspector-debug__summary strong {
    font-weight: 600;
}

.inspector-debug__summary code,
.inspector-debug__row code {
    min-width: 0;
    font: 11px/1.45 var(--mono);
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

.inspector-debug__summary small {
    white-space: nowrap;
}

.inspector-debug__body {
    border-block-start: 1px solid var(--sep);
    padding: 6px 12px 10px;
}

.inspector-debug__actions {
    display: flex;
    justify-content: flex-end;
    gap: 4px;
    min-height: 40px;
    margin-block-end: 4px;
}

.inspector-debug__actions:empty {
    display: none;
}

.inspector-debug__actions button,
.inspector-debug__copy {
    position: relative;
    min-width: 44px;
    min-height: 40px;
    border: 0;
    border-radius: 0;
    padding: 0 10px;
    background: transparent;
    color: var(--ink);
    cursor: pointer;
    font-weight: 600;
    transition-property: background-color, scale;
    transition-duration: 150ms;
    transition-timing-function: var(--ease);
}

.inspector-debug__actions button:hover,
.inspector-debug__copy:hover {
    background: var(--wash);
}

.inspector-debug__actions button:focus-visible,
.inspector-debug__copy:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: -3px;
}

.inspector-debug__actions button:active,
.inspector-debug__copy:active {
    scale: 0.96;
}

.inspector-debug__actions button:disabled {
    cursor: wait;
    opacity: 0.55;
}

.inspector-debug__rows {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    margin: 0;
}

.inspector-debug__row,
.inspector-debug__probe-error {
    min-height: 40px;
    display: grid;
    grid-template-columns: 120px minmax(0, 1fr);
    align-items: stretch;
    border-block-end: 1px solid var(--sep);
}

.inspector-debug__row dt,
.inspector-debug__probe-error dt {
    display: flex;
    align-items: center;
    margin: 0;
    padding-inline-end: 8px;
    color: var(--soft);
    font-weight: 500;
}

.inspector-debug__row dd,
.inspector-debug__probe-error dd {
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    margin: 0;
    padding-inline-start: 8px;
}

.inspector-debug__probe-error dd {
    display: flex;
    padding-inline-end: 8px;
    color: var(--soft);
    font-size: 12px;
    line-height: 1.45;
    text-wrap: pretty;
}

.inspector-debug__copy {
    align-self: stretch;
    width: 56px;
    min-width: 56px;
    margin-inline-start: 8px;
    padding-inline: 8px;
    color: var(--soft);
}

.inspector-debug__copy.is-copied {
    background: var(--wash);
    color: var(--ink);
}

.inspector-debug__notice {
    min-height: 40px;
    display: flex;
    align-items: center;
    color: var(--soft);
    font-size: 12px;
    line-height: 1.45;
    text-wrap: pretty;
}

.inspector-debug__notice.is-error,
.inspector-debug__transient.is-error {
    color: var(--ink);
}

.inspector-debug__transient {
    min-height: 20px;
    padding-block-start: 6px;
    color: var(--soft);
    font: 500 12px/1.4 var(--cond);
}

@media (max-width: 720px) {
    .inspector-debug__summary {
        grid-template-columns: auto auto minmax(0, 1fr) 18px;
        padding-block: 7px;
    }

    .inspector-debug__summary small {
        grid-column: 1 / 4;
        grid-row: 2;
    }

    .inspector-debug__summary::after {
        grid-column: 4;
        grid-row: 1 / span 2;
    }

    .inspector-debug__actions button {
        flex: 1;
    }

    .inspector-debug__row,
    .inspector-debug__probe-error {
        grid-template-columns: 84px minmax(0, 1fr);
    }
}

.branch-cells {
    display: flex;
    flex-direction: column;
}
.branch-yaml-query.cell-sealed {
    /* Sealed cells visually recede — they're history, not
       affordance. Active cell stays at full contrast. */
    opacity: 0.7;
}
.branch-yaml-query.cell-sealed:hover {
    /* Restore on hover so the user can read the source clearly
       when reviewing past commits. */
    opacity: 1;
}

.evaluate-editor {
    position: relative;
    /* Reserve space so the floating button doesn't overlap
       whatever follows (the result callout). */
    margin-block-end: var(--wa-space-l);
}
.evaluate-editor tonk-code {
    display: block;
    width: 100%;
}
.evaluate-play {
    position: absolute;
    /* Half-overlap the editor's bottom edge. */
    bottom: calc(-1 * var(--wa-space-m));
    /* Keep the button on the right; mirrors Observable's
       `Run`/`Stop` placement. */
    right: var(--wa-space-s);
    z-index: 1;
    /* Hidden by default; the `is-visible` class fades it in
       when the buffer has at least one parseable expression. */
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease-out;
}
.evaluate-play.is-visible {
    opacity: 1;
    pointer-events: auto;
}

/* The "no view for this model" notice.
 *
 * A full callout is right on a page, where landing on the raw default is a
 * surprise worth explaining. In a notebook's CELL OUTPUT it is noise: you
 * ran a query to see data, and the default rendering IS the data — so the
 * notice explains something nobody asked about while taking more room than
 * the result it labels.
 *
 * Everywhere else it collapses to a thin bar: the same information, read at
 * a glance, without a paragraph-sized box around one sentence. */
[data-tonk-display-default-notice] {
  --wa-callout-padding: var(--wa-space-2xs, 0.25rem) var(--wa-space-xs, 0.5rem);
  font-size: var(--wa-font-size-xs, 0.75rem);
  line-height: 1.3;
  margin-block-end: var(--wa-space-2xs, 0.25rem);
}
[data-tonk-display-default-notice]::part(icon) {
  font-size: var(--wa-font-size-xs, 0.75rem);
}
/* Cell output drops it entirely — but that rule lives with the notebook's
   own injected styles, since this stylesheet does not cross into the
   editor's shadow root where cells render. */

/* Result-view switcher — a `<wa-tab-group>` with the tab strip
   on the inline-end side. The active-tab indicator and the
   selected tab itself follow the page foreground (black in
   light mode, white in dark) rather than WA's default brand
   blue, so the switcher reads as neutral chrome. */
.evaluate-tabs {
    --indicator-color: var(--wa-color-text-normal);
}
.evaluate-tabs wa-tab[active]::part(base) {
    color: var(--wa-color-text-normal);
}

/* Table view — one `<table>` per query block. */
.query-tables {
    width: 100%;
}
.query-table table {
    border-collapse: collapse;
    width: 100%;
    font-size: var(--tonk-code-font-size);
}
.query-table th,
.query-table td {
    padding: var(--wa-space-2xs) var(--wa-space-s);
    text-align: start;
    border-block-end: 1px solid var(--wa-color-surface-border);
    word-break: break-word;
}
/* Headers stand clearly apart from the body — bolder, kept in
   their natural case. */
.query-table th {
    font-weight: 700;
}

/* The concept-name header sits in a span painted in inverse
   colors (page foreground as background) — the same
   headline-cover treatment the page's `h1` uses. The cover hugs
   the text, not the whole cell. It is the table's title, so it
   reads a notch larger than the plain field headers. */
.query-table th.query-table-this > span {
    background: var(--wa-color-text-normal);
    color: var(--wa-color-neutral-fill-quiet);
    font-size: var(--wa-font-size-l);
    padding: var(--wa-space-3xs) var(--wa-space-2xs);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}
/* The entity URI is monospaced and hard-clipped to its last 8
   characters (the unique suffix; the `did:key:z6Mk…` prefix is
   boilerplate). The cell is a `<wa-copy-button>` whose trigger
   span does the clip — clicking it copies the full URI.
   `direction: rtl` clips from the start with no ellipsis; a
   leading LRM keeps the glyphs left-to-right. */
.query-table td.query-table-this {
    font-family: var(--wa-font-family-code, ui-monospace, monospace);
}
.query-table td.query-table-this wa-copy-button {
    cursor: pointer;
}
.query-table td.query-table-this span {
    display: inline-block;
    width: 8ch;
    overflow: hidden;
    white-space: nowrap;
    direction: rtl;
    text-align: start;
    vertical-align: bottom;
}
.query-table td.query-table-this span::before {
    content: "\200E"; /* LRM — preserves left-to-right glyph order */
}

/* The evaluate-result comparison panel.
   `.branch-row` re-defines `--wa-color-surface-default` /
   `--wa-color-surface-raised` as transparent so the row blends
   into the page; that cascade reaches the `<wa-comparison>`
   inside, leaving its panels and handle invisible. Restore
   solid surfaces here. */
.evaluate-comparison {
    /* Reset the surface vars to the page-level defaults so the
       comparison's slot panels render with a real fill. */
    --wa-color-surface-default: var(--wa-color-neutral-fill-quiet);
    --wa-color-surface-raised: var(--wa-color-neutral-fill-normal);

    display: block;
    /* Comparison panels are absolute inside the slider; without
       a height on the host the whole thing collapses to 0. */
    min-height: 18rem;
    background: var(--wa-color-surface-default);
    border: 1px solid var(--wa-color-surface-border);
    border-radius: var(--wa-border-radius-s);
}
.evaluate-comparison::part(handle) {
    /* Force a visible handle — without an explicit fill the
       SVG inside renders against whatever happens to be behind
       it, which is often the same color as the comparison panel. */
    background: var(--wa-color-text-normal);
    color: var(--wa-color-neutral-fill-quiet);
}
.evaluate-comparison::part(divider) {
    background: var(--wa-color-text-normal);
}
.evaluate-side {
    /* Each slot fills its slider half. Padding gives the
       contents breathing room from the divider. */
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    padding: var(--wa-space-s);
    background: var(--wa-color-surface-default);
    color: var(--wa-color-text-normal);
    overflow: auto;
}
.evaluate-side-before {
    /* Dim the pre-commit side so the user can tell at a glance
       which is "old". Slightly desaturated + lower opacity
       reads as "previous" without making the text hard to
       read. */
    color: var(--wa-color-text-quiet);
    background: var(
        --wa-color-surface-lowered,
        var(--wa-color-neutral-fill-quiet)
    );
}
.evaluate-revision {
    /* Sits above the match list as a one-line tag. */
    margin-block-end: var(--wa-space-2xs);
}

/* ── Board route height anchor ────────────────────────────────
 * The board's strip/column/tile layout lives WITH the view, in
 * the `board-view` / `column-view` `display:` templates (see
 * core.yaml). Those templates size the strip with
 * `block-size: 100%`, which only resolves against a host that
 * establishes a real height — that's the route's job, and the
 * only board CSS that stays here.
 *
 * This route mounts `tonk-board` inside `main.board-view`, so the
 * height chain runs `main → tonk-board → tonk-display → tonk-view →
 * tonk-strip`. The bare
 * `/display` route establishes its own chain (see `.display-route`
 * above); both bottom out at `tonk-strip`, which the view styles
 * from there down. See `plan/tonk-board.md`.
 */
.board-view {
    /* Fill the viewport; no padding so the strip's graph-paper
       background reaches the edges. */
    padding: 0;
    margin: 0;
    block-size: 100vh;
    block-size: 100dvh;
    inline-size: 100%;
}

/* Every custom-element layer between <main.board-view> and the
   strip defaults to `display: inline`. Force the whole chain into
   the layout flow so block-size: 100% propagates from the
   viewport down to the strip (the view template takes over from
   the strip downward). */
.board-view tonk-board,
.board-view > tonk-board > tonk-display,
.board-view > tonk-board > tonk-display > tonk-view {
    display: block;
    block-size: 100%;
    min-block-size: 0;
}


/* ── Notebook index ─────────────────────────────────────────────────────
   `/space/{repo}/notebook` — every notebook in the space. A list of links,
   not cards: the index is a way through to a notebook, not a place to
   linger, so it should read in one glance. */
.notebook-index {
  max-width: 48rem;
  margin: 0 auto;
  padding: var(--wa-space-l, 1.5rem);
}
.notebook-index h1 {
  font-size: var(--wa-font-size-xl, 1.5rem);
  margin: 0 0 var(--wa-space-m, 1rem);
}
/* The row this draws is the same one the notebook page hides in its data
   pane (`.notebook-data[hidden]`), so the link is invisible there and shown
   only here, where the index gives it a home. */
.notebook-index .notebook-row { display: block; }
.notebook-index__item {
  display: block;
  padding: var(--wa-space-s, 0.75rem) var(--wa-space-m, 1rem);
  border-radius: var(--wa-border-radius-m, 0.375rem);
  color: var(--wa-color-text-normal);
  text-decoration: none;
}
.notebook-index__item:hover {
  background: var(--wa-color-neutral-fill-quiet);
}
.notebook-index__title { font-weight: 500; }
.notebook-index .notebook-block-rows { display: none; }

/* The switcher. The heading IS the control: an empty document showing
   `# `, with the suggestion list under it. No field, no button — what you
   type is the title, and the list says what pressing Enter will do. */
/* The rows are DATA, not a list.
   `<tonk-display model=tonk:notebook>` renders one link per notebook inside
   the switcher; those links are what the suggestions are built from, and
   what the page falls back to if the script never loads. Once the switcher
   is running they must not also render as a list above the input, which is
   the "why am I looking at Scratch" of it. */
/* The rows are the switcher's DATA, not a list to read: they feed the
   suggestions and are the no-script fallback. Hidden once the draft
   notebook mounts — keyed on the notebook, since that is what the view
   renders. */
tonk-notebook-index:has(tonk-notebook[draft]) .notebook-index,
tonk-notebook-index:has(tonk-notebook[draft]) .notebook-index__item {
  display: none;
}
tonk-notebook-index {
  display: block;
  max-width: 48rem;
  margin: 0 auto;
  padding: var(--wa-space-l, 1.5rem);
}

.notebook-switcher__input {
  display: block;
  margin: 0 0 var(--wa-space-s, 0.75rem);
  font-size: var(--wa-font-size-xl, 1.5rem);
}
.notebook-switcher__panel {
  margin: 0 0 var(--wa-space-l, 1.5rem);
  border: 1px solid var(--wa-color-surface-border);
  border-radius: var(--wa-border-radius-m, 0.375rem);
  overflow: hidden;
}
.notebook-switcher__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--wa-space-m, 1rem);
  padding: var(--wa-space-s, 0.75rem) var(--wa-space-m, 1rem);
  cursor: pointer;
}
.notebook-switcher__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* What Enter will do, quiet and out of the way: the name is what you
   read, the verb only says which of the two things happens. */
.notebook-switcher__verb {
  flex: 0 0 auto;
  color: var(--wa-color-text-quiet);
  font-size: var(--wa-font-size-xs, 0.75rem);
}
.notebook-switcher__row--active {
  background: var(--wa-color-neutral-fill-quiet);
}
/* The matched characters, so the reason a row is here is visible. */
.notebook-switcher__row mark {
  background: none;
  color: var(--wa-color-brand-fill-loud, currentColor);
  font-weight: 600;
}
/* The create row is an ordinary row: same shape, same weight. Only its
   verb differs, which is the point — creating is a pick, not a special
   case parked at the bottom. */

/* Rows read as a list, separated rather than boxed: the index is a way
   through to a notebook, not a place to linger. */
.notebook-index__item + .notebook-index__item {
  border-top: 1px solid var(--wa-color-surface-border);
}
.notebook-index__empty {
  margin: 0;
  padding: var(--wa-space-m, 1rem);
  color: var(--wa-color-text-quiet);
  font-size: var(--wa-font-size-s, 0.875rem);
}
.notebook-index__empty kbd {
  font-family: var(--wa-font-family-code, monospace);
  font-size: 0.9em;
  padding: 0.1em 0.35em;
  border: 1px solid var(--wa-color-surface-border);
  border-radius: var(--wa-border-radius-s, 0.25rem);
}
/* ── The ceremony cluster ──────────────────────────────────────────
 *
 * A column of blocks over a dimmed page, from `fabb/onboard.html`:
 * "the page dims, the surface never". Rows settle into records as
 * their step completes, so what has been answered stays legible above
 * what is being answered now.
 *
 * Written as classes rather than against one element's id: the
 * registration cluster is the first surface built this way, not the
 * only one it suits. `.ed` in particular is a bare input wearing the
 * row's own type, and any row that takes a value wants it.
 */
  /* The cluster, from `fabb/onboard.html`: rows stack over a dimmed
     page and the surface itself never dims. Each row settles into a
     record as its step completes, so what you have already answered
     stays legible above what you are answering now.

     Not a `wa-dialog`: this is a column of blocks, and the way out is a
     bare word at the foot rather than a titlebar close. */
  /* The dim carries the whole separation — the surface never dims — so
     it has to actually hide what is behind it. A light-theme value
     hardcoded here read as nearly transparent against the dark theme,
     and the page showed through and collided with the rows. */
  #tonk-register {
    position: fixed; inset: 0; z-index: 21;
    box-sizing: border-box;
    inline-size: auto; max-inline-size: none;
    block-size: auto; max-block-size: none;
    margin: 0; padding: 0; overflow: auto;
    border: 0; border-radius: 0;
    background: transparent; box-shadow: none;
    color: inherit;
  }
  #tonk-register[open] { display: block; }
  /* Suspended = the account tab it is a page of went to the background:
     hidden with its state intact, shown again on the switch back. After
     the [open] rule so it wins their specificity tie. */
  #tonk-register[data-suspended] { display: none; }
  #tonk-register::backdrop { background: var(--dim); backdrop-filter: blur(3px); }
  /* Hub account work is a trusted modal over the spaces collection. */
  html:has(#tonk-register[data-anchored][open]:not([data-suspended])),
  body:has(#tonk-register[data-anchored][open]:not([data-suspended])) {
    overflow: hidden;
  }
  #tonk-register[data-anchored] {
    overflow: hidden;
    overscroll-behavior: none;
  }
  #tonk-register[data-anchored]::backdrop { backdrop-filter: none; }
  #tonk-register[data-anchored] .ocol {
    position: absolute; left: auto; top: var(--anchor-top, 86px);
    right: max(16px, calc(100vw - var(--anchor-left, 0px) - var(--anchor-width, 0px)));
    bottom: auto; margin: 0;
    width: min(360px, calc(100vw - 32px));
    max-height: calc(100dvh - 110px); overflow: auto;
    overscroll-behavior: none;
    display: flex; flex-flow: row wrap; align-content: flex-start;
    border: 1.5px solid var(--ink); border-radius: 25px;
    background: var(--modal, var(--card));
  }
  #tonk-register[data-anchored] .ostack { display: contents; }
  #tonk-register[data-anchored] .mblk { box-shadow: none; }
  #tonk-register[data-anchored] .m-head {
    box-sizing: border-box; flex: 0 0 100%;
    min-height: 48px; height: 48px; padding: 0 18px;
    align-items: center; gap: 16px;
    background: var(--wash); font-size: 17px;
  }
  #tonk-register[data-anchored] .fabb-task-disc {
    display: block; box-sizing: border-box;
    width: 18px; height: 18px; flex: 0 0 18px;
    background: var(--ink); border-radius: 50%;
  }
  #tonk-register[data-anchored] .fabb-task-title {
    margin-left: auto; text-align: right;
  }
  #tonk-register[data-anchored] .orow {
    box-sizing: border-box; flex: 0 0 100%;
    min-height: 48px; height: auto; padding: 18px 18px 14px;
    grid-template-rows: auto 48px; gap: 8px;
    background: var(--wash);
  }
  #tonk-register[data-anchored] .orow.editing { min-height: 104px; }
  #tonk-register[data-anchored] .orow.pre,
  #tonk-register[data-anchored] .obtn.pre {
    min-height: 0; height: 0; padding-top: 0; padding-bottom: 0;
  }
  #tonk-register[data-anchored] .orow.editing .k {
    color: var(--ink); font: 600 17px/1.1 var(--cond);
  }
  #tonk-register[data-anchored] .orow.editing .v { align-items: stretch; }
  #tonk-register[data-anchored] .orow.editing .ed {
    box-sizing: border-box; min-height: 48px; height: 48px;
    padding: 12px 14px 9px; border: 0;
    background: var(--wash); font: 400 18px/1.5 var(--cond);
  }
  #tonk-register[data-anchored] .orow.editing .ed:focus-visible {
    outline: 2px solid var(--ink); outline-offset: -2px;
  }
  #tonk-register[data-anchored] .oexp {
    order: 90; box-sizing: border-box; flex: 0 0 100%;
    min-height: 105px; margin: 0; padding: 10px 18px 18px;
    border-top: 0; background: var(--wash);
  }
  #tonk-register[data-anchored] .oexp p {
    font: 400 18px/1.5 var(--cond); letter-spacing: 0;
    text-transform: none;
  }
  #tonk-register[data-anchored] .ghost,
  #tonk-register[data-anchored] .obtn {
    box-sizing: border-box; flex: 0 0 50%;
    min-height: 48px; height: 48px; margin: 0;
    padding: 12px 18px; border: 0;
    font: 600 17px/1.1 var(--cond); letter-spacing: 0;
    text-decoration: none;
  }
  #tonk-register[data-anchored] .ghost { order: 100; justify-content: flex-start; text-align: left; }
  #tonk-register[data-anchored] .obtn {
    order: 101; align-items: center; justify-content: flex-end;
    opacity: 1;
  }
  @media (max-width: 600px) {
    #tonk-register[data-anchored] .ocol {
      top: auto; right: 16px; bottom: calc(82px + env(safe-area-inset-bottom, 0px));
      max-height: calc(100dvh - 110px - env(safe-area-inset-bottom, 0px));
    }
  }

  /* In-space account work is a trusted modal occupying the FABB's translated
     seat. The guest chrome remains mounted for restoration but is hidden. */
  #tonk-register[data-fabb-task] { overflow: hidden; }
  #tonk-register[data-fabb-task] .ocol {
    position: absolute;
    margin: 0;
    max-height: calc(100dvh - 32px);
    overflow: auto;
    border: 1.5px solid var(--ink);
    border-radius: 25px;
    background: var(--page);
  }
  #tonk-register[data-fabb-task] .ostack { gap: 0; }
  #tonk-register[data-fabb-task] .mblk,
  #tonk-register[data-fabb-task] .obtn { min-height: 48px; border-radius: 0; }
  #tonk-register[data-fabb-task] .ghost { margin: 0; min-height: 48px; }

  /* The study's chrome type: condensed, 600, 13px, slightly tracked and
     lowercased. Inheriting the page's body font instead made everything
     read thin and a size too large. */
  .tonk-ceremony {
    font-family: var(--cond);
    font-weight: 600;
    font-size: 13px;
    line-height: 1;
    letter-spacing: .02em;
    text-transform: lowercase;
    color: var(--ink);
  }
  .tonk-ceremony ::selection {
    background: var(--ink);
    color: var(--on-ink);
  }
  .tonk-ceremony .ocol {
    width: min(576px, calc(100vw - 32px));
    margin: 22vh auto 80px;
    display: flex; flex-direction: column;
  }
  .tonk-ceremony .ostack { display: flex; flex-direction: column; gap: 7px; }

  /* The dim does the separating, so the blocks need no blur of their own. */
  /* `--ring` is the study's edge: translucent ink, not a grey border
     token. Reaching for the app's `--wa-color-*` set instead put a
     near-white ring around every block in the dark theme, which nothing
     else in the design has. */
  .tonk-ceremony .mblk {
    background: var(--modal, var(--card));
    color: var(--ink);
    box-shadow: 0 0 0 1px var(--ring);
  }

  .tonk-ceremony .m-head {
    height: 36px; display: flex; align-items: flex-end;
    padding: 0 16px 9px; font-size: 13px; white-space: nowrap;
  }
  .tonk-ceremony .fabb-task-disc { display: none; }

  /* One row per step. `pre` is the folded state a row unfolds out of. */
  .tonk-ceremony .orow {
    position: relative; height: 36px;
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 16px; padding: 0 10px 9px 16px; overflow: hidden;
    transition: height .4s var(--ease),
                opacity .4s var(--ease),
                padding-top .4s var(--ease),
                padding-bottom .4s var(--ease);
  }
  .tonk-ceremony .orow.pre,
  .tonk-ceremony .obtn.pre {
    height: 0 !important; opacity: 0;
    padding-top: 0; padding-bottom: 0; pointer-events: none;
  }
  .tonk-ceremony .orow .k {
    color: var(--soft);
    white-space: nowrap; display: flex; align-items: flex-end; gap: 8px;
  }
  .tonk-ceremony .orow .v {
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; text-align: right;
    display: flex; align-items: flex-end; justify-content: flex-end;
    flex: 1;
  }
  /* Descender room for the clip, handed straight back so the seat holds. */
  .tonk-ceremony .orow .v,
  .tonk-ceremony .orow .k { padding-bottom: 4px; margin-bottom: -4px; }

  /* An active editor opens into a two-line workspace. The noun remains
     visible at the upper left while the value gets the full row below;
     settling the answer removes `editing` and restores the compact record. */
  .tonk-ceremony .orow.editing {
    box-sizing: border-box;
    height: 60px;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 13px 20px;
    align-items: stretch;
    gap: 7px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .tonk-ceremony .orow.editing .k,
  .tonk-ceremony .orow.editing .v {
    width: 100%;
    padding-bottom: 0;
    margin-bottom: 0;
  }
  .tonk-ceremony .orow.editing .k { align-items: flex-start; }
  .tonk-ceremony .orow.editing .v {
    align-items: flex-end;
    justify-content: flex-start;
    text-align: left;
  }

  /* The editor: inline text with the browser's native caret. */
  /* A real `<input>`, not the study's contenteditable span.
     Conditional mediation is the reason: WebAuthn will only offer a
     discoverable passkey through `autocomplete="username webauthn"` on
     an actual form control, so a span could never surface the autofill
     this ceremony is built around. Styled to sit in the row like the
     study's editor — no chrome and wearing the row's own type. */
  /* A real input wearing the study's editor, with native text-entry
     behavior and no extra control chrome. */
  .tonk-ceremony .ed {
    appearance: none; -webkit-appearance: none;
    box-sizing: border-box;
    background: none; border: 0; outline: none; margin: 0;
    padding: 0;
    font: inherit; letter-spacing: inherit; text-transform: none;
    color: var(--ink); caret-color: var(--ink);
    text-align: right; width: 100%; min-width: 0;
    /* The row is `align-items: flex-end`, so the input has to sit on the
       same baseline as the noun beside it rather than centring itself in
       whatever height it claims. */
    height: 1em; line-height: 1;
  }
  .tonk-ceremony .orow.editing .ed {
    display: block;
    inline-size: 100%;
    max-inline-size: none;
    height: 20px;
    line-height: 20px;
    text-align: left;
  }
  .tonk-ceremony .ed::placeholder {
    color: var(--soft); opacity: .55; text-transform: none;
  }

  /* Autofill (an address fill or a passkey's) otherwise repaints the
     field yellow with the vendor's own ink. */
  .tonk-ceremony .ed:-webkit-autofill,
  .tonk-ceremony .ed:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--ink);
    box-shadow: 0 0 0 100px var(--card) inset;
  }

  /* An action step: solid ink, full rung, the word bottom-right. While a
     ceremony is out of our hands the block blinks rather than spins. */
  .tonk-ceremony .obtn {
    display: flex; align-items: flex-end; justify-content: flex-end; gap: 6px;
    height: 36px; padding: 0 10px 9px 24px; overflow: hidden;
    background: var(--ink);
    color: var(--on-ink);
    box-shadow: 0 0 0 1px var(--ink);
    font-size: 13px; cursor: pointer; white-space: nowrap; border: 0;
    transition: height .4s var(--ease),
                opacity .4s var(--ease);
  }
  .tonk-ceremony .obtn.wait {
    cursor: default;
    animation: tonk-register-wait 2.4s var(--ease) infinite;
  }
  @keyframes tonk-register-wait { 0%, 100% { opacity: 1 } 50% { opacity: .72 } }

  /* A mistake flashes rather than colouring: attention is earned by
     blinking, never by hue. */
  .tonk-ceremony .flash { animation: tonk-register-flash .45s var(--ease) 2; }
  @keyframes tonk-register-flash { 50% { opacity: .55 } }

  /* The narrator: one block whose sentence changes with the step. */
  .tonk-ceremony .oexp {
    margin-top: 7px; min-height: 36px; padding: 10px 16px 11px;
    display: flex; flex-direction: column; gap: 2px;
  }
  .tonk-ceremony .oexp p {
    margin: 0; font-size: 13px; line-height: 1.55;
    color: var(--soft);
  }
  .tonk-ceremony .oexp p b {
    font-weight: 600; color: var(--ink);
  }
  .tonk-ceremony .oexp a {
    color: var(--ink);
    text-decoration: underline; text-underline-offset: 3px;
  }
  .tonk-ceremony .oexp a:focus-visible {
    outline: 2px solid var(--ink); outline-offset: 3px;
  }

  /* The way out: the quietest thing on screen. */
  .tonk-ceremony .ghost {
    align-self: flex-end; margin-top: 10px; background: none; border: 0;
    padding: 0; display: block;
    font-size: 13px; cursor: pointer;
    color: var(--ink);
    text-decoration: underline; text-underline-offset: 3px;
  }

  /* The in-space account ceremony uses the current FABB task shell while the
     same DOM continues to drive the real email lookup and WebAuthn ceremony. */
  #tonk-register[data-fabb-task] .ocol {
    display: flex;
    flex-flow: row wrap;
    overflow: hidden;
    background: var(--modal, var(--card));
  }
  #tonk-register[data-fabb-task] .ostack { display: contents; }
  #tonk-register[data-fabb-task] .mblk { box-shadow: none; }
  #tonk-register[data-fabb-task] .m-head {
    box-sizing: border-box;
    flex: 0 0 100%;
    min-height: 48px;
    height: 48px;
    padding: 0 18px;
    align-items: center;
    gap: 16px;
    border-bottom: 0;
    background: var(--wash);
    font-size: 17px;
  }
  #tonk-register[data-fabb-task] .fabb-task-disc {
    display: block;
    box-sizing: border-box;
    width: 18px;
    height: 18px;
    flex: 0 0 18px;
    border: 2px solid var(--ink);
    border-radius: 50%;
  }
  #tonk-register[data-fabb-task] .fabb-task-title {
    margin-left: auto;
    text-align: right;
  }
  #tonk-register[data-fabb-task] .orow {
    box-sizing: border-box;
    flex: 0 0 100%;
    min-height: 48px;
    height: 48px;
    padding: 12px 18px 9px;
    align-items: center;
    font: 400 18px/1.5 var(--cond);
    letter-spacing: 0;
    text-transform: none;
    background: var(--wash);
  }
  /* Keep these rows in the DOM for the activation wait, but let the
     instruction below the saved email and name carry the visible message. */
  #tonk-register[data-fabb-task] #tonk-register-passkey-row,
  #tonk-register[data-fabb-task] #tonk-register-confirm-row {
    display: none;
  }
  #tonk-register[data-fabb-task] .orow.editing {
    min-height: 104px;
    height: auto;
    grid-template-rows: auto 48px;
    gap: 8px;
    padding: 18px 18px 14px;
  }
  #tonk-register[data-fabb-task] .orow.editing .k {
    color: var(--ink);
    font: 600 17px/1.1 var(--cond);
    letter-spacing: 0;
  }
  #tonk-register[data-fabb-task] .orow.editing .v {
    align-items: stretch;
  }
  #tonk-register[data-fabb-task] .orow.editing .ed {
    box-sizing: border-box;
    min-height: 48px;
    height: 48px;
    padding: 12px 14px 9px;
    border: 0;
    background: var(--wash);
    font: 400 18px/1.5 var(--cond);
    letter-spacing: .02em;
  }
  #tonk-register[data-fabb-task] .orow.editing .ed:hover {
    background: var(--wash-p);
  }
  #tonk-register[data-fabb-task] .orow.editing .ed:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: -2px;
  }
  #tonk-register[data-fabb-task] .obtn {
    order: 101;
    box-sizing: border-box;
    flex: 0 0 50%;
    min-height: 48px;
    height: 48px;
    padding: 4px 12px;
    align-items: center;
    justify-content: flex-end;
    border-top: 0;
    font: 600 17px/1.1 var(--cond);
    letter-spacing: 0;
    text-align: right;
    white-space: normal;
    opacity: 1;
  }
  #tonk-register[data-fabb-task] .oexp {
    order: 90;
    box-sizing: border-box;
    flex: 0 0 100%;
    min-height: 105px;
    margin: 0;
    padding: 10px 18px 18px;
    border-top: 0;
    background: var(--wash);
  }
  #tonk-register[data-fabb-task] .oexp p {
    font: 400 18px/1.5 var(--cond);
    letter-spacing: 0;
    text-transform: none;
  }
  #tonk-register[data-fabb-task] .ghost {
    order: 100;
    box-sizing: border-box;
    flex: 0 0 50%;
    min-height: 48px;
    margin: 0;
    padding: 12px 18px;
    border-top: 0;
    text-align: left;
    text-decoration: none;
    font: 600 17px/1.1 var(--cond);
  }
  #tonk-register[data-fabb-task] .ocol:has(.obtn[hidden]) .ghost {
    flex-basis: 100%;
  }

  @media (max-width: 519px) {
    .tonk-ceremony .m-head,
    .tonk-ceremony .orow,
    .tonk-ceremony .obtn {
      min-height: 44px;
      height: 44px;
    }
    .tonk-ceremony .orow {
      align-items: center;
      padding-top: 0;
      padding-bottom: 0;
    }
    .tonk-ceremony .orow .v,
    .tonk-ceremony .orow .k {
      align-items: center;
      padding-bottom: 0;
      margin-bottom: 0;
    }
    .tonk-ceremony .ed {
      height: 44px;
      font-size: 16px;
      line-height: 44px;
    }
    .tonk-ceremony .ghost {
      min-height: 44px;
      display: flex;
      align-items: center;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    #tonk-register,
    .tonk-ceremony .orow,
    .tonk-ceremony .obtn {
      transition: none;
    }
    .tonk-ceremony .obtn.wait,
    .tonk-ceremony .flash {
      animation: none;
    }
  }
