/* ============================================================================
   nik — components. Everything the markup can wear, built from tokens.css and
   nothing else.

   The rule: tokens govern colour, type, radius, elevation and the page grid.
   Component-internal geometry (a 28px step badge, a 72px chart, a 5px bar gap)
   may be literal — it is the component's own anatomy, not a shared decision.
   If you are typing a hex, a font stack, a shadow or a font-size here, stop
   and add a token instead.

   Order: reset → chrome → typography → surfaces → controls → data → chat →
   mascot → landing → prose → styleguide → responsive.
   ============================================================================ */

/* ---- reset ---------------------------------------------------------------- */
* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; background: var(--bg); }
body {
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--fg);
  /* The ground: lavender, with two auroras pinned to the viewport so short
     pages get the same air as long ones. The landing paints its own (below). */
  background:
    radial-gradient(900px 620px at 100% -140px, var(--glow-cool), transparent 70%) no-repeat,
    radial-gradient(760px 560px at -10% 88%, var(--glow-warm), transparent 70%) no-repeat,
    var(--bg);
  background-attachment: fixed, fixed, scroll;
  -webkit-font-smoothing: antialiased;
  min-height: 100dvh; display: flex; flex-direction: column;
}
a { color: var(--accent); text-decoration: none; }
/* Every external link wears the icon — by rule, not by memory. */
a[target="_blank"]::after {
  content: "\2197"; font-size: 0.75em; margin-left: 4px;
  opacity: 0.55; vertical-align: 1px;
}
/* One visible focus ring for the whole product, keyboard only. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- chrome: nav, page column, footer ------------------------------------ */
.nav {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 8px;
  height: calc(52px + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) var(--gutter) 0;
  background: var(--bg-nav);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border-bottom: var(--hairline) solid var(--line);
}
.nav .brand {
  display: flex; align-items: center; gap: 8px;
  font-weight: 700; font-size: var(--t-lead); color: var(--fg);
}
.nav .brand svg { display: block; }
.nav .links { margin-left: auto; display: flex; gap: 20px; font-size: var(--t-nav); }
.nav .links a { color: var(--fg2); }
.nav .links a:hover { color: var(--fg); }
/* The signed-in page being read: its own link, at full ink. */
.nav .links a[aria-current="page"] { color: var(--fg); font-weight: 600; }
/* Sign out sits among the links and reads as one: a form's button in the
   nav wears the link's voice, not .linkish's accent. */
.nav .links .linkish { font-size: var(--t-nav); color: var(--fg2); }
.nav .links .linkish:hover { color: var(--fg); text-decoration: none; }

/* The snapping column: one column, the page owns the gutter, every child
   spans it exactly. Vertical rhythm is the gap. */
.page {
  width: 100%; max-width: var(--col); margin: 0 auto;
  padding: 24px var(--gutter) 64px;
  display: flex; flex-direction: column; gap: var(--gap);
}
.page > * { width: 100%; margin: 0; }

.footer {
  margin-top: auto; padding: 28px var(--gutter) calc(28px + env(safe-area-inset-bottom));
  text-align: center; font-size: var(--t-fine); color: var(--fg2);
}
.footer a { color: var(--fg2); }
.footer .lang { margin-top: 10px; }
.footer select {
  font: inherit; font-size: var(--t-micro); color: var(--fg2);
  background: transparent; border: none; padding: 2px 0; cursor: pointer;
}

/* ---- typography ---------------------------------------------------------- */
h1 {
  font-size: var(--t-title); font-weight: 700;
  letter-spacing: var(--tracking-head); padding: 0 var(--inset);
}
.lead { color: var(--fg2); padding: 0 var(--inset); }
/* Secondary words sitting on their own line — "no number yet" in a table
   cell, a note beside a value. It was written in the markup twice before it
   was ever written here, so the admin tables rendered their asides at full
   body weight. Secondary copy is --fg2, never --fg3 (rule 5). */
.muted { color: var(--fg2); }
.footnote { padding: 0 var(--inset); font-size: var(--t-micro); color: var(--fg2); }
.footnote a { color: var(--fg2); text-decoration: underline; }
/* A footnote that captions something centred (the product shot) rather than
   trailing a column of text. Everywhere else a footnote stays left, on the
   same edge as the paragraph it belongs to. */
.footnote.center { text-align: center; }

/* Two label voices, and they never mix.

   The marketing voice is the mono kicker: .eyebrow over a claim, .cap over a
   card. Wide tracking, monospaced — the terminal heart of the product, kept
   quiet. The operator voice is .section-title / .tile-l below: sans, uppercase,
   ordinary tracking. A mono kicker in admin reads like a billboard; sans caps
   on the landing reads like a settings screen.

   Both are standalone: they work anywhere, not only inside the one container
   they were first written for. */
.eyebrow, .cap {
  font-family: var(--font-mono); font-weight: 600; line-height: 1;
  text-transform: uppercase;
}
.eyebrow {
  font-size: var(--t-eyebrow); letter-spacing: var(--tracking-mono-lg);
  color: var(--accent); margin-bottom: 12px;
}
.cap {
  font-size: var(--t-kicker); letter-spacing: var(--tracking-mono);
  color: var(--fg3);
}

/* The trail above a nested page's title: Dashboard › Your nik. Small and
   quiet, one line, the last crumb is the page itself. */
.crumbs { padding: 0 var(--inset); font-size: var(--t-micro); color: var(--fg2); display: flex; gap: 6px; align-items: center; }
.crumbs a { color: var(--accent); }
.crumbs .sep { color: var(--fg3); }
.crumbs + h1 { margin-top: -4px; }

.section-title {
  margin-top: 20px; padding: 0 var(--inset);
  font-size: var(--t-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--fg2);
}
.section-title + * { margin-top: calc(-1 * var(--gap) + 8px); }

/* No orphaned words: balanced headings, no hanging last lines in blurbs. */
h1, .feat h2, .card.dream h2, .card.vault h2, .fin h2 { text-wrap: balance; }
.hero .tagline, .feat p, .card.dream p, .card.vault p { text-wrap: pretty; }

/* ---- surfaces: cards and cells (the iOS list grammar, re-inked) ---------- */
.card {
  background: var(--card);
  border: var(--edge) solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.card.pad { padding: var(--inset); }
/* The app scale. An app page uses four sizes and no more: the title
   (--t-title), section titles (--t-micro caps), everything inside a card at
   --t-callout — text, inputs, buttons alike — and one-line subs at --t-micro.
   --t-callout was always reserved for "buttons, cells, table rows"; the cell
   just never applied it, so rows inherited the 17px body size and every
   settings row read like a paragraph. Rows are 44px tall at minimum, a thumb. */
.cell {
  display: flex; align-items: center; gap: var(--gap);
  min-height: 44px; padding: 12px var(--inset);
  font-size: var(--t-callout);
}
.cell + .cell { border-top: var(--hairline) solid var(--line); }
.cell .grow { flex: 1; min-width: 0; overflow-wrap: anywhere; }
/* A secondary line under a primary one. Standalone by rule (DESIGN §6): it
   was written as `.cell .sub` and therefore silently did nothing the first
   time it was used outside a cell — the tier cards rendered their pitch at
   full body weight and read like three headlines. */
.sub { display: block; font-size: var(--t-micro); color: var(--fg2); }
/* Read aloud, not drawn: the name a row's button acts on. It sits at its
   container's corner, so words past a scroller's edge never widen the page. */
.vh { position: absolute; top: 0; left: 0; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.cell.empty { color: var(--fg2); }
/* An explanation at the top of a card is a paragraph, not a row: it wraps,
   and a 44px minimum on a three-line sentence leaves a hole under it. */
.card > .cell.empty { display: block; min-height: 0; font-size: var(--t-fine); line-height: 1.5; }
/* A person: photo beside name, email as the sub-line. Standalone by rule
   (DESIGN §6) — the admin users table shows people too, and scoped under
   .cell the photos came out at whatever size Google served them. */
.row { display: flex; align-items: center; gap: 12px; }
.avatar { width: 28px; height: 28px; border-radius: 50%; flex: none; object-fit: cover; }
.avatar.blank { display: block; background: var(--accent-soft); }
/* A status dot before a status word. Green breathes: he is answering. */
.dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--fg3); margin-right: 6px; vertical-align: 1px;
}
.dot.ok { background: var(--ok); animation: nik-breathe 2.2s ease-out infinite; }
@keyframes nik-breathe {
  0% { box-shadow: 0 0 0 0 var(--ok-glow); }
  70% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) { .dot.ok { animation: none; } }
/* A status that could not be read says something is wrong; somebody
   waiting on an owner is amber. */
.dot.warn { background: var(--danger); }
.dot.pending { background: var(--warn-dot); }
/* Two actions in a row read as two things, not one word. */
.cell .actions { display: flex; gap: 8px; }
.cell.stack { flex-direction: column; align-items: stretch; gap: 8px; }
a.cell { color: var(--fg); transition: background var(--fast) var(--ease); }
a.cell:hover { background: var(--hover); }
.cell-icon { flex: none; display: grid; place-items: center; }
.cell-icon svg { width: 22px; height: 22px; display: block; }
.chev { color: var(--fg3); font-size: 20px; }


/* ---- the workspace: the signed-in person's pages -------------------------
   Kilian approved the dusk-sky layout on 2026-10-11 (option D of the
   dashboard canvas): the person's pages in a sidebar drawn on the night,
   with the brand art's moss growing at its foot, beside one flat white
   working surface. Each page opens under a band of the brand's sky at dusk;
   below it nothing is a card. Sections are split by hairlines, rows run the
   surface's full width, and only code is drawn on a filled block. On a phone
   the sidebar becomes a bar across the top. */
body.ws-body { min-height: 100dvh; background: var(--card); }
.ws { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 100dvh; font-size: 14px; line-height: 1.5; }
/* Icons are 24-unit strokes; the brand marks keep their own paint. */
.ws svg[viewBox="0 0 24 24"]:not([fill]) { flex: none; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.ws h1 { padding: 0; font-size: 30px; line-height: 1.1; font-weight: 700; letter-spacing: -0.03em; overflow-wrap: anywhere; text-wrap: balance; }
.ws h2 { font-size: 17px; line-height: 1.3; font-weight: 700; letter-spacing: -0.02em; }
.ws h3 { font-size: 15px; line-height: 1.35; font-weight: 600; }
.ws small { display: block; font-size: 13px; color: var(--fg2); }
.ws .avatar { width: 34px; height: 34px; border-radius: 50%; flex: none; object-fit: cover; }
.ws .avatar.blank { background: var(--accent-soft); }
.ws-grow { flex: 1; min-width: 0; overflow-wrap: anywhere; }

/* The sidebar: the night, lit from its top corner, with a foot that stays
   at the bottom of the window as the links scroll under it: two clumps of
   the brand art's moss over the night fading in behind them, the second
   turned round so they never read as one picture twice, and the reader's
   own name standing in the moss. */
.ws-side {
  position: sticky; top: 0; height: 100dvh; overflow: hidden auto;
  display: flex; flex-direction: column; gap: 18px;
  padding: 18px 12px 0; background: var(--night-lit); color: var(--night-ink);
  scrollbar-width: none;
}
.ws-side-foot {
  position: sticky; bottom: 0; isolation: isolate; margin: auto -12px 0; padding: 44px 12px 22px;
  background: linear-gradient(180deg, rgba(18, 15, 34, 0), #120f22 44px);
}
.ws-side-foot::before, .ws-side-foot::after {
  content: ""; position: absolute; bottom: 0; z-index: -1; height: 96px; pointer-events: none;
  background: url(/site/foreground.webp) no-repeat -779px calc(100% + 82px) / 1100px auto;
}
.ws-side-foot::before { left: 0; right: 0; }
.ws-side-foot::after { left: 120px; right: -60px; bottom: -6px; transform: scaleX(-1); opacity: .85; }
.ws-brand { display: flex; align-items: center; gap: 10px; padding: 4px 10px; font-size: 19px; font-weight: 700; letter-spacing: -0.02em; color: var(--night-ink); }
.ws-brand svg { flex: none; width: 30px; height: 30px; border-radius: 9px; box-shadow: 0 0 14px var(--blob-glow); }
.ws-nav { display: flex; flex-direction: column; gap: 18px; }
.ws-nav-group { display: flex; flex-direction: column; gap: 1px; }
.ws-nav a { display: flex; align-items: center; gap: 10px; min-height: 34px; padding: 0 10px; border-radius: 8px; color: var(--night-ink2); font-size: 15px; transition: background .15s, color .15s; }
.ws .ws-nav svg[viewBox] { width: 17px; height: 17px; opacity: .8; }
.ws-nav a:hover { background: var(--night-hover); color: var(--night-ink); }
.ws-nav a[aria-current="page"] { background: var(--night-wash); color: var(--night-ink); font-weight: 600; box-shadow: inset 0 0 0 var(--hairline) var(--night-line); }
.ws-nav a[aria-current="page"] svg { color: var(--night-accent); opacity: 1; }
/* The search palette Command-K opens, over a dimmed and blurred page: the
   field, the hits by kind as the reader types, and the keys that move
   through them. The search page draws the same field and results as
   .ws-search. */
.ws-palette { width: min(680px, calc(100vw - 32px)); margin: 96px auto auto; padding: 0; border: 0; border-radius: 16px; background: var(--card); color: var(--fg); box-shadow: 0 30px 80px rgba(24, 17, 40, .45), 0 0 0 var(--hairline) rgba(43, 33, 64, .2); overflow: hidden; }
.ws-palette::backdrop { background: rgba(24, 17, 40, .48); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
.ws-palette-form { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; grid-template-areas: "mark field key"; align-items: center; column-gap: 12px; padding: 0 18px; border-bottom: var(--hairline) solid var(--line-strong); }
.ws-palette-form svg[viewBox] { grid-area: mark; width: 18px; height: 18px; color: var(--accent); }
.ws-palette-form input[type="search"] { grid-area: field; min-width: 0; min-height: 58px; padding: 0; border: 0; outline: 0; background: none; color: var(--fg); font: inherit; font-size: 19px; }
.ws-palette-form kbd { grid-area: key; padding: 3px 7px; border-radius: 6px; box-shadow: inset 0 0 0 var(--hairline) var(--line-strong); font-family: var(--font-mono); font-size: 11px; color: var(--fg2); }
.ws-hits { --pick-x: 18px; --pick-end: 18px; max-height: 560px; overflow: auto; padding-bottom: 6px; }
.ws-hits:empty { display: none; }
.ws-hits .ws-notice { margin: 12px 18px 6px; }
.ws-hits > .ws-note { margin: 0; padding: 12px var(--pick-x); font-size: 13px; }
.ws-hits-keys { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 0; padding: 10px 18px; border-top: var(--hairline) solid var(--line); background: var(--bg); font-size: 12px; color: var(--fg2); }
.ws-hits-keys kbd { font-family: var(--font-mono); }
/* The search page: the field across the surface under the band, then each
   kind's hits as rows across the full width under its small heading, split
   by hairlines, as the workspace draws every list. The first hit is marked
   only while the field has the keys, since Enter opens it then. */
.ws-search-field { display: grid; grid-template-columns: 18px minmax(0, 1fr); grid-template-areas: "mark field"; align-items: center; column-gap: 12px; padding: 0 var(--ws-x); border-bottom: var(--hairline) solid var(--line-strong); }
.ws-search-field svg[viewBox] { grid-area: mark; width: 18px; height: 18px; color: var(--accent); }
.ws-search-field input[type="search"] { grid-area: field; min-width: 0; min-height: 56px; padding: 0; border: 0; outline: 0; background: none; color: var(--fg); font: inherit; font-size: 17px; }
.ws-search .ws-hits { --pick-x: var(--ws-x); --pick-end: var(--ws-x); max-height: none; overflow: visible; padding: 0; }
.ws-search [role="group"] { padding-bottom: 6px; border-bottom: var(--hairline) solid var(--line-strong); }
.ws-search:not(:focus-within) .ws-pick[aria-selected="true"]:not(:hover) { background: none; box-shadow: none; }
.ws-search .ws-hits .ws-notice { margin: 16px var(--ws-x); }
.ws-nav-head { padding: 0 10px 5px; font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--night-ink3); }
/* The reader's name and picture, a pane of the night's glass standing in
   the moss, which opens their account. */
.ws-you {
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 10px 8px 9px;
  border-radius: 14px; background: rgba(24, 17, 40, .5); color: var(--night-ink); text-decoration: none;
  -webkit-backdrop-filter: blur(12px) saturate(1.3); backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 0 0 var(--hairline) var(--night-ring), 0 8px 22px rgba(12, 8, 24, .4);
  transition: background .15s;
}
.ws-you:hover, .ws-you[aria-current="page"] { background: rgba(24, 17, 40, .66); color: var(--night-ink); }
.ws-you .avatar { width: 30px; height: 30px; box-shadow: 0 0 0 1.5px var(--night-glass-edge); }
.ws-you .avatar.blank { background: var(--night-wash); }
.ws-you > .ws-glyph { width: 14px; height: 14px; color: var(--night-ink3); }
.ws-you:hover > .ws-glyph, .ws-you[aria-current="page"] > .ws-glyph { color: var(--night-accent); }
.ws-who { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Help beside the reader's name, a smaller pane of the same glass, which
   opens the Help dialog. */
.ws-side-foot { display: flex; align-items: stretch; gap: 8px; }
.ws-you { flex: 1; min-width: 0; }
.ws-help {
  flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; min-width: 52px; padding: 6px 8px;
  border: 0; border-radius: 14px; background: rgba(24, 17, 40, .5); color: var(--night-ink2); font: inherit; font-size: 11px; font-weight: 600; cursor: pointer;
  -webkit-backdrop-filter: blur(12px) saturate(1.3); backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 0 0 var(--hairline) var(--night-ring), 0 8px 22px rgba(12, 8, 24, .4);
  transition: background .15s, color .15s;
}
.ws-help:hover { background: rgba(24, 17, 40, .66); color: var(--night-ink); }
.ws-help svg[viewBox] { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.ws-col { display: flex; flex-direction: column; min-width: 0; }
/* Privacy and Terms, small at the foot of the account page. */
.ws-foot-links { display: flex; gap: 6px; margin: 0; padding: 18px var(--ws-x); font-size: 12px; color: var(--fg3); }
.ws-foot-links a { color: inherit; }
.ws-foot-links a:hover { color: var(--accent); }
/* The main column is the surface edge to edge: it neither pads nor spaces
   what it holds. Every component spans it, and a component that holds
   words insets them by the page's margin, --ws-x, so nothing reaches past
   its own box to meet the edge. */
.ws-main { width: 100%; display: flex; flex-direction: column; }

/* Head: the band of sky every page opens under, at dusk, with what the page
   is and its one action on the right. It spans the surface edge to edge. */
/* The band is a grid: the page's words at its left, its actions at its
   right, and its tabs along its foot, where the selected one's rule is the
   band's own edge. A phone stacks the three. Every band is one height: it
   keeps a line for the lead and a row for the tabs whether it draws them or
   not, so moving from one page to another never moves the page under it. */
.ws-head {
  position: relative; isolation: isolate; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "text actions" "tabs tabs"; align-items: end; column-gap: 20px;
  padding: 22px var(--ws-x) 20px;
  background: #3f92ea url(/site/sky.jpg) center 22% / cover no-repeat; color: var(--night-ink);
  text-shadow: 0 1px 14px rgba(24, 17, 40, .4);
}
.ws-head::before { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(90deg, rgba(24, 17, 40, .38), transparent 65%), var(--sky-dusk); }
.ws-head-text { grid-area: text; min-width: 0; min-height: calc(16px + 4px + 33px + 6px + 22px); }
/* The actions hold the band at one height whatever they are: a find box, a
   button, or a status. */
.ws-head-actions { grid-area: actions; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 10px; min-width: 0; min-height: 40px; }
.ws-head:not(:has(> .ws-tabs)) { padding-bottom: calc(14px + 44px); }
.ws-head:has(> .ws-tabs) { padding-bottom: 0; }
.ws-head > .ws-tabs { grid-area: tabs; margin-top: 14px; }
@media (max-width: 760px) {
  .ws-head { grid-template-columns: minmax(0, 1fr); grid-template-areas: "text" "actions" "tabs"; }
  .ws-head-actions { justify-content: flex-start; margin-top: 14px; }
}
/* The page's own icon, the sidebar's, on a pane of glass beside its title. */
.ws-main[style*="--ws-page-icon"] > .ws-head > .ws-head-text { position: relative; padding-left: 58px; }
.ws-main[style*="--ws-page-icon"] > .ws-head > .ws-head-text::before { content: ""; position: absolute; left: 0; top: 4px; width: 44px; height: 44px; border-radius: 13px; background: rgba(255, 255, 255, .16); box-shadow: inset 0 0 0 var(--hairline) rgba(255, 255, 255, .34), 0 8px 22px rgba(24, 17, 40, .18); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.ws-main[style*="--ws-page-icon"] > .ws-head > .ws-head-text::after { content: ""; position: absolute; left: 11px; top: 15px; width: 22px; height: 22px; background: var(--night-ink); -webkit-mask: var(--ws-page-icon) center / contain no-repeat; mask: var(--ws-page-icon) center / contain no-repeat; }
@media (max-width: 760px) {
  .ws-main[style*="--ws-page-icon"] > .ws-head > .ws-head-text { padding-left: 48px; }
  .ws-main[style*="--ws-page-icon"] > .ws-head > .ws-head-text::before { width: 36px; height: 36px; border-radius: 11px; }
  .ws-main[style*="--ws-page-icon"] > .ws-head > .ws-head-text::after { left: 9px; top: 13px; width: 18px; height: 18px; }
}
.ws-eyebrow { margin-bottom: 4px; font-family: var(--font-mono); font-size: 11px; line-height: 16px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--night-ink2); }
.ws-lead { margin-top: 6px; font-size: 15px; line-height: 22px; color: var(--night-ink2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-lead a { color: var(--night-ink); }
.ws-lead > span + span::before { content: " · "; }
/* The promise that what a page shows is the family's alone: a lock on a
   small tile, then the words. As a band's lead it is in the night's ink;
   at the top of a conversation it is a pill in the middle of the stream. */
.ws-private { display: inline-flex; align-items: center; gap: 8px; }
.ws-private > .ws-glyph { flex: none; box-sizing: content-box; width: 12px; height: 12px; padding: 4px; border-radius: 7px; background: var(--night-wash); }
.ws-private.mark { margin-right: 10px; cursor: help; }
/* In a band the line keeps the lead's height, so a page that says it is
   private stands as tall as one with a lead or none. */
.ws-lead > .ws-private { height: 22px; vertical-align: top; }
.ws-lead > .ws-private.mark + span::before { content: none; }
/* Among what it covers, the line is a pill: atop a conversation, and at
   the foot of Home. */
:is(.ws-chat-msgs, .ws-board-foot) > .ws-private { display: flex; width: fit-content; max-width: 100%; margin: 6px auto 14px; padding: 6px 14px 6px 8px; border-radius: 999px; background: var(--accent-soft); color: var(--fg2); font-size: 12.5px; text-align: center; }
:is(.ws-chat-msgs, .ws-board-foot) > .ws-private > .ws-glyph { background: var(--card); color: var(--accent); }
.ws-board-foot > .ws-private { margin: 22px auto; }
/* A crumb over a page's title: the list it belongs to, then which one. */
.ws-crumb { display: flex; gap: 8px; }
.ws-crumb a { color: inherit; }
.ws-head .ws-btn { background: var(--night-wash); color: var(--night-ink); box-shadow: inset 0 0 0 var(--hairline) rgba(255, 255, 255, .3); text-shadow: none; }
.ws-head .ws-btn:hover { background: rgba(255, 255, 255, .22); }
.ws-head .ws-btn.ws-primary { background: var(--card); color: var(--accent); }

/* Buttons: the brand's pills. */
.ws-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px; flex: none;
  padding: 8px 16px; border: 0; border-radius: 999px; cursor: pointer; white-space: nowrap;
  background: var(--accent-soft); color: var(--accent); font: inherit; font-size: 14px; line-height: 1.3; font-weight: 500;
  transition: transform .12s, background .15s, opacity .15s;
}
.ws-btn:hover { background: rgba(124, 92, 214, 0.18); }
.ws-btn:active { transform: scale(.97); }
.ws .ws-btn svg { width: 16px; height: 16px; }
.ws-btn.ws-primary { background: var(--accent-grad); color: #fff; box-shadow: var(--shadow-btn); }
.ws-btn.ws-primary:hover { opacity: .92; }
.ws-btn.ws-quiet { background: transparent; color: var(--fg2); }
.ws-btn.ws-quiet:hover { background: var(--hover); color: var(--fg); }
.ws-btn.ws-danger { background: var(--danger-soft); color: var(--danger); }
.ws-btn.ws-danger-solid { background: var(--danger-grad); color: #fff; box-shadow: var(--shadow-btn-danger); }
.ws-btn.ws-small { padding: 6px 13px; font-size: 13px; }
.ws .ws-btn.ws-small svg { width: 14px; height: 14px; }
/* A quiet button that removes something turns red under the pointer. */
.ws-btn.ws-quiet.ws-danger { background: transparent; color: var(--fg2); }
.ws-btn.ws-quiet.ws-danger:hover { background: var(--danger-soft); color: var(--danger); }
.ws-btn.ws-glass { background: var(--night-glass); color: var(--night-ink); border: 1px solid var(--night-glass-edge); }
/* The no-family card's call to action: a solid white button on the sky,
   and the glass one beside it for the other way in. */
.ws-btn.ws-solid { background: #fff; color: #2b1f5c; border: 1px solid #fff; box-shadow: 0 8px 22px rgba(10, 30, 90, .25); }
.ws-sky-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.ws-sky-actions .ws-btn { min-height: 44px; }
.ws-find-opener kbd { margin-left: 4px; font-family: var(--font-mono); font-size: 12px; opacity: .8; }
.ws-sky-note { margin-top: 12px; font-size: 13px; }
.ws-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }

/* A page's tabs are words underlined when chosen, on a hairline across the
   surface, not a segmented control. */
.ws .seg-bar { gap: 26px; margin: 0 calc(-1 * var(--ws-x)); padding: 0 var(--ws-x); background: none; border-radius: 0; border-bottom: var(--hairline) solid var(--line-strong); overflow-x: auto; scrollbar-width: none; }
.ws .seg { flex: none; padding: 13px 0 12px; border-radius: 0; font-size: 15px; font-weight: 500; white-space: nowrap; }
.ws .seg.on { background: none; box-shadow: inset 0 -2px 0 var(--accent); font-weight: 600; }
/* What was a card is a run of rows across the surface, edge to edge, with a
   hairline above and below: no fill, no corner, no shadow. */
.ws-section { min-width: 0; }
/* A section's heading: its title with how many beside it, what the
   section holds in a line under it, and its one action at the right. It
   is set larger than anything in the list under it and has no mark, so it
   never reads as one of the list's rows. */
.ws-section-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "title action" "about action";
  align-items: center; column-gap: 16px; min-width: 0;
}
.ws .ws-section-title { grid-area: title; min-width: 0; font-size: 22px; line-height: 1.2; font-weight: 700; letter-spacing: -0.02em; }
.ws-section-kicker { margin-right: 12px; font-family: var(--font-mono); font-size: 11px; font-weight: 400; letter-spacing: .14em; text-transform: uppercase; vertical-align: 3px; color: var(--accent); }
.ws-section-count { margin-left: 10px; font-family: var(--font-mono); font-size: 13px; font-weight: 400; letter-spacing: 0; color: var(--fg3); }
.ws-section-about { grid-area: about; margin-top: 2px; font-size: 14.5px; color: var(--fg2); max-width: 64ch; }
.ws-section-action { grid-area: action; display: flex; align-items: center; gap: 8px; }
/* A list: its section's heading, then its rows edge to edge, each the same
   grid whatever it holds: the mark it opens with, its words (a title and a
   word beside it, what it is for, and what went wrong), its status, and its
   action. A row that adds one more is drawn as one of them. */
/* A section's heading over what reaches the surface's edges takes the
   page's margin, the same over every list. */
:is(.ws-list, .ws-picks, .ws-panel, .ws-specimen, .ws-section, .ws-needs) > .ws-section-head { padding: 22px var(--ws-x) 12px; }
.ws-rows { margin: 0; padding: 0; list-style: none; border-bottom: var(--hairline) solid var(--line-strong); }
.ws-rows form { margin: 0; }
.ws-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; grid-template-areas: "lead text state action";
  align-items: center; column-gap: 14px; min-height: 64px; padding: 10px var(--ws-x); border-top: var(--hairline) solid var(--line);
}
.ws-row-lead { grid-area: lead; display: grid; place-items: center; width: 36px; }
.ws-row-text { grid-area: text; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ws-row-title { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 10px; font-size: 15px; font-weight: 600; }
.ws-row-title code { font-family: var(--font-mono); font-size: 13.5px; overflow-wrap: anywhere; }
.ws-row-title a { color: inherit; text-decoration: none; }
.ws-row-title a:hover { color: var(--accent); }
.ws-row-aside { font-family: var(--font-mono); font-size: 12.5px; font-weight: 400; color: var(--fg2); overflow-wrap: anywhere; }
.ws-row-sub { font-size: 13.5px; color: var(--fg2); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.ws-row-fault { font-size: 13px; color: var(--danger); overflow-wrap: anywhere; }
.ws-row-state { grid-area: state; justify-self: end; }
.ws-row-action { grid-area: action; display: flex; align-items: center; gap: 8px; }
.ws-row-body { grid-area: body; min-width: 0; }
.ws-row:has(> .ws-row-body) { grid-template-areas: "lead text state action" "lead body body body"; row-gap: 8px; }
.ws-row:has(> .ws-row-body) > .ws-row-lead { align-self: start; }
.ws-row-add { width: 100%; border: 0; border-top: var(--hairline) solid var(--line); background: none; font: inherit; color: inherit; text-align: left; cursor: pointer; }
.ws-row-add:hover { background: var(--accent-soft); }
.ws-row-add .ws-row-title { color: var(--accent); }
.ws-row-plus { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 11px; border: 1.5px dashed var(--line-strong); color: var(--accent); }
/* An entry of a list the reader picks from beside what it opens: its tile,
   its name and a line under it, and a count or a size at its end. Every
   entry is one height, so lists side by side line up row for row: each
   line keeps its height in either face, and the two lines and their gap
   fill the row's. */
.ws-pick {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "mark text trail";
  align-items: center; column-gap: 10px; min-height: 58px; padding: 8px var(--pick-end) calc(8px - var(--hairline)) var(--pick-x);
  border-bottom: var(--hairline) solid var(--line); color: var(--fg); text-decoration: none;
}
/* The hairline sits inside the bottom padding, so an entry of one line and
   one of two stand the same 58 pixels in every list beside every other. */
/* An entry with no tile keeps no room for one. */
.ws-pick:not(:has(> .ws-pick-mark)) { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "text trail"; }
a.ws-pick:hover { background: var(--hover); }
.ws-pick.on { background: var(--chosen); box-shadow: inset 3px 0 0 var(--accent); }
.ws-pick[aria-selected="true"] { background: var(--selected); box-shadow: inset 3px 0 0 var(--accent); }
/* An entry that opens something is pressed as a whole: the wash it takes
   under the pointer deepens while it is held down. */
:is(a.ws-pick, .ws-tile):active { background: var(--accent-soft); }
.ws-pick-mark { grid-area: mark; display: grid; place-items: center; }
.ws-pick-text { grid-area: text; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ws-pick-title, .ws-pick-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-pick-title { font-size: 15px; line-height: 22px; font-weight: 500; }
.ws-pick-title.code { font-family: var(--font-mono); font-size: 13px; }
.ws-pick.on .ws-pick-title { font-weight: 700; }
.ws-pick.on .ws-pick-title.code { font-weight: 600; color: var(--accent); }
.ws-pick-sub { font-size: 12px; line-height: 18px; color: var(--fg2); }
.ws-pick-sub a { font-weight: 500; color: var(--fg); text-decoration: none; }
/* Code keeps to the line's 18 pixels, where its own font's ascent would
   push the line half a pixel taller than its neighbours'. */
.ws-pick-sub code { line-height: 1; }
.ws-pick-sub a:hover { color: var(--accent); }
.ws-pick-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.ws-pick-head > .ws-pick-title { min-width: 0; }
.ws-pick-aside { display: inline-flex; align-items: center; align-self: center; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--fg2); }
.ws-pick-note { font-size: 12px; color: var(--fg2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-pick-trail { grid-area: trail; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; font-family: var(--font-mono); font-size: 12px; color: var(--fg3); }
/* Entries across the page, edge to edge: each takes the page's margin, and
   its title and the lines under it wrap, so it is as tall as its words. */
.ws-picks { container: picks / inline-size; }
.ws-picks > .ws-pick { align-items: start; column-gap: 14px; padding: 12px var(--ws-x); }
.ws-picks .ws-pick-text { gap: 3px; }
.ws-picks :is(.ws-pick-title, .ws-pick-sub, .ws-pick-note) { white-space: normal; overflow-wrap: anywhere; }
.ws-picks .ws-pick-sub { font-size: 13px; }
.ws-picks .ws-pick-trail { padding-top: 2px; }
/* In a narrow column, such as a phone or the calendar's day, the trail
   goes under the words, so the title keeps the width. */
@container picks (max-width: 440px) {
  .ws-picks > .ws-pick { grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "mark text" "mark trail"; row-gap: 6px; }
  .ws-picks > .ws-pick:not(:has(> .ws-pick-mark)) { grid-template-columns: minmax(0, 1fr); grid-template-areas: "text" "trail"; }
  .ws-picks .ws-pick-trail { flex-direction: row; flex-wrap: wrap; align-items: center; column-gap: 12px; padding-top: 0; }
}
/* The time something goes off, at the head of its entry: accent while it
   is set, amber when late, grey when it no longer counts. */
.ws-clock { display: block; width: 48px; padding-top: 1px; font-family: var(--font-mono); font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--accent); }
.ws-clock.warn { color: var(--warn); }
.ws-clock.off { font-weight: 500; color: var(--fg3); }
/* A day as a tile at the head of an entry: its weekday over its date. */
.ws-day { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 50px; height: 56px; border: var(--hairline) solid var(--line-strong); border-radius: 12px; background: var(--card); }
.ws-day-week { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg2); }
.ws-day-date { font-size: 20px; font-weight: 600; line-height: 1.15; font-variant-numeric: tabular-nums; }
.ws-day.late { border-color: var(--danger); }
.ws-day.late .ws-day-week { color: var(--danger); }
/* A line read as one sentence: its parts apart by a middle dot. */
.ws-dots > * + *::before { content: "·"; margin: 0 6px; color: var(--fg3); }
.ws-flag { display: inline-block; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.ws-flag.warn { background: var(--warn-dot); }
.ws-flag.bad { background: var(--danger); }
h3.ws-pick-group { display: flex; align-items: baseline; gap: 8px; margin: 0; padding: 14px var(--pick-end) 6px var(--pick-x); font-family: var(--font-mono); font-size: 11px; line-height: 16px; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); }
.ws-option-group > .ws-note { margin: 0; padding: 12px var(--pick-x); font-size: 13px; }
.ws-pick-group-count { color: var(--fg3); letter-spacing: 0; }
.ws-pick-group-action { margin-left: auto; font-family: var(--font); font-size: 12.5px; font-weight: 400; letter-spacing: 0; text-transform: none; }
.ws-pick-group-action a { text-decoration: none; }
@media (max-width: 760px) {
  .ws-row { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "lead text text" "lead state action"; row-gap: 8px; }
  .ws-row-state { justify-self: start; }
  .ws-row:has(> .ws-row-body) { grid-template-areas: "lead text text" "lead state action" "lead body body"; }
}
/* Facts: what a thing is, as labels and their values. A strip lays them
   side by side between two rules, each label over its value, the first
   flush with the text above it and a hairline in the gap before each
   other; facts that do not fit wrap under, where the strip clips the
   hairline at the edge. A ledger lists them one under another, label at
   the left and value at the right over a hairline, in as many columns of
   at least 260px as fit. A value in the code face is a name or an id. */
.ws-facts { margin: 0; }
.ws-facts dt { font-size: 12px; color: var(--fg2); }
.ws-facts dd { margin: 0; min-width: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.ws-facts dd code { font-family: var(--font-mono); font-size: 13px; font-weight: 400; }
.ws-facts.strip {
  display: flex; flex-wrap: wrap; column-gap: 32px; row-gap: 10px; padding-block: 10px; overflow: hidden;
  border-block: var(--hairline) solid var(--line-strong);
}
.ws-facts.strip > .ws-fact { position: relative; flex: 1 1 120px; min-width: 0; }
.ws-facts.strip > .ws-fact.wide { flex: 2 1 220px; }
.ws-facts.strip > .ws-fact::before { content: ""; position: absolute; top: -10px; bottom: -10px; left: -16px; border-left: var(--hairline) solid var(--line); }
.ws-facts.strip dd { margin-top: 2px; font-size: 15px; font-weight: 600; }
.ws-facts.ledger { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); column-gap: 32px; }
.ws-facts.ledger > .ws-fact {
  display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "label value"; align-items: baseline; column-gap: 16px;
  padding: 9px 0; border-bottom: var(--hairline) solid var(--line);
}
.ws-facts.ledger dt { grid-area: label; font-size: 13px; }
.ws-facts.ledger dd { grid-area: value; font-size: 14px; text-align: right; }
/* The component gallery (/family/design): each specimen under the heading
   that names its form. */
.ws-specimen { padding-bottom: 24px; border-bottom: var(--hairline) solid var(--line); }
.ws-specimen > .ws-section-head { margin-bottom: 4px; }
/* A specimen of a component that sits among words, inset by the page's
   margin, and one drawn in a column as narrow as a phone's. */
.ws-specimen-inset { padding: 0 var(--ws-x); }
.ws-specimen-narrow { max-width: 390px; box-shadow: inset calc(-1 * var(--hairline)) 0 0 var(--line-strong); }
/* A list that is empty, and a pager under a list. */
.ws-pager { display: flex; align-items: center; gap: 8px; padding: 12px var(--ws-x); }
.ws-pager > :last-child:not(:first-child), .ws-pager > .ws-btn:not(.ws-quiet) { margin-left: auto; }
.ws-part { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.ws-part-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 12px; }
.ws .ws-part-label { margin: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 400; letter-spacing: .14em; text-transform: uppercase; color: var(--fg3); }
.ws-part-count { margin-left: 8px; letter-spacing: 0; color: var(--fg2); }
.ws-part-aside { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px; color: var(--fg2); }
.ws-part-lead { margin: 0; font-size: 14px; color: var(--fg2); max-width: 62ch; }
/* A part places rows among its words: flush with its label, with no
   closing rule. */
.ws-part > .ws-rows { border-bottom: 0; }
.ws-part { --pick-x: 0px; --pick-end: 0px; }
.ws-part .ws-row { padding-inline: 0; }
.ws-part-aside a { text-decoration: none; }
.ws-tick { display: inline-grid; place-items: center; flex: none; width: 16px; height: 16px; margin-inline-end: 6px; vertical-align: -3px; color: var(--fg3); }
.ws-tick .ws-glyph { width: 13px; height: 13px; stroke-width: 2.6; }
.ws-tick.ok { color: var(--ok); }
.ws-tick.busy { color: var(--accent); }
.ws-tick.warn { color: var(--warn); }
.ws-tick.bad { color: var(--danger); }
.ws-tick-ring { width: 9px; height: 9px; border: 1.5px solid currentColor; border-radius: 50%; }
.ws-choice { display: inline-flex; flex-wrap: wrap; gap: 0 20px; }
.ws-choice-option { position: relative; display: inline-flex; align-items: center; gap: 6px; min-height: 38px; font-size: 14.5px; color: var(--fg2); cursor: pointer; }
.ws-choice-option input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.ws-choice-option:hover { color: var(--fg); }
.ws-choice-option:has(input:checked) { color: var(--fg); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent); }
.ws-choice-option:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.ws-choice-count { font-family: var(--font-mono); font-size: 12px; font-weight: 400; color: var(--fg2); }
.ws-pill { display: inline-flex; align-items: center; gap: 5px; flex: none; padding: 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 500; background: var(--neutral-soft); color: var(--fg2); white-space: nowrap; }
.ws-pill.ok { background: var(--ok-soft); color: var(--ok); }
.ws-pill.tint { background: var(--accent-soft); color: var(--accent); }
.ws-pill.bad { background: var(--danger-soft); color: var(--danger); }
.ws-pill.warn { background: var(--warn-soft); color: var(--warn); }
.ws-pill .ws-spin { width: 10px; height: 10px; }
.ws-pill .ws-glyph { width: 11px; height: 11px; }
/* A status in a band's actions is glass over the sky, its tone said by its
   glyph or spinner. */
.ws-head-actions > .ws-pill { gap: 6px; padding: 4px 12px; background: var(--night-wash); box-shadow: inset 0 0 0 var(--hairline) var(--night-ring); color: inherit; font-size: 13px; font-weight: 600; text-shadow: none; }
.ws-head-actions > .ws-pill .ws-glyph { width: 12px; height: 12px; stroke-width: 2.6; }

/* A field the reader types one value into, alike in a dialog, a setting,
   and a form that saves a pasted key; a number is as wide as its digits,
   and code is set in the monospace face. */
.ws-input {
  width: 100%; min-width: 0; height: 40px; padding: 0 12px;
  border: var(--edge) solid var(--line-strong); border-radius: 10px;
  background: var(--bg); color: var(--fg); font: inherit; font-size: 15px; font-weight: 400;
}
.ws-input::placeholder { color: var(--fg2); }
.ws-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; background: var(--card); }
.ws-input.number { width: 96px; }
.ws-input.code { font-family: var(--font-mono); font-size: 14px; }

/* Forms. */
.ws-form { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.ws-form label { display: flex; flex-direction: column; gap: 7px; font-size: 13px; font-weight: 600; }
.ws-form p { font-size: 12.5px; color: var(--fg2); }

/* Status: a dot and a word. */
.ws-status { display: inline-flex; align-items: center; gap: 7px; font-family: var(--font); font-size: 12.5px; color: var(--fg2); white-space: nowrap; }
.ws-status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.ws-status.ok { color: var(--ok); }
.ws-status.warn { color: var(--warn); }
.ws-status.busy { color: var(--accent); }
.ws-status.bad { color: var(--danger); }
.ws-status.neutral { color: var(--fg2); }

/* Nik's sky: the brand art itself, with Nik on the art's big island, as the
   band the dashboard opens under, edge to edge. The picture keeps its
   proportions at the band's height, against its right edge, so Nik always
   sits where the island leaves room for him; the sky's own blue fills the
   band to its left. The other states tint the same picture. */
.ws-sky {
  position: relative; overflow: hidden; isolation: isolate;
  height: var(--sky-h); display: flex; align-items: center; padding: 28px var(--ws-x);
  color: #fff;
  background: url(/site/sky.jpg) right center / auto 100% no-repeat, #3a83de;
}
/* Where the band is wider than the picture, the picture continues to the
   left as its own mirror image, so the sky meets itself at the seam rather
   than fading into a flat blue (Kilian, 2026-10-11); past the mirror, the
   picture again the right way round, which meets the mirror's far edge, so
   a window as wide as 2560 pixels holds no flat blue. */
.ws-sky-fill { position: absolute; top: 0; bottom: 0; left: 0; right: calc(3 * var(--sky-h) - 1px); z-index: -2; transform: scaleX(-1); background: url(/site/sky.jpg) left center / auto 100% no-repeat, #3a83de; pointer-events: none; }
.ws-sky-fill::after { content: ""; position: absolute; top: 0; bottom: 0; left: calc(3 * var(--sky-h) - 1px); width: calc(3 * var(--sky-h) + 1px); transform: scaleX(-1); background: url(/site/sky.jpg) center / 100% 100% no-repeat; }
.ws-sky::before { /* keeps the words readable over the bright left of the sky */
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(90deg, rgba(14, 60, 140, .42), rgba(14, 60, 140, .12) 45%, transparent 60%);
}
.ws-sky::after { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.ws-sky.down::after { background: var(--sky-dusk); mix-blend-mode: multiply; }
.ws-sky.notyet::after { background: var(--sky-night); mix-blend-mode: multiply; }
.ws-sky.unread { filter: grayscale(.85); }
.ws-scene { position: absolute; top: 0; right: 0; bottom: 0; width: calc(3 * var(--sky-h)); container-type: inline-size; pointer-events: none; }
/* The island's front mounds, cut from the same picture, stand in front of
   Nik so he sits in the moss rather than on it. */
.ws-scene::after { content: ""; position: absolute; inset: 0; background: url(/site/foreground.webp) center / 100% 100% no-repeat; pointer-events: none; }
/* The mounds take the state's tint with the picture behind them, cut to
   their own outline. */
.ws-sky.down .ws-scene::after, .ws-sky.notyet .ws-scene::after {
  background-blend-mode: multiply;
  -webkit-mask: url(/site/foreground.webp) center / 100% 100% no-repeat; mask: url(/site/foreground.webp) center / 100% 100% no-repeat;
}
.ws-sky.down .ws-scene::after { background: var(--sky-dusk), url(/site/foreground.webp) center / 100% 100% no-repeat; }
.ws-sky.notyet .ws-scene::after { background: var(--sky-night), url(/site/foreground.webp) center / 100% 100% no-repeat; }
/* Nik's place on the picture's big island: his centre is 81.7% across and
   43.6% down, low enough that the front mounds cover his base and high
   enough that his glass never reaches the straight lower edge of the
   mounds' cut-out. The bubble he wears is up to 21.2% of the picture wide. */
.ws-orb { position: absolute; left: 71.1%; top: 11.8%; width: 21.2%; aspect-ratio: 1; display: grid; place-items: center; pointer-events: auto; }
.ws-blob { display: grid; place-items: center; padding: 8px 12px; border-radius: 48% / 52%; background: radial-gradient(circle at 34% 28%, #4b2b8c, #1c1036 72%); box-shadow: 0 6px 16px rgba(10, 5, 30, .45), inset 0 2px 6px rgba(255, 255, 255, .12); animation: ws-bob 5s ease-in-out infinite alternate; }
/* In the sky Nik's body is a bubble, dark inside like the small Niks drawn
   in the art. mascot.js draws the body on a canvas as a soft membrane that
   follows him as he moves, fills it with the still body's colour, and
   stretches the still body's picture over it as glass, so the bubble
   squashes and ripples with him. Without the script the still body is a
   bubble round the resting face: the face's dot ring is wider than it is
   tall and sits a little right of and below the centre of its glyph box.
   The still body's width, 90% of Nik's place, is the moving body's resting
   width too; --glass-size is the glass's, as a share of the same place.
   --glass-opacity and --glass-blend say how the glass is laid on the body. */
.ws-orb .ws-blob { --glass-size: 1.03; --glass-opacity: 1; --glass-blend: color-dodge; position: relative; isolation: isolate; left: -.093em; top: -.102em; padding: 0; font-size: 4.05cqw; background: none; box-shadow: none; }
.ws-orb .ws-blob::before {
  content: ""; position: absolute; left: 50%; top: 50%; z-index: -1; border-radius: 50%;
  width: calc(.9 * 21.2cqw); height: calc(.9 * 21.2cqw * 4.178 / 4.541); translate: calc(-50% + .093em) calc(-50% + .102em);
  background: rgba(28, 18, 56, .8) url(/site/bubble.webp) center / 100% 100% no-repeat;
}
.ws-orb .ws-blob:has(canvas)::before { visibility: hidden; }
.ws-orb canvas { position: absolute; left: 50%; top: 50%; z-index: -1; width: 48cqw; height: 44cqw; translate: -50% -50%; pointer-events: none; }
@keyframes ws-bob { to { translate: 0 -5px; } }
.ws-mascot { margin: 0; font-family: var(--font-mono); font-size: 27px; line-height: 1.1; transform: scaleX(.8); color: #d9c8ff; text-shadow: 0 0 10px rgba(190, 160, 255, .8); user-select: none; cursor: pointer; }
.ws-orb .ws-mascot { font-size: 1em; color: #fff; text-shadow: 0 0 .25em rgba(255, 255, 255, .6); }
.ws-sky.unread .ws-mascot { opacity: .5; }
.ws-sky-text { position: relative; max-width: 52%; text-shadow: 0 1px 12px rgba(10, 40, 100, .35); }
.ws-sky-status { display: inline-flex; align-items: center; gap: 8px; padding: 5px 11px 5px 9px; border-radius: 999px; background: rgba(255, 255, 255, .2); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, .3); font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; text-shadow: none; }
.ws-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: rgba(255, 255, 255, .7); }
.ws-dot.running { background: #4ade80; box-shadow: 0 0 0 0 rgba(74, 222, 128, .6); animation: ws-breathe 2.4s ease-out infinite; }
.ws-dot.down { background: #fbbf24; }
.ws-dot.unread { background: #f87171; }
@keyframes ws-breathe { 70% { box-shadow: 0 0 0 7px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
.ws-sky .ws-eyebrow { color: rgba(255, 255, 255, .86); }
.ws-sky .ws-faint { color: var(--night-faint); }
.ws-sky h1 { margin: 4px 0 8px; font-size: 42px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.04; }
.ws-sky p { color: rgba(255, 255, 255, .92); font-size: 16px; max-width: 44ch; }
.ws-sky .ws-sky-status { margin-top: 16px; }
@media (prefers-reduced-motion: reduce) { .ws-blob, .ws-dot.running { animation: none; } }

/* A new family's first Nik wakes here (dashboard.gsx, dashboardHatch): the
   brand's sky over the whole window, with nothing on it to press. The
   picture is the window's height. On a tall window Nik stands in the middle
   of it, and on a wide one it keeps to the right edge as the band's does, so
   the island is always in view. Nik's size is set apart from the picture's,
   which would draw him wider than a phone. The stage tints the sky: night
   before a capsule is assigned, dusk while it comes up, day once he wakes,
   and dusk gone grey when it failed. The sky opens out of the band's place
   and closes back into it when it settles into the dashboard. */
.ws-hatch {
  position: fixed; top: 0; left: 0; right: 0; height: var(--hatch-h); z-index: 70; overflow: hidden; isolation: isolate;
  color: #fff; background: url(/site/sky.jpg) var(--hatch-x) 0 / auto 100% no-repeat, #3a83de;
  clip-path: inset(0); animation: ws-hatch-in 1.1s cubic-bezier(.65, 0, .25, 1) both;
  transition: clip-path .7s cubic-bezier(.65, 0, .25, 1), filter 2.4s ease;
}
@keyframes ws-hatch-in { from { clip-path: inset(0 0 calc(100% - var(--hatch-band)) var(--hatch-side)); } to { clip-path: inset(0); } }
.ws-hatch[data-stage="settle"] { clip-path: inset(0 0 calc(100% - var(--hatch-band)) var(--hatch-side)); }
.ws-hatch[data-stage="failed"] { filter: grayscale(.6); }
body:has(#nik-hatch) { overflow: hidden; }
body:has(#nik-hatch) .ws-side { visibility: hidden; }
.ws-hatch::before { /* keeps the words readable over the bright sky */
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(180deg, rgba(14, 50, 130, .42), transparent 42%);
}
@media (min-width: 761px) { .ws-hatch::before { background: linear-gradient(90deg, rgba(14, 60, 140, .42), rgba(14, 60, 140, .12) 45%, transparent 60%); } }
.ws-hatch .ws-sky-fill { right: calc(100% - var(--hatch-x) - 1px); }
.ws-hatch .ws-sky-fill::after { left: calc(3 * var(--hatch-h) - 1px); width: calc(3 * var(--hatch-h) + 1px); }
.ws-hatch-night, .ws-hatch-dusk { position: absolute; inset: 0; z-index: -1; mix-blend-mode: multiply; opacity: 0; pointer-events: none; transition: opacity 2.4s ease; }
.ws-hatch-night { background: var(--sky-night); }
.ws-hatch-dusk { background: var(--sky-dusk); }
.ws-hatch[data-stage="night"] .ws-hatch-night { opacity: 1; }
.ws-hatch[data-stage="dawn"] .ws-hatch-night { opacity: .3; }
.ws-hatch[data-stage="dawn"] .ws-hatch-dusk, .ws-hatch[data-stage="failed"] .ws-hatch-dusk { opacity: 1; }
.ws-hatch .ws-scene { top: 0; right: auto; bottom: auto; left: var(--hatch-x); width: calc(3 * var(--hatch-h)); height: var(--hatch-h); }
/* The front mounds stand in front of the tints, so they take the stage's
   light themselves. Their cut-out ends in a straight edge 77.5% down, which
   crosses the whole of a phone's screen here, so they fade out above it into
   the picture's own moss. */
.ws-hatch .ws-scene::after {
  transition: filter 2.4s ease;
  -webkit-mask: linear-gradient(#000 71%, transparent 77.5%); mask: linear-gradient(#000 71%, transparent 77.5%);
}
.ws-hatch[data-stage="night"] .ws-scene::after { filter: brightness(.28) saturate(.55); }
.ws-hatch[data-stage="dawn"] .ws-scene::after, .ws-hatch[data-stage="failed"] .ws-scene::after { filter: brightness(.62) saturate(.85); }
/* Nik sits in the island's moss at his own size: Nik's place is a container
   here, and the band's sizes, shares of the picture there, are restated as
   shares of his place. */
.ws-hatch .ws-orb { container-type: inline-size; left: calc(81.7% - var(--hatch-nik) / 2); top: calc(62% - var(--hatch-nik) * .85 - 8px); width: var(--hatch-nik); }
.ws-hatch .ws-orb .ws-blob { font-size: 19.1cqw; }
.ws-hatch .ws-orb .ws-blob::before { width: 90cqw; height: calc(90cqw * 4.178 / 4.541); }
.ws-hatch .ws-orb canvas { width: 226.4cqw; height: 207.5cqw; }
.ws-hatch .ws-mascot { cursor: default; }
.ws-hatch[data-stage="night"] .ws-mascot { text-shadow: 0 0 .3em rgba(214, 196, 255, .95), 0 0 .9em rgba(160, 140, 255, .8); animation: ws-hatch-breathe 2.6s ease-in-out infinite; }
@keyframes ws-hatch-breathe { 50% { opacity: .55; } }
/* What Nik says sits in one cell, so a stage's words take the place of the
   last stage's. */
.ws-hatch-words { position: absolute; left: 28px; right: 28px; top: calc(env(safe-area-inset-top, 0px) + 72px); display: grid; max-width: 520px; text-shadow: 0 1px 14px rgba(10, 20, 70, .45); transition: opacity .4s ease; }
.ws-hatch-words > * { grid-area: 1 / 1; }
.ws-hatch[data-stage="settle"] .ws-hatch-words { opacity: 0; }
@media (min-width: 761px) { .ws-hatch-words { left: 120px; right: auto; top: 30%; width: 520px; } }
.ws-hatch-hello, .ws-hatch-failed, .ws-hatch-slow { visibility: hidden; opacity: 0; transform: translateY(10px); transition: opacity .9s ease, transform .9s ease, visibility 0s .9s; }
.ws-hatch[data-stage="hello"] .ws-hatch-hello,
.ws-hatch[data-stage="failed"] .ws-hatch-failed,
.ws-hatch[data-slow]:is([data-stage="night"], [data-stage="dawn"]) .ws-hatch-slow { visibility: visible; opacity: 1; transform: none; transition: opacity .9s ease, transform .9s ease, visibility 0s; }
.ws-hatch-words p { margin: 0; max-width: 36ch; font-size: 17px; line-height: 1.4; color: rgba(255, 255, 255, .94); }
.ws-hatch-words .ws-hatch-says { margin-bottom: 8px; font-size: 34px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.04; color: #fff; }
@media (min-width: 761px) {
  .ws-hatch-words p { font-size: 20px; }
  .ws-hatch-words .ws-hatch-says { font-size: 56px; }
}
.ws-hatch-failed a { display: inline-flex; align-items: center; min-height: 44px; margin-top: 6px; color: #fff; font-size: 15px; font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .ws-hatch { animation: none; }
  .ws-hatch-hello, .ws-hatch-failed, .ws-hatch-slow { transform: none; }
  .ws-hatch[data-stage="night"] .ws-mascot { animation: none; }
}

/* A board: what waits on the reader across the top, panels two side by
   side in each row, split by a hairline, and a line across the foot; one
   under another on a narrow screen. While its parts load, they are their
   own skeletons (.ws-skeleton). */
.ws-board { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.ws-board-needs, .ws-board-foot { grid-column: 1 / -1; }
.ws-board-needs { background: var(--warn-soft); }
.ws-board-panel:nth-child(even of .ws-board-panel) { border-left: var(--hairline) solid var(--line-strong); }
@media (max-width: 900px) {
  .ws-board { grid-template-columns: minmax(0, 1fr); }
  .ws-board-panel:nth-child(even of .ws-board-panel) { border-left: 0; }
}
/* Who the Nik plays, in a panel: the line that sums the character up, its
   facts, the things it says as bubbles, and its strongest traits as ten
   steps each, as the soul page draws them. */
.ws-character { display: flex; flex-direction: column; gap: 14px; padding: 2px var(--ws-x) 8px; }
.ws-character-line { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
.ws-character-sayings { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; }
.ws-character-saying { padding: 5px 12px; border-radius: 14px 14px 14px 4px; background: var(--accent-soft); font-size: 13.5px; quotes: "\201C" "\201D"; }
.ws-traits { margin: 0; padding: 0; list-style: none; }
.ws-trait { display: flex; align-items: center; gap: 12px; min-height: 32px; border-bottom: var(--hairline) solid var(--line); }
.ws-trait-name { flex: 0 0 8.5em; min-width: 0; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-trait-steps { flex: 1; display: flex; gap: 3px; min-width: 60px; }
.ws-trait-step { flex: 1; height: 8px; border-radius: 3px; background: var(--accent-soft); }
.ws-trait-step.on { background: var(--accent); }
.ws-trait-n { flex: none; width: 22px; text-align: right; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; }
/* A strip of counts split by hairlines, each a label over its figure and a
   word on what the figure covers: Home's record and the activations page. */
.ws-counts { display: flex; flex-wrap: wrap; margin: 0; border-bottom: var(--hairline) solid var(--line-strong); }
.ws-counts > div { flex: 1 1 130px; display: flex; flex-direction: column; gap: 2px; padding: 12px 20px; border-right: var(--hairline) solid var(--line); }
.ws-counts > div:first-child { padding-left: var(--ws-x); }
.ws-counts dt { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--fg2); }
.ws-counts-mark { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 6px; background: var(--accent-soft); color: var(--accent); }
.ws-counts-mark .ws-glyph { width: 12px; height: 12px; }
.ws-counts dd { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 6px; font-size: 12px; color: var(--fg3); }
.ws-counts-n { font-family: var(--font-mono); font-size: 20px; font-weight: 600; color: var(--fg); white-space: nowrap; }
/* A figure that sums days draws them as a line along its foot, edge to
   edge across its cell; a figure that warns is tinted. */
.ws-counts > div:has(.ws-spark) { padding-bottom: 0; }
.ws-counts .ws-spark { display: block; flex: none; width: calc(100% + 40px); height: 34px; margin: 8px -20px 0; }
.ws-counts > div:first-child .ws-spark { width: calc(100% + 20px + var(--ws-x)); margin-left: calc(-1 * var(--ws-x)); }
.ws-spark-area { fill: var(--accent-soft); }
.ws-spark-line { fill: none; stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.ws-counts > .warn .ws-counts-mark { background: var(--danger-soft); color: var(--danger); }
.ws-counts > .warn .ws-counts-n { color: var(--danger); }
.ws-counts > .warn .ws-spark-area { fill: var(--danger-soft); }
.ws-counts > .warn .ws-spark-line { stroke: var(--danger); }
/* A panel is its section's heading over its entries, which reach its
   edges. */
.ws-panel { height: 100%; padding-bottom: 14px; border-bottom: var(--hairline) solid var(--line-strong); }
/* Views under a choice of words: the choice's radio buttons show the
   chosen one's view alone, so a switch reads nothing again. */
.ws-views > .ws-choice { display: flex; padding: 0 var(--ws-x); border-bottom: var(--hairline) solid var(--line-strong); }
.ws-views > .ws-view { display: none; }
.ws-views:has(> .ws-choice > :nth-child(1) > input:checked) > .ws-view:nth-child(2),
.ws-views:has(> .ws-choice > :nth-child(2) > input:checked) > .ws-view:nth-child(3),
.ws-views:has(> .ws-choice > :nth-child(3) > input:checked) > .ws-view:nth-child(4),
.ws-views:has(> .ws-choice > :nth-child(4) > input:checked) > .ws-view:nth-child(5) { display: block; }
@media (max-width: 760px) {
  .ws-counts > div { flex-basis: 33%; padding: 10px 16px; }
  .ws-counts > div:first-child { padding-left: 16px; }
  .ws-counts .ws-spark, .ws-counts > div:first-child .ws-spark { width: calc(100% + 32px); margin-inline: -16px; }
}

.ws-logo { display: grid; place-items: center; flex: none; width: 36px; height: 36px; border-radius: 11px; background: var(--accent-soft); color: var(--accent); font-size: 15px; font-weight: 700; text-transform: uppercase; }
.ws-logo.brand { background: var(--card); box-shadow: 0 0 0 var(--hairline) var(--line-strong); }
.ws-logo.brand svg { width: 18px; height: 18px; }
.ws-logo.brand svg.ws-wordmark { width: 26px; height: 26px; }
.ws-logo.brand svg.ws-app-icon { width: 100%; height: 100%; border-radius: inherit; }
/* What the reader does elsewhere to finish: the page to open and the code
   to enter there. The code is read off the screen and typed on another
   device, so it is large and selects whole. */
.ws-handoff { display: flex; flex-direction: column; gap: 6px; padding: 2px 0 2px 14px; border-left: 2px solid var(--accent); }
.ws-handoff-label { margin: 0; font-size: 13px; color: var(--fg2); }
.ws-handoff-label:not(:first-child) { margin-top: 6px; }
.ws-handoff-page { overflow-wrap: anywhere; }
.ws-handoff-code { margin: 0; font-family: var(--font-mono); font-size: var(--t-lead); font-weight: 600; letter-spacing: var(--tracking-caps); user-select: all; -webkit-user-select: all; }
.ws-handoff-address { margin: 0; font-family: var(--font-mono); overflow-wrap: anywhere; user-select: all; -webkit-user-select: all; }
.ws-handoff > .ws-notice { margin-top: 6px; }
/* A form that saves one secret pasted into it. */
.ws-paste { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-width: 460px; }
.ws-paste label { font-size: 13px; color: var(--fg2); }
.ws-paste-line { display: flex; gap: 8px; }
.ws-paste-line .ws-input { flex: 1; }
/* An empty list says so beside an empty tray, with what the reader can
   do about it after the words. */
.ws-empty { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 16px var(--ws-x); font-size: 14px; color: var(--fg2); }
.ws-empty > p { flex: 1 1 auto; min-width: 0; margin: 0; }
.ws-blank { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 48px var(--ws-x); font-size: 14px; color: var(--fg2); text-align: center; }
.ws-blank > p { margin: 0; }
.ws-blank-mark { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 16px; background: var(--accent-soft); color: var(--accent); }
.ws-blank-mark > .ws-glyph { width: 24px; height: 24px; }
.ws-blank-mark.nik { height: 40px; background: var(--blob); color: var(--night-ink); font-family: var(--font-mono); font-size: 12px; }
.ws-hits > .ws-blank { padding-block: 36px; }
.ws-part > .ws-empty { padding: 4px 0; }
.ws-empty::before { content: ""; flex: none; width: 18px; height: 18px; background: var(--fg3); -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M22%2012h-6l-2%203h-4l-2-3H2%22%20%2F%3E%3Cpath%20d%3D%22M5.5%205.1%202%2012v6a2%202%200%200%200%202%202h16a2%202%200%200%200%202-2v-6l-3.5-6.9A2%202%200%200%200%2016.8%204H7.2a2%202%200%200%200-1.7%201.1z%22%20%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M22%2012h-6l-2%203h-4l-2-3H2%22%20%2F%3E%3Cpath%20d%3D%22M5.5%205.1%202%2012v6a2%202%200%200%200%202%202h16a2%202%200%200%200%202-2v-6l-3.5-6.9A2%202%200%200%200%2016.8%204H7.2a2%202%200%200%200-1.7%201.1z%22%20%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; }

/* A pane's bar: its name or what it starts with, and its actions at the
   end, over a hairline, inset as the pane's own words are. */
.ws-pane-bar { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; min-height: 44px; padding: 6px var(--pane-x); border-bottom: var(--hairline) solid var(--line-strong); background: var(--card); font-size: 14px; }
.ws-pane-bar-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); }
.ws-pane-bar-end { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-left: auto; }

/* A notice is a strip across the surface, tinted for what it says. */
.ws-notice { display: flex; align-items: flex-start; gap: 12px; padding: 12px var(--ws-x); background: var(--accent-soft); font-size: 14px; }
.ws-notice.bad { background: var(--danger-soft); }
.ws-notice-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ws-notice-text > p { white-space: pre-wrap; }

/* Words someone wrote, as they wrote them. */
.ws-text { margin: 0; max-width: 68ch; font-size: 15px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
.ws-notice-mark { flex: none; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); font-weight: 700; font-size: 13px; }
.ws-notice-mark svg[viewBox] { width: 14px; height: 14px; }
.ws-notice.bad .ws-notice-mark { background: #fff; color: var(--danger); }

/* A dropdown that acts when it changes, drawn as a small button with a
   chevron: a conversation's access, the account's language. */
.ws-dropdown { display: flex; align-items: center; gap: 8px; flex: none; }
.ws-dropdown select {
  appearance: none; padding: 6px 30px 6px 13px; border: 0; border-radius: 999px; cursor: pointer;
  background: var(--accent-soft) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237c5cd6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 10px center / 14px;
  color: var(--accent); font: inherit; font-size: 13px; line-height: 1.3; font-weight: 500;
}
.ws-dropdown select:hover { background-color: var(--accent-hover); }

/* The family's page: who is asking, then one row per person with how they
   sign in. */
.ws-badge { margin-left: auto; min-width: 20px; padding: 1px 7px; border-radius: 999px; background: var(--warn); color: #fff; font-size: 11.5px; font-weight: 700; text-align: center; }

/* Remove asks once, in place; its button reads Keep while the question is
   open, and closing it answers no. */
.confirm:not([open]) .when-open, .confirm[open] .when-closed { display: none; }
.confirm-body { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px; margin-top: 10px; padding: 12px 14px; border-radius: 12px; background: var(--danger-soft); font-size: 13.5px; }
.confirm-body p { flex: 1 1 220px; }

@media (max-width: 760px) {
  .ws { display: block; }
  /* The sidebar lies down across the top, still the night, without its
     moss; the links take their own row under the brand and scroll across. */
  .ws-side {
    position: sticky; z-index: 10; height: auto; overflow: visible; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px 10px;
    padding: calc(10px + env(safe-area-inset-top)) 14px 8px;
  }
  .ws-side-foot { position: static; margin: 0 0 0 auto; padding: 0; background: none; }
  .ws-side-foot::before, .ws-side-foot::after { content: none; }
  .ws-brand { padding: 0; }
  .ws-brand svg { width: 28px; height: 28px; }
  .ws-brand span { display: none; }
  .ws-nav { order: 3; flex-basis: 100%; flex-direction: row; gap: 2px; margin: 0 -14px; padding: 0 14px; overflow-x: auto; scrollbar-width: none; }
  .ws-nav-group { display: contents; }
  .ws-nav a { flex-shrink: 0; min-height: 32px; padding: 0 10px; font-size: 14px; white-space: nowrap; }
  .ws-nav svg { display: none; }
  .ws-nav-head { display: none; }
  .ws-you { flex: none; min-height: 0; padding: 0; border-radius: 50%; background: none; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
  .ws-you:hover, .ws-you[aria-current="page"] { background: none; }
  .ws-who, .ws-you > .ws-glyph { display: none; }
  .ws-side-foot { align-items: center; }
  .ws-help { min-width: 0; padding: 4px; background: none; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
  .ws-help span { display: none; }
  .ws h1 { font-size: 26px; }
  .ws-nav .ws-badge { margin-left: 4px; }
  /* The words stand on the open sky above; the picture's moss and Nik
     keep their proportions in the strip below them. */
  .ws-sky {
    height: auto; min-height: 0; flex-direction: column; align-items: stretch; padding: 22px 20px calc(min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px - 40px);
    background:
      linear-gradient(180deg, #3a83de 0, #3a83de calc(100% - (min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px)), rgba(58, 131, 222, 0) calc(100% - (min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px) + 70px)),
      url(/site/sky.jpg) right bottom / auto calc(min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px) no-repeat, #3a83de;
  }
  .ws-sky::before { background: linear-gradient(180deg, rgba(14, 60, 140, .35), transparent 55%); }
  .ws-sky-text { max-width: none; }
  .ws-sky h1 { font-size: 30px; }
  .ws-scene { top: auto; left: auto; height: calc(min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px); width: calc((min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px) * 3); container-type: inline-size; }
}

/* A dialog over the page, for editing something the page lists: a mark
   and a title with a word under it, the fields, then what to do at the
   foot. Native popover, so the browser puts it in the top layer, closes it
   on Escape or a click outside, and moves focus in and back again. */
.ws-modal[popover] {
  width: min(540px, calc(100vw - 32px)); max-height: calc(100dvh - 48px); margin: auto; padding: 0;
  border: 0; border-radius: 18px; background: var(--card); color: var(--fg);
  box-shadow: 0 30px 80px rgba(24, 17, 40, .42), 0 0 0 var(--hairline) rgba(43, 33, 64, .18);
  overflow: auto; opacity: 0; transform: translateY(10px) scale(.98);
  transition: opacity .16s ease, transform .2s cubic-bezier(.2, .8, .2, 1), overlay .2s allow-discrete, display .2s allow-discrete;
}
.ws-modal[popover]:popover-open { opacity: 1; transform: none; }
@starting-style { .ws-modal[popover]:popover-open { opacity: 0; transform: translateY(10px) scale(.98); } }
.ws-modal[popover]::backdrop { background: rgba(24, 17, 40, 0); -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); transition: background .2s, backdrop-filter .2s, overlay .2s allow-discrete, display .2s allow-discrete; }
.ws-modal[popover]:popover-open::backdrop { background: rgba(24, 17, 40, .45); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
@starting-style { .ws-modal[popover]:popover-open::backdrop { background: rgba(24, 17, 40, 0); backdrop-filter: blur(0); } }
.ws-modal-head { display: flex; align-items: flex-start; gap: 14px; padding: 20px 20px 16px 22px; }
.ws-modal-head h2 { margin: 2px 0 4px; font-size: 18px; font-weight: 700; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.ws-modal-head p { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--fg2); }
.ws-modal-mark { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 12px; background: var(--accent-grad); color: #fff; box-shadow: var(--shadow-btn); }
.ws-modal-mark .ws-glyph { width: 20px; height: 20px; }
.ws-modal-danger .ws-modal-mark { background: var(--danger-grad); box-shadow: var(--shadow-btn-danger); }
.ws-modal-close { flex: none; display: grid; place-items: center; width: 32px; height: 32px; margin: -4px -4px 0 0; border: 0; border-radius: 50%; background: transparent; color: var(--fg3); cursor: pointer; }
.ws-modal-close:hover { background: var(--hover); color: var(--fg); }
.ws-modal-close .ws-glyph { width: 18px; height: 18px; }
.ws-modal-body { padding: 4px 22px 20px; }
.ws-modal-body .ws-notice { margin: 0; }
.ws-modal-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; padding: 14px 20px 14px 22px; border-top: var(--hairline) solid var(--line); background: var(--bg); }
.ws-modal-note { display: flex; align-items: center; gap: 7px; padding: 10px 12px; border-radius: 10px; background: var(--accent-soft); font-size: 12.5px; color: var(--fg2); }
.ws-modal-note .ws-glyph { width: 13px; height: 13px; flex: none; }
.ws .ws-modal .ws-btn svg { width: 15px; height: 15px; }
@media (max-width: 760px) {
  .ws-modal[popover] { width: 100vw; max-width: none; max-height: 92dvh; margin: auto 0 0; border-radius: 18px 18px 0 0; }
}
/* The value box masks what is typed until Show is ticked, so a pasted key
   is not on screen by default and its lines stay where they were. */
.ws-value-field { display: flex; flex-direction: column; gap: 7px; }
.ws-value-field > p { margin: 0; font-size: 12.5px; color: var(--fg2); }
.ws-value-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 13px; font-weight: 600; }
.ws-value-top label { display: inline-flex; flex-direction: row; }
.ws-reveal { align-items: center; gap: 6px; font-weight: 500; color: var(--fg2); cursor: pointer; }
.ws-reveal-box { width: 15px; height: 15px; margin: 0; accent-color: var(--accent); }
textarea.ws-value {
  width: 100%; min-width: 0; min-height: 104px; padding: 10px 14px; resize: vertical;
  border: var(--edge) solid var(--line-strong); border-radius: 12px;
  background: var(--bg); color: var(--fg); font-family: var(--font-mono); font-size: 13.5px; line-height: 1.5;
  white-space: pre; overflow-wrap: normal; overflow-x: auto;
  -webkit-text-security: disc;
}
textarea.ws-value:focus { outline: 2px solid var(--accent); outline-offset: 1px; background: var(--card); }
.ws-value-field:has(.ws-reveal-box:checked) .ws-value { -webkit-text-security: none; }
.ws-notice-action { margin-top: 10px; }

/* The settings page, as the D design draws it: the band's status, the
   section links under it, then one form whose sections each say what they
   change on the left and hold their fields on the right. Choices are words
   underlined when chosen; a change saves the form. */
.ws-saved { font-size: 15px; font-weight: 600; }
.ws-settings-section { display: flex; flex-wrap: wrap; gap: 16px 40px; padding: 26px var(--ws-x); border-bottom: var(--hairline) solid var(--line-strong); }
.ws-settings-about { flex: 1 1 240px; display: flex; flex-direction: column; gap: 6px; }
.ws-settings-kicker { margin: 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); }
.ws-settings-about h2 { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; }
.ws-settings-sub { margin: 0; font-size: 14px; line-height: 1.5; color: var(--fg2); }
.ws-settings-fields { flex: 999 1 460px; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.ws-settings-block { display: flex; flex-direction: column; gap: 12px; padding: 16px 0; border-top: var(--hairline) solid var(--line); }
.ws-settings-block:first-child { padding-top: 0; border-top: 0; }
.ws-settings-block:last-child { padding-bottom: 0; }
.ws-settings-block h3 { font-size: 14px; font-weight: 600; }
.ws-settings-label { flex: none; width: 120px; font-size: 14px; color: var(--fg2); }
.ws-settings-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; }
.ws-settings-control { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 20px; min-width: 0; }
/* A field of words beside its button shares the line with it. */
.ws-settings-control:has(> .ws-input:not(.number) + .ws-btn) { flex: 1 1 260px; flex-wrap: nowrap; gap: 10px; }
.ws-settings-control > .ws-input:not(.number) { flex: 1 1 auto; width: auto; min-width: 0; max-width: 420px; }
.ws-unit { font-size: 13px; color: var(--fg2); }
.ws-settings-hint { margin: 0; font-size: 13px; color: var(--fg2); }
.ws-settings-line > .ws-settings-hint { flex-basis: 100%; padding-left: 138px; }
.ws-settings-wheres { display: flex; flex-direction: column; }
.ws-settings-wheres .ws-settings-label { width: auto; padding-bottom: 2px; }
.ws-settings-where { display: flex; align-items: flex-start; gap: 12px; padding: 8px 0; cursor: pointer; }
.ws-settings-where input { flex: none; width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--accent); }
.ws-settings-where span { display: flex; flex-direction: column; gap: 1px; }
.ws-settings-where b { font-size: 15px; font-weight: 600; }
.ws-settings-where small { font-size: 14px; color: var(--fg2); }
.ws-settings-field { display: flex; flex-direction: column; gap: 6px; max-width: 420px; }
.ws-settings-field label { font-size: 14px; font-weight: 600; }
.ws-dropdown select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ws-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.ws-chip { position: relative; display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 6px 0 12px; border-radius: 999px; background: var(--accent-soft); color: var(--fg); font-size: 14px; cursor: pointer; }
.ws-chip input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.ws-chip span { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; color: var(--fg2); }
.ws-chip:hover span { background: var(--hover); color: var(--fg); }
.ws-chip:has(input:not(:checked)) { opacity: .45; text-decoration: line-through; }
.ws-chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A box the reader ticks, its words beside it: a yes or no in a dialog
   or a setting. In a dialog's form it stays a row rather than stacking
   its words over the box as a labelled field does. */
.ws-settings-tick { display: flex; flex-direction: column; gap: 6px; }
.ws-settings-tick > .ws-settings-hint { padding-left: 28px; }
.ws-checkbox, .ws-form label.ws-checkbox { display: flex; flex-direction: row; align-items: flex-start; gap: 10px; max-width: 520px; font-size: 14px; font-weight: 400; line-height: 1.4; cursor: pointer; }
.ws-checkbox input { flex: none; width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--accent); cursor: pointer; }
.ws-checkbox:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.ws-chips-add { display: flex; gap: 10px; max-width: 420px; }
.ws-chips-add .ws-input { flex: 1; }
/* A field the reader types lines into: drawn as a field is, or, as prose,
   as a paragraph of the page they edit where it stands, which grows with
   what it holds. */
.ws-textarea { height: auto; min-height: 96px; padding: 10px 12px; line-height: 1.5; resize: vertical; }
.ws-textarea.prose { min-height: 0; margin: 0 -8px; padding: 4px 8px; border-color: transparent; background: transparent; font-size: 16px; line-height: 1.55; field-sizing: content; resize: none; }
.ws-textarea.prose:hover { background: var(--hover); }
.ws-textarea.prose:focus { background: var(--card); }
/* Short facts edited where they stand: each a tag that is its own field,
   its name small over its value, dashed while it holds nothing; the big
   one is the headline the others sit under. */
.ws-facets { display: flex; flex-wrap: wrap; gap: 8px; }
.ws-facet { display: inline-flex; flex-direction: column; justify-content: center; gap: 1px; max-width: 100%; min-height: 48px; padding: 5px 14px; border: var(--edge) solid var(--line-strong); border-radius: 12px; background: var(--card); cursor: text; }
.ws-facet:has(.ws-facet-value:placeholder-shown) { border-style: dashed; background: transparent; }
.ws-facet:focus-within { border-style: solid; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ws-facet-name { padding: 0; border: 0; background: transparent; font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--fg2); }
input.ws-facet-name { field-sizing: content; min-width: 18ch; max-width: 100%; }
.ws-facet-value { field-sizing: content; min-width: 8ch; max-width: 100%; padding: 0; border: 0; background: transparent; color: var(--fg); font: inherit; font-size: 15px; font-weight: 600; }
.ws-facet-value::placeholder, input.ws-facet-name::placeholder { color: var(--fg2); font-weight: 400; }
.ws-facet-value:focus, input.ws-facet-name:focus { outline: none; }
.ws-facet.big { display: flex; min-height: 0; padding: 0; border: 0; background: transparent; box-shadow: none; }
.ws-facet.big .ws-facet-value { width: 100%; field-sizing: fixed; margin: 0 -8px; padding: 2px 8px; border-radius: 10px; font-size: 28px; font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; }
.ws-facet.big .ws-facet-value:hover { background: var(--hover); }
.ws-facet.big .ws-facet-value:focus { background: var(--card); outline: 2px solid var(--accent); }
/* The things someone says, each a speech bubble edited where it stands,
   and a dashed one that takes another. */
.ws-sayings { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ws-saying { field-sizing: content; min-width: 10ch; max-width: 100%; height: 38px; padding: 0 14px; border: 0; border-radius: 18px 18px 18px 6px; background: var(--accent); color: var(--accent-ink); font: inherit; font-size: 15px; }
.ws-saying.add { border: var(--edge) dashed var(--line-strong); background: transparent; color: var(--fg); }
.ws-saying::placeholder { color: var(--fg2); }
.ws-saying:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A trait's level from 0 to 10: eleven steps, those up to the chosen one
   filled and the first a ring that reads as none; its number; the lock
   that keeps it where the reader put it; and its last change. */
.ws-level { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; min-height: 48px; padding: 6px 0; border-bottom: var(--hairline) solid var(--line); }
.ws-level-trait { flex: 0 0 120px; font-size: 15px; font-weight: 600; }
.ws-level-steps { flex: 1 1 240px; display: flex; align-items: center; gap: 3px; min-width: 0; }
.ws-step { position: relative; flex: 1 1 0; display: flex; align-items: center; height: 30px; cursor: pointer; }
.ws-step::before { content: ""; width: 100%; height: 10px; border-radius: 3px; background: var(--accent-soft); }
.ws-step:first-child { flex: 0 0 14px; }
.ws-step:first-child::before { width: 10px; border-radius: 50%; background: transparent; box-shadow: inset 0 0 0 var(--edge) var(--line-strong); }
.ws-step:not(:first-child):has(input:checked)::before,
.ws-step:not(:first-child):has(~ .ws-step input:checked)::before { background: var(--accent); }
.ws-step:first-child:has(input:checked)::before { box-shadow: inset 0 0 0 2px var(--accent); }
.ws-step:not(:first-child):hover::before { background: var(--accent-hover); }
.ws-step:not(:first-child):has(input:checked):hover::before { background: var(--accent); }
.ws-step input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.ws-level-steps:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.ws-level-n { flex: none; width: 24px; text-align: right; font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ws-level-lock { position: relative; flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; color: var(--fg3); cursor: pointer; }
.ws-level-lock .ws-glyph { width: 16px; height: 16px; }
.ws-level-lock input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.ws-level-lock:hover { background: var(--hover); color: var(--fg2); }
.ws-level-lock:has(input:checked) { background: var(--accent-soft); color: var(--accent); }
.ws-level-lock:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.ws-level-last { flex: 1 1 200px; min-width: 0; font-size: 13px; line-height: 1.4; color: var(--fg2); }
.ws-settings-foot { padding: 18px var(--ws-x); }
@media (max-width: 760px) {
  .ws-settings-label { width: 100%; }
  .ws-settings-line > .ws-settings-hint { padding-left: 0; }
}

/* A one-shot message carried across a redirect. Something happened — or was
   refused — somewhere the user cannot see, and the page they land on has to
   say so. Spans the column like every other child of .page, so it brings no
   margins of its own. */
.notice {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 14px var(--inset);
  border-radius: var(--radius);
  background: var(--accent-soft);
  font-size: var(--t-fine);
}
.notice .mark { flex: none; font-weight: 700; color: var(--accent); }
.notice.warn { background: var(--danger-soft); }
.notice.warn .mark { color: var(--danger); }

/* Where nik runs: three tiers as a choice, not a list. The cards carry four
   labelled facts because four is all that differs between them, and reading
   them down a column is the comparison. On a phone they stack, which is also
   the reading order we want — cheapest and simplest first. */
/* Six rows — the head, four facts, the action — shared across all three
   columns via subgrid, so BILL sits level with BILL and the cards read as a
   table you scan down rather than three paragraphs you compare by memory.
   Without it, a pitch one line longer pushes that column's facts out of step
   and the comparison stops being one. */
.tiers {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto repeat(4, auto) auto;
}
.tier {
  display: grid; grid-row: span 6; grid-template-rows: subgrid; gap: 12px;
  padding: 14px; border-radius: var(--radius);
  border: var(--edge) solid var(--line); background: var(--bg);
}
.tier.soon { opacity: 0.72; }
.tier-head { display: flex; flex-direction: column; gap: 2px; }
.tier-lead { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--gap); }
.tier-facts { display: grid; grid-row: span 4; grid-template-rows: subgrid; gap: 8px; }
.fact { display: flex; flex-direction: column; }
.fact-k {
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--fg3);
}
.fact-v { font-size: var(--t-fine); color: var(--fg2); }
.tier-act { display: flex; }

/* The states gallery (/admin/states): every page at phone width, so mobile is
   what an operator sees first rather than what they remember to check. The
   frames are real pages in iframes, not screenshots — they cannot go stale. */
.gallery {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(375px, 1fr));
}
.shot {
  display: flex; flex-direction: column; gap: 8px;
  background: var(--card); border-radius: var(--radius-lg);
  border: var(--edge) solid var(--line);
  box-shadow: var(--shadow-card);
  padding: 14px;
}
.shot-label { display: flex; flex-direction: column; }
.shot-frame {
  width: 375px; height: 720px; max-width: 100%;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
}

/* The pairing QR. Pixelated on purpose: a QR is a bitmap of hard squares and
   smoothing it is what makes a scan fail. */
.qr {
  width: 100%; max-width: 280px; aspect-ratio: 1; align-self: center;
  image-rendering: pixelated;
  border-radius: var(--radius-sm);
  background: var(--card);
}

/* Numbered steps: a run-book inside a card. */
.step { display: flex; gap: 14px; align-items: flex-start; padding: 16px 18px; }
.step + .step { border-top: var(--hairline) solid var(--line); }
.step .n {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent);
  font-weight: 700; font-size: var(--t-callout); display: grid; place-items: center;
}
.step .body strong { display: block; }
.step .body span { color: var(--fg2); font-size: var(--t-fine); }

/* ---- controls ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-grad); color: var(--accent-ink);
  border: none; cursor: pointer;
  padding: 12px 24px; border-radius: var(--radius-pill); text-decoration: none;
  box-shadow: var(--shadow-btn);
  /* Regular weight, body line-height: the landing's CTA is the reference for
     every button in the product, and it is a quiet label in a loud pill. Set
     these explicitly because <button> does not inherit type from the page —
     without them an <a class="btn"> and a <button class="btn"> come out
     different heights. */
  font-family: inherit; font-size: var(--t-callout);
  font-weight: 400; line-height: var(--lh-body);
}
.btn.block { display: flex; width: 100%; }
.btn.danger { background: var(--danger-grad); box-shadow: var(--shadow-btn-danger); }
.btn:active { opacity: 0.75; }
.btn.small { padding: 6px 14px; border-radius: var(--radius-pill); position: relative; }
.btn.tinted {
  background: var(--accent-soft); color: var(--accent); box-shadow: none;
}
/* Two controls on an app page, and a rule: a tinted pill ACTS ON THE ROW it
   sits in (Save, Approve, Connect, Invite more; Remove and Decline in
   danger); a plain text row IS the action (Sign out, Leave, Disable). The
   gradient .btn is the landing's, and stays there. */
.btn.tinted.danger { background: var(--danger-soft); color: var(--danger); box-shadow: none; }
/* fx swaps the label for a spinner while the request is in flight. */
.btn.small.fx-loading { color: transparent; pointer-events: none; }
.btn.small.fx-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--accent-soft); border-top-color: var(--accent);
  animation: nik-spin 0.7s linear infinite;
}
@keyframes nik-spin { to { transform: rotate(360deg); } }

.linkish {
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent);
}
.linkish:hover { text-decoration: underline; }
.linkish.danger { color: var(--danger); }

.cell.stack .btn { align-self: flex-end; }

/* Status pills. Green means linked/live/delivered, red means this removes
   something, grey means nothing happened. */
.pill {
  font-size: var(--t-nano); font-weight: 600; padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: var(--neutral-soft); color: var(--fg2); white-space: nowrap;
}
.pill.tint { background: var(--accent-soft); color: var(--accent); }
.pill.green { background: var(--ok-soft); color: var(--ok); }
.pill.red { background: var(--danger-soft); color: var(--danger); }
.pill.gray { background: var(--neutral-soft); color: var(--fg2); }
/* A pill after a name ("Ada Lovelace  you", "Google  connected") — never in
   front of the photo, so the column of photos stays a column. */
.cell .grow > .pill, .cell .row .pill { margin-left: 6px; vertical-align: 1px; }

/* Terminal surfaces: a command to copy, and a token shown exactly once. */
.snippet {
  padding: 10px 14px; border-radius: var(--radius-sm);
  background: var(--code-bg); color: var(--code-fg);
  font-family: var(--font-mono); font-size: var(--t-micro); line-height: 1.5;
  user-select: all; -webkit-user-select: all; overflow-x: auto; white-space: nowrap;
}
.snippet::before {
  content: "$ "; color: var(--code-dim);
  user-select: none; -webkit-user-select: none;
}
/* A command to copy. Square, because it is a strip across the card rather
   than a thing floating in it; wraps, so the whole command is on screen and
   nothing scrolls sideways; and a copy button in the corner, because select-
   all-then-copy on a phone is a chore. */
.tokenbox {
  padding: 14px calc(var(--inset) + 64px) 14px var(--inset);
  background: var(--code-bg); color: var(--code-fg);
  font-family: var(--font-mono); font-size: var(--t-fine); line-height: 1.5;
  user-select: all; -webkit-user-select: all;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.codebox { position: relative; }
.codebox .copy {
  position: absolute; top: 10px; right: var(--inset);
  font: inherit; font-size: var(--t-nano); font-weight: 600; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; cursor: pointer;
  padding: 4px 10px; border: none; border-radius: var(--radius-pill);
  background: var(--code-dim); color: var(--code-bg);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.codebox .copy:hover { background: var(--code-fg); }
.codebox .copy.done { background: var(--ok); color: var(--accent-ink); }

/* ---- data display: tiles, charts, tables, segmented control ------------- */
.seg-bar {
  display: flex; gap: 2px; padding: 2px;
  background: var(--accent-soft); border-radius: var(--radius-sm);
}
.seg {
  flex: 1; text-align: center; padding: 7px 4px; border-radius: var(--radius-xs);
  font-size: var(--t-callout); font-weight: 600; color: var(--fg2);
  transition: color var(--fast) var(--ease);
}
.seg:hover { color: var(--fg); }
.seg.on { background: var(--card); color: var(--fg); box-shadow: var(--shadow-raise); }

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--gap); }
.tile { padding: var(--inset); }
/* A strip of counts over a table, rather than the board's own tiles. */
.tiles.strip .tile { padding: 13px 16px; }
.tiles.strip .tile-n { font-size: var(--t-head); }
.tile-n {
  font-size: var(--t-head); font-weight: 700;
  letter-spacing: var(--tracking-head); font-variant-numeric: tabular-nums;
}
/* A count that means act now, not a count that is merely large. Reserved for
   a number whose non-zero-ness is the alarm — families losing messages on
   /admin/niks — because a tile that is red whenever it is busy is a tile an
   operator stops reading. */
.tile-n.danger { color: var(--danger); }
.tile-l {
  margin-top: 4px; font-size: var(--t-nano); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--fg2);
}

.bars { display: flex; align-items: flex-end; gap: 5px; height: 72px; }
.bar {
  flex: 1; background: var(--accent-grad);
  border-radius: 4px 4px 0 0; min-height: 2px;
}

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { border-collapse: collapse; width: 100%; font-size: var(--t-callout); background: var(--card); }
th, td { text-align: left; padding: 10px var(--inset); white-space: nowrap; }
th {
  font-size: var(--t-nano); font-weight: 600; color: var(--fg2);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
thead th { border-bottom: var(--hairline) solid var(--line-strong); }
tr + tr td { border-top: var(--hairline) solid var(--line); }
tbody tr:hover td { background: var(--hover); }
td.mono { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--fg2); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Sealed ciphertext is unreadable on purpose — it is shown as proof, so it
   is styled as texture, not as data. */
td.sealed {
  color: var(--fg3); max-width: 260px; overflow: hidden; text-overflow: ellipsis;
}
.mono-cell { font-family: var(--font-mono); font-size: var(--t-callout); }
.pager { display: flex; gap: 20px; padding: 0 var(--inset); }
/* A chooser: every table on screen at once, rather than one short name per
   line down the edge of a window that is mostly empty. The hairlines meet
   across the columns, so it reads as a grid rather than as loose links. */
.chooser { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.chooser > .cell { border-top: var(--hairline) solid var(--line); }
.chooser > .cell + .cell { border-top: var(--hairline) solid var(--line); }
/* The foot is a strip, and a pager in it keeps the strip's padding. */
.card-foot.pager { padding: 9px var(--inset); }

/* ---- the cockpit: rail, title bar, workspace ----------------------------- */
/* The operator's panel, and the one surface in the product that is not a
   reading column — it fills the window. Eleven destinations never fit a
   segmented control (they scrolled, and the one you wanted was always just
   off the edge), and a table of ninety-six families never fit 980px. So
   navigation stands up as a rail and the content takes the whole width.

   #page IS the cockpit, so an fx navigation re-renders the rail along with
   the content and the lit item cannot go stale. That is the whole reason the
   rail is inside the swapped fragment rather than beside it. */
.cockpit {
  display: grid; grid-template-columns: var(--rail) 1fr;
  height: 100dvh; overflow: hidden;
}
/* The states wall dumped to files has nowhere to navigate to (make states),
   so it runs the cockpit without its rail rather than pretending. */
.cockpit.solo { grid-template-columns: 1fr; }

.rail {
  display: flex; flex-direction: column; min-height: 0;
  padding: calc(16px + env(safe-area-inset-top)) 12px 12px;
  background: var(--bg-nav);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border-right: var(--hairline) solid var(--line);
}
.rail-brand { display: flex; align-items: center; gap: 8px; padding: 2px 8px 16px; }
.rail-brand svg { display: block; }
.rail-brand .word { font-weight: 700; font-size: var(--t-lead); color: var(--fg); }
.rail-brand .tag {
  margin-left: auto; font-size: var(--t-nano); font-weight: 600; color: var(--fg3);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
/* The operator voice again (rule 4): sans caps, not a mono kicker. */
.rail-group {
  padding: 14px 10px 5px;
  font-size: var(--t-nano); font-weight: 600; color: var(--fg2);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
/* The lit item is .seg.on stood on its side — same white card, same raise.
   Nothing new was invented to replace the tab bar. */
.rail-item {
  display: flex; align-items: center; gap: 10px;
  min-height: 34px; padding: 0 10px;
  border-radius: var(--radius-xs);
  font-size: var(--t-callout); color: var(--fg2);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.rail-item svg {
  width: 18px; height: 18px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.rail-item:hover { background: var(--hover); color: var(--fg); }
.rail-item.on {
  background: var(--card); color: var(--fg); font-weight: 600;
  box-shadow: var(--shadow-raise);
}
/* Which build you are reading. Two services disagreeing is the question this
   answers before the deployments page is even open. */
.rail-foot {
  margin-top: auto; padding-top: 12px;
  border-top: var(--hairline) solid var(--line);
  display: flex; align-items: center; gap: 8px; padding-left: 10px;
  font-size: var(--t-nano); color: var(--fg2);
}
.rail-foot .mono { margin-left: auto; font-size: var(--t-kicker); color: var(--fg3); }

.deck { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

/* The title bar. What the page is, on the left; what you can do to it, on the
   right, in the same place on every page. */
.topbar {
  flex: none; display: flex; align-items: center; gap: 14px;
  height: calc(var(--bar) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) var(--pad) 0;
  background: var(--bg-nav);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border-bottom: var(--hairline) solid var(--line);
}
/* h1 carries the column's inset everywhere else; in the bar the bar owns it. */
.topbar h1 { padding: 0; }
.topbar .note {
  padding-left: 14px; border-left: var(--hairline) solid var(--line-strong);
  font-size: var(--t-micro); color: var(--fg2);
}
.topbar .tools { margin-left: auto; display: flex; align-items: center; gap: 10px; }
/* A page that reloads itself says so, and the dot beside it is the same
   breathing green as a nik that is answering — it is the same claim. */
.live { display: flex; align-items: center; gap: 7px; font-size: var(--t-micro); color: var(--fg2); }

/* The workspace stacks like .page does — same gap, same rhythm, same rule
   that a child spans it exactly — so a page that has not been rebuilt for
   the cockpit yet still reads correctly inside it. */
.workspace {
  flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 18px var(--pad) var(--pad);
  display: flex; flex-direction: column; gap: var(--gap);
}
/* A child keeps its own height and the workspace scrolls. Without `flex:
   none` every card is a shrinkable flex item — and because `.card` sets
   `overflow: hidden`, its automatic minimum size is zero, so a page taller
   than the window does not scroll, it crushes: /admin/fleet drew a 250px
   table 67px tall and clipped the rest. Any stacked page long enough to
   overflow hit this; the ones built on a grid (.overview, .split) never did,
   which is exactly why it survived three pages of review. */
.workspace > * { width: 100%; margin: 0; flex: none; }
/* Prose does not get wider just because the window did: 96ch is where a line
   stops being readable, and the cockpit is the only place that can exceed it. */
.workspace > .lead, .workspace > .footnote, .workspace > p { max-width: 96ch; }

/* A sub-page's way back, in the bar where the title would start. */
.back {
  display: inline-flex; align-items: center; gap: 3px;
  height: 30px; padding: 0 12px 0 7px; border-radius: var(--radius-pill);
  font-size: var(--t-callout); font-weight: 600; color: var(--accent);
}
.back:hover { background: var(--accent-soft); }
.back svg {
  width: 17px; height: 17px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}

/* A card that fills the grid cell it was given, and scrolls its own table
   rather than growing the page. The workspace is the only scroller that
   should ever move. */
.card.fills { display: flex; flex-direction: column; min-height: 0; }
.card.fills .card-body { flex: 1; min-height: 0; }
.card.fills .table-scroll { flex: 1; min-height: 0; overflow-y: auto; }
.card.fills .table-scroll thead th { position: sticky; top: 0; z-index: 1; }

/* A card's own padded body, for the one thing that is not a list or a table. */
.card-body { padding: var(--inset); }

/* Ten console links read as two columns of five, not one column of ten. */
.ops {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 6px; display: grid; grid-template-columns: 1fr 1fr;
  align-content: start; gap: 0 6px;
}

/* A card says what it is in its own head, rather than under a title floating
   above it: at full width a .section-title sits a long way from the thing it
   names. Same operator voice, same words, attached. */
.card-head {
  display: flex; align-items: center; gap: 10px;
  padding: 13px var(--inset) 11px;
  border-bottom: var(--hairline) solid var(--line);
}
.card-head h2 {
  font-size: var(--t-micro); font-weight: 600; color: var(--fg2);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
}
.card-head .note { margin-left: auto; font-size: var(--t-nano); color: var(--fg3); }
/* The sentence that belongs to one card — the explanation that used to
   trail it as a .lead and read as if it belonged to the next thing. */
.card-foot {
  padding: 9px var(--inset); border-top: var(--hairline) solid var(--line);
  font-size: var(--t-nano); color: var(--fg2);
}

/* The chart, at cockpit size: a column per day with its count above it. */
.bars.tall { height: 100%; align-items: stretch; gap: 8px; }
.bars.tall .barcol {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.bars.tall .plot { flex: 1; min-height: 0; width: 100%; position: relative; }
.bars.tall .plot .bar { position: absolute; left: 0; right: 0; bottom: 0; }
.bars.tall .d { font-size: var(--t-kicker); color: var(--fg3); font-variant-numeric: tabular-nums; }
.bars.tall .v {
  font-size: var(--t-kicker); font-weight: 600; color: var(--fg2);
  font-variant-numeric: tabular-nums;
}

/* An external console link, at the density a grid of ten wants. */
.opslink {
  display: flex; align-items: center; gap: 8px;
  height: 42px; padding: 0 10px; border-radius: var(--radius-xs);
  font-size: var(--t-callout); color: var(--fg);
}
.opslink:hover { background: var(--hover); }
/* The arrow every external link already wears, moved to the end of the row
   instead of trailing the words. */
.opslink[target="_blank"]::after { margin-left: auto; }

/* The overview is a board rather than a stack: everything an operator opens
   the panel to check, on one screen, at the size the window actually is. The
   rows are stated rather than grown so a card that scrolls its own table has
   a definite height to scroll inside; a window shorter than the board scrolls
   the workspace, which is the only scroller that should ever move. */
.overview {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-template-rows: auto 244px 330px 254px;
}
.s3 { grid-column: span 3; }
.s5 { grid-column: span 5; }
.s7 { grid-column: span 7; }
/* Narrower than a laptop the board stops being a board: the rows grow instead
   of being stated, and the two-up pairs go full width rather than squeezing a
   six-column table into a third of a tablet. The tiles stay four across —
   they are four numbers, and they fit.

   The min-height is on `.card.fills` rather than on every child: `.fills` sets
   `min-height: 0` so its table can scroll inside a stated row, and without the
   same specificity here that zero wins and the chart collapses to nothing. */
@media (max-width: 1200px) {
  .overview { grid-template-rows: none; }
  .overview > .card.fills { min-height: 260px; }
  .s5, .s7 { grid-column: span 12; }
}
@media (max-width: 900px) {
  .s3 { grid-column: span 6; }
}

/* A list beside the one row you are looking at. The table stays a table; the
   inspector is where one family's numbers are gathered, which is the shape
   the question has at 3am — "what is happening to the Rossis", not "what is
   happening". */
.split {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: minmax(0, 1fr) 400px; gap: var(--gap);
}
/* Two tables rather than a table and an inspector: the second one earns more
   than a panel's width. */
.split.wide { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
@media (max-width: 1200px) {
  .split { grid-template-columns: minmax(0, 1fr); }
  .split > * { min-height: 280px; }
}
.insp-head { padding: 16px var(--inset) 14px; border-bottom: var(--hairline) solid var(--line); }
.insp-head .name { font-size: var(--t-lead); font-weight: 700; letter-spacing: var(--tracking-head); }
.insp-head .badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.insp-empty { padding: var(--inset); color: var(--fg2); font-size: var(--t-fine); }
/* Label left, value right — the iOS settings row, at desk density. */
.ir {
  display: flex; align-items: center; gap: var(--gap);
  min-height: 40px; padding: 9px var(--inset); font-size: var(--t-callout);
}
.ir + .ir { border-top: var(--hairline) solid var(--line); }
.ir .k { flex: none; width: 108px; color: var(--fg2); }
.ir .v { flex: 1; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.ir .v.alarm { color: var(--danger); font-weight: 600; }
.ir .v.mono { font-family: var(--font-mono); font-size: var(--t-micro); }

/* The name is the row's link: it takes the row's ink, not a link's, so a
   column of names stays a column of names rather than a column of links. */
a.pick { color: inherit; display: block; }

/* The row an inspector is showing. */
tbody tr.on td { background: var(--accent-soft); }
tbody tr.on td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* Row actions. Eighteen rows of buttons out-shout eighteen names, so they
   wait for the pointer — and for a keyboard, which has no pointer to wait
   with, focus is the same signal. Where there is no hover at all (a finger
   on an iPad) they are simply always there: an action nobody can reveal is
   an action nobody has. */
td.act { width: 1%; text-align: right; }
.acts {
  display: inline-flex; gap: 6px; justify-content: flex-end;
  opacity: 0; transition: opacity var(--fast) var(--ease);
}
tr:hover .acts, .acts:focus-within { opacity: 1; }
@media (hover: none) { .acts { opacity: 1; } }

/* A page you read and copy from is the one place the cockpit does not go edge
   to edge: the rail stays, the content sits in the reading column. */
.reading {
  width: 100%; max-width: var(--col); margin: 0 auto;
  display: flex; flex-direction: column; gap: var(--gap);
}

/* A value shown once, to be selected and copied. .tokenbox already dresses
   the surface; `plain` is the one without a copy button, so it does not hold
   64px open on the right for a control that is not there. */
.tokenbox.plain { padding-right: var(--inset); }
.tokenbox code { font: inherit; }
.tokenbox + .tokenbox { border-top: var(--hairline) solid var(--code-dim); }

/* ---- the action dialog --------------------------------------------------- */
/* An action the panel offers, rather than a page you go to. Native `popover`,
   so there is no script here: the browser puts it in the top layer — which is
   also how it escapes the cockpit's own `overflow: hidden` — closes it on Esc
   or a click outside, and moves focus in and back out again. Every one of
   those would otherwise have been ours to write and ours to get wrong. */
.dialog[popover] {
  width: 560px; max-width: calc(100vw - 2 * var(--pad));
  padding: 0; margin: auto;
  background: var(--card); color: var(--fg);
  border: var(--edge) solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-dialog);
}
.dialog[popover]::backdrop { background: var(--scrim); }
.dialog-head { padding: 16px var(--inset) 13px; border-bottom: var(--hairline) solid var(--line); }
.dialog-head h2 { font-size: var(--t-lead); font-weight: 700; letter-spacing: var(--tracking-head); }
.dialog-head .sub { margin-top: 5px; }
.dialog-foot {
  display: flex; gap: var(--gap); align-items: center; justify-content: flex-end;
  padding: 12px var(--inset);
  border-top: var(--hairline) solid var(--line); background: var(--bg);
}

/* A labelled field: `.cell` with a name in front of it. The unlabelled kind
   already shipped — the dashboard names a family with one — and it is right
   for a form of one field, where the placeholder can say everything. A form
   of seven has to read as seven rows, and each row has to still say what it
   is once it has been filled in, which a placeholder cannot do. */
.field {
  display: flex; align-items: center; gap: var(--gap);
  min-height: 44px; padding: 8px var(--inset); font-size: var(--t-callout);
}
.field + .field, .field + .cell { border-top: var(--hairline) solid var(--line); }
.field > .k { flex: none; width: 158px; color: var(--fg2); }
.field > input, .field > select {
  flex: 1; min-width: 0;
  font: inherit; font-size: var(--t-callout); color: var(--fg);
  background: transparent; border: none; outline: none;
}
.field > input::placeholder { color: var(--fg3); }
.field:focus-within { background: var(--hover); }
/* A value that wraps sits under its own label rather than dragging the row's
   baseline around. */
.field.top { align-items: flex-start; }
.field.top > .k { padding-top: 3px; }
.field .checks { flex: 1; display: flex; flex-wrap: wrap; gap: 9px 16px; }
.check { display: inline-flex; align-items: center; gap: 7px; }
.check input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }

/* Search, in the title bar where a page's controls live. A form rather than a
   box with script behind it: Return submits it, the address carries what was
   asked for, and a filtered list is therefore a link somebody can send. */
.search {
  display: flex; align-items: center; gap: 8px;
  height: 32px; width: 236px; padding: 0 12px;
  border-radius: var(--radius-pill);
  background: var(--card); border: var(--edge) solid var(--line);
}
.search svg {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: var(--fg3); stroke-width: 1.8; stroke-linecap: round;
}
.search input[type="text"] { font-size: var(--t-callout); }
.search:focus-within { border-color: var(--accent); }

/* The bar's actions, in the two groups they fall into: what brings something
   into being, and what points an existing thing at another existing thing. */
.acts-bar { display: flex; align-items: center; gap: 8px; }
.acts-bar .sep {
  width: var(--hairline); align-self: stretch; margin: 4px 4px;
  background: var(--line-strong);
}
/* An action that belongs to one table rather than to the page sits in that
   table's head — the product's own rule about a tinted pill acting on the
   thing it sits in, one surface up from a row. */
.card-head .btn { margin-left: auto; }
.card-head .note + .btn { margin-left: var(--gap); }

/* ---- chat (landing only: the product shot and the demo cards) ----------- */
.chat { display: flex; flex-direction: column; gap: 8px; padding: 18px var(--inset); }
.bubble {
  max-width: 78%; padding: 9px 14px; border-radius: 18px;
  font-size: var(--t-callout); line-height: 1.35; text-align: left;
}
.bubble.them { align-self: flex-start; border-bottom-left-radius: 5px; }
.bubble.nik { align-self: flex-end; border-bottom-right-radius: 5px; }
.bubble .who {
  display: block; font-size: var(--t-nano); font-weight: 600;
  opacity: 0.65; margin-bottom: 2px;
}

/* The product shot: an iPhone running the family chat. Everything inside the
   bezel is WhatsApp's colour, not ours (see the --wa-* tokens). */
.stage { display: flex; justify-content: center; margin-top: 8px; }
.phone {
  width: min(360px, 100%); padding: 11px; border-radius: 48px;
  background: var(--wa-bezel); box-shadow: var(--shadow-device);
}
.phone .screen { border-radius: 38px; overflow: hidden; background: var(--wa-screen); }
.chat-head {
  display: flex; align-items: center; gap: 10px; padding: 14px var(--inset) 10px;
  background: var(--wa-head); border-bottom: var(--hairline) solid var(--wa-line);
}
.chat-head .t strong {
  display: block; font-size: var(--t-callout); line-height: 1.2; color: var(--wa-ink);
}
.chat-head .t span { font-size: var(--t-nano); color: var(--wa-ink2); }
.phone .chat { padding: 14px 12px 20px; }
.phone .bubble.them { background: var(--wa-them); color: var(--wa-ink); }
.phone .bubble.nik { background: var(--wa-mine); color: var(--wa-ink); }
.phone .bubble .who { color: var(--wa-who); }

/* Where the environment carries iMessage, the shot is the same chat on either
   app, and two radio buttons drawn as a segmented control switch between
   them. The one not chosen is hidden, so the switch needs no script. */
.stage.shots { flex-direction: column; align-items: center; gap: 20px; }
.app-shot { display: flex; justify-content: center; width: 100%; }
.page:has(#shot-imessage:checked) :is(.app-shot.whatsapp, .shot-caption.whatsapp),
.page:has(#shot-whatsapp:checked) :is(.app-shot.imessage, .shot-caption.imessage) { display: none; }
.shot-pick {
  display: inline-flex; gap: 2px; padding: 3px;
  border-radius: var(--radius); background: var(--well);
}
.shot-pick input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.shot-pick label {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 18px;
  border-radius: var(--radius-sm); font-size: var(--t-callout); color: var(--fg2); cursor: pointer;
}
.shot-pick label > span { display: flex; }
.shot-pick input:checked + label { background: var(--card); color: var(--fg); box-shadow: var(--shadow-raise); }
.shot-pick input:focus-visible + label { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The iMessage shot: Messages' light thread, the group's picture and name
   centred over it, each sender's name above their bubble. */
.phone.im .screen { background: var(--im-screen); }
.phone.im .chat-head {
  flex-direction: column; gap: 4px; padding: 12px var(--inset) 10px;
  background: var(--im-head); border-bottom-color: var(--im-line);
}
/* The group's picture is 40 pixels, so its circle is too, not the 28 every
   other avatar is. */
.phone.im .chat-head .avatar { display: flex; width: 40px; height: 40px; border-radius: 50%; overflow: hidden; }
.phone.im .chat-head .avatar svg { display: block; width: 40px; height: 40px; }
.phone.im .chat-head .t { text-align: center; }
.phone.im .chat-head .t strong { font-size: var(--t-nano); font-weight: 600; color: var(--im-ink); }
.phone.im .chat-head .t span { font-size: var(--t-eyebrow); color: var(--im-ink2); }
.phone.im .from { align-self: flex-start; max-width: 78%; display: flex; flex-direction: column; gap: 2px; }
.phone.im .from .bubble { max-width: none; }
.phone.im .from .who { padding-left: 12px; font-size: var(--t-eyebrow); color: var(--im-ink2); }
.phone.im .bubble.them { background: var(--im-them); color: var(--im-ink); }
.phone.im .bubble.nik { background: var(--im-mine); color: var(--im-mine-ink); }
.phone.im .vn .play { background: var(--im-play); }
.phone.im .vn .dur { color: var(--im-ink2); }

/* The standalone demo cards match the page: the --chat-* tokens. */
.card.chat { max-width: 480px; margin-left: auto; margin-right: auto; }
.card.chat .bubble.them { background: var(--chat-them); color: var(--fg); }
.card.chat .bubble.nik { background: var(--chat-nik); color: var(--chat-nik-ink); box-shadow: inset 0 0 0 var(--edge) var(--chat-nik-edge); }
.card.chat .bubble .who { color: var(--chat-who); opacity: 0.9; }
.card.chat .bubble.nik .who { color: var(--chat-who-nik); }
/* Scenario captions: the mono kicker, card-sized. */
.card.chat .cap { align-self: flex-start; padding-left: 2px; }

/* Chat props: a voice note and a photo, so the demos show senses. */
.vn { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.vn .play {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  background: var(--wa-play); display: grid; place-items: center; font-size: var(--t-eyebrow);
}
.vn .wave {
  width: 92px; height: 16px; border-radius: 2px; opacity: 0.4;
  background: repeating-linear-gradient(90deg, currentColor 0 2px, transparent 2px 5px);
}
.vn .dur { font-size: var(--t-nano); color: var(--wa-ink2); }
.bubble .snap {
  display: grid; place-items: center; width: 180px; height: 116px;
  margin-bottom: 6px; border-radius: var(--radius); font-size: 42px;
  background: var(--wa-photo);
}

/* ---- the mascot --------------------------------------------------------- */
/* nik's braille blob. Monospace so the dot grid stays true; blank cells are
   U+2800 braille blanks, so every line is exactly 7 cells and never shifts
   between animation states. Monospace advances render the blob wider than the
   original sketch's dot grid, so scaleX squeezes it back to the sketch's
   proportions. */
/* The poke ladder is built out of repeated taps, so the blob must not look
   like text to a touch: no double-tap-to-zoom holding each tap hostage while
   the browser waits to see whether a second one is coming, no selection
   callout or magnifier over the dots, no highlight box around them. */
.mascot {
  margin: 0 auto; width: max-content;
  font-family: var(--font-mono); font-size: 30px; line-height: 1.1;
  color: var(--blob); user-select: none; transform: scaleX(0.8);
  -webkit-user-select: none; -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
/* The mascot living in the margins: static poses between claims. */
.mascot.mini {
  font-size: var(--t-micro); color: var(--blob); opacity: 0.75;
  margin: 0 auto 14px; filter: none; text-shadow: var(--blob-shine);
}

/* ---- the landing -------------------------------------------------------- */
/* Display context: the blob is the face, the words keep their voice down.
   Type comes from the --d-* display scale (tokens.css). */
body:has(.hero.big) { background: var(--night-stars), var(--bg); }
/* The landing keeps its labels quieter than the app's. */
body:has(.hero.big) .section-title { color: var(--fg3); margin-top: 40px; }
body:has(.hero.big) .footnote,
body:has(.hero.big) .footnote a,
body:has(.hero.big) .footer,
body:has(.hero.big) .footer a { color: var(--fg3); }

/* The sign-in handoff keeps its actions and instructions in one surface. */
.page:has(.signin) { max-width: calc(var(--col-signin) + 2 * var(--gutter)); padding-top: 64px; }
.signin-card { padding: 32px; display: flex; flex-direction: column; gap: 24px; }
.signin-heading { text-align: center; }
.signin-heading > svg { display: block; margin: 0 auto 20px; }
.signin-heading h1 { font-size: var(--t-title); margin-bottom: 8px; }
.signin-heading p { font-size: var(--t-callout); color: var(--fg2); }
.signin-whatsapp { display: flex; flex-direction: column; gap: 20px; }
.signin-apps { display: flex; flex-direction: column; gap: 10px; }
.signin-provider { gap: 10px; min-height: 48px; padding: 12px 16px; }
.signin-provider > span { display: flex; flex: none; }
.signin-provider svg { width: 20px; height: 20px; }
.signin-whatsapp .signin-provider svg { fill: currentColor; }
.signin-provider.signin-google { background: var(--card); color: var(--fg); border: var(--edge) solid var(--line-strong); box-shadow: none; }
.signin-provider.signin-google:hover { background: var(--hover); }
.signin-steps { list-style: none; counter-reset: signin-step; display: grid; gap: 14px; }
.signin-steps li { counter-increment: signin-step; display: flex; align-items: flex-start; gap: 12px; font-size: var(--t-fine); }
.signin-steps li > span { flex: none; display: grid; place-items: center; width: 24px; height: 24px; border-radius: var(--radius-pill); background: var(--accent-soft); color: var(--fg); font-size: var(--t-micro); }
.signin-steps li > span::before { content: counter(signin-step); }
.signin-note { font-size: var(--t-micro); color: var(--fg2); text-align: center; }
.signin-help { font-size: var(--t-micro); color: var(--fg2); text-align: center; }
.signin-help summary { display: inline-flex; align-items: center; gap: 8px; min-height: 32px; color: var(--fg); }
.signin-help summary::after { content: ""; width: 6px; height: 6px; border-right: var(--edge) solid currentColor; border-bottom: var(--edge) solid currentColor; transform: rotate(45deg); }
.signin-help[open] summary::after { transform: rotate(225deg); }
.signin-help p { margin-top: 8px; text-wrap: pretty; }
.signin-divider { display: flex; align-items: center; gap: 16px; font-size: var(--t-micro); color: var(--fg2); }
.signin-divider::before, .signin-divider::after { content: ""; height: var(--hairline); background: var(--line-strong); flex: 1; }
.signin-soon { margin-top: 20px; text-align: center; font-size: var(--t-micro); color: var(--fg2); }
/* The sign-in callback: the owner comes back to the card they left from,
   with the mascot in the logo's place. */
.connect .signin-heading .mascot { font-size: 44px; margin-bottom: 20px; filter: drop-shadow(0 0 22px var(--blob-glow)); }
@media (max-width: 480px) {
  .page:has(.signin) { padding-top: 28px; }
  .signin-card { padding: 28px 24px; }
}

.hero { text-align: center; padding: 32px var(--inset) 8px; }
.hero h1 { font-size: var(--t-display); margin: 18px 0 8px; }
.hero .tagline { font-size: var(--t-lead); color: var(--fg2); max-width: 26em; margin: 0 auto; }
.hero .cta { margin-top: 26px; display: flex; gap: var(--gap); justify-content: center; }
/* Where iMessage is carried the landing has a button for each app, each
   wearing its app's mark in the button's ink, and the line under them says
   which devices iMessage needs. On a phone each button takes the row. */
.btn .mark { display: flex; margin-right: 10px; }
.btn .mark svg { width: 18px; height: 18px; fill: currentColor; }
.hero .cta, .fin .cta { flex-wrap: wrap; }
.cta-note { margin-top: 14px; font-size: var(--t-micro); color: var(--fg2); }
.feat .marks { display: flex; justify-content: center; gap: 18px; }
@media (max-width: 480px) {
  .cta:has(.btn + .btn) .btn { flex: 1 1 100%; }
}

/* The landing spans the window where its sky does, the hero and the closing
   band, and keeps the reading column everywhere else. */
.page:has(> .hero.big) {
  max-width: none; padding: 0;
  display: grid; column-gap: 0;
  grid-template-columns: 1fr min(calc(var(--col) - 2 * var(--gutter)), calc(100% - 2 * var(--gutter))) 1fr;
}
.page:has(> .hero.big) > * { grid-column: 2; }
.page:has(> .hero.big) > :is(.hero.big, .fin) { grid-column: 1 / -1; }

/* The Halloween hero is the dashboard's band of sky at display size, drawn
   on the Halloween art: Nik in his bubble on the art's big island, the words
   on the open sky beside him. The band's own rules place the picture, the
   scene and Nik, and mascot.js gives him the dashboard's body and glass,
   which it reads from the still body's picture; these rules swap in the
   Halloween sky, mounds and glass, size the band, set the words in the
   landing's voice, and light his face. The CLI sign-in confirmation keeps
   the plain hero with the face alone. */
.hero.big {
  padding: 58px var(--inset) 48px;
  background: radial-gradient(480px 340px at 50% 190px, var(--glow-hero), transparent 70%);
}
.hero.big .mascot {
  font-size: 64px; color: var(--blob); cursor: pointer;
  filter: drop-shadow(0 0 26px var(--blob-glow)); text-shadow: var(--blob-shine);
}
.hero.big:has(> .ws-sky) { padding: 0; background: none; }
.hero.big .ws-sky { --sky-h: var(--hero-h); background-image: url(/site/halloween-sky.jpg); background-color: var(--hero-ground); }
.hero.big .ws-sky::before { background: var(--hero-veil); }
.hero.big .ws-sky-fill { background-image: url(/site/halloween-sky.jpg); background-color: var(--hero-ground); }
.hero.big .ws-sky-fill::after { background-image: url(/site/halloween-sky.jpg); }
.hero.big .ws-scene::after { background-image: url(/site/halloween-foreground.webp); }
.hero.big .ws-orb .ws-blob::before { background-image: url(/site/halloween-bubble.webp); }
.hero.big .ws-sky-text { z-index: 2; }
.hero.big h1 {
  font-size: var(--d-hero); line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight); margin: 22px 0 10px; color: var(--fg);
}
.hero.big .ws-sky h1 { margin-top: 0; padding: 0; }
.hero.big .tagline { font-size: var(--d-tag); max-width: 30em; color: var(--fg2); }
.hero.big .cta-note { color: var(--fg2); }
.hero.big .ws-orb .ws-mascot {
  color: var(--blob); text-shadow: var(--blob-shine);
  -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
/* On a wide window the band ends above the foot of the picture: the mounds
   drawn in front of Nik are a picture of their own, unlike the moss under
   them, and stop at a straight edge 77.4% down, which shows as a seam at
   this size. The picture and the scene keep their full height and the band
   cuts them just above that edge, fading into the ground. The words stop
   short of Nik's glass, whose left edge is 71.1% across the scene, three
   picture heights wide, that ends at the window's right edge. */
@media (min-width: 761px) {
  .hero.big .ws-sky {
    height: calc(.765 * var(--sky-h)); padding: 28px var(--hero-x); text-align: left;
    background-position: right top; background-size: auto var(--sky-h);
  }
  .hero.big .ws-scene { bottom: auto; height: var(--sky-h); }
  .hero.big .ws-sky-fill { background-position: left top; background-size: auto var(--sky-h); }
  .hero.big .ws-sky-fill::after { background-position: left top; background-size: 100% var(--sky-h); }
  .hero.big .ws-sky::after { z-index: 1; background: linear-gradient(180deg, transparent 80%, var(--bg)); }
  .hero.big .ws-sky-text { max-width: calc(100vw - var(--hero-x) - 3 * (1 - .711) * var(--hero-h) - 24px); }
  .hero.big .ws-sky .tagline { margin: 0; }
  .hero.big .ws-sky .cta { justify-content: flex-start; }
}
/* On a narrow screen the words stand on the Halloween ground above the strip
   of moss the dashboard draws, and the last line stays clear of Nik's
   glass, where the dashboard lets its words reach 40px into the strip. */
@media (max-width: 760px) {
  .hero.big .ws-sky {
    background:
      linear-gradient(180deg, var(--hero-ground) 0, var(--hero-ground) calc(100% - (min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px)), transparent calc(100% - (min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px) + 70px)),
      url(/site/halloween-sky.jpg) right bottom / auto calc(min(100vw - 32px, 640px) * 1 / 3 * 1.5 + 40px) no-repeat, var(--hero-ground);
  }
  .hero.big .ws-sky::before { background: var(--hero-veil-narrow); }
  .hero.big .ws-sky-text { margin-bottom: 48px; }
}

/* The closing invitation stands on the whole of the Halloween sky, veiled
   where its words are. */
.page:has(> .hero.big) > .fin {
  margin-top: 56px; padding: 120px var(--gutter) 132px;
  background: var(--fin-veil), url(/site/halloween-sky.jpg) 22% 58% / cover no-repeat, var(--hero-ground);
}
/* The word that carries the gradient. */
/* The word nik, in a title, wears the gradient — on the landing and on every
   page whose h1 or h2 names him. Standalone (DESIGN §6): it began as a hero
   rule and was needed elsewhere. In body copy the word stays plain. */
.fox {
  background: linear-gradient(96deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* Feature statements: one idea each, mono kicker over the claim. */
.feat { text-align: center; padding: 64px var(--inset) 6px; }
.feat h2 {
  font-size: var(--d-claim); font-weight: 700;
  letter-spacing: var(--tracking-head); line-height: var(--lh-head); color: var(--fg);
}
/* Every paragraph in a claim sits in the same centred measure — the kicker
   included. Only the body type is stated separately, so the .eyebrow component
   keeps its own size and colour without a specificity fight. */
/* Longhands, not the margin shorthand: the shorthand would reset the kicker's
   own bottom margin back to zero. */
.feat :where(p) { margin-top: 12px; margin-right: auto; margin-left: auto; max-width: 34em; }
.feat :where(p):not(.eyebrow) { font-size: var(--t-callout); color: var(--fg2); }

/* The Halloween spooks: braille pictures drawn the way Nik's face is, a
   ghost with his eyes, a jack-o'-lantern, bats, spiders, and Nik in a witch's
   hat. One stands over a claim; more drift down a wide window's margins,
   clear of the column, the hero, and the closing band. They go with the
   edition. */
.spook {
  margin: 0 auto 18px; width: max-content;
  font-family: var(--font-mono); font-size: 14px; line-height: 1.1;
  transform: scaleX(0.8); user-select: none; -webkit-user-select: none;
}
/* Each kind names itself in :where, so it weighs one class, as the
   reduced-motion rule below does, and that rule, coming later, stops it. */
.spook:where(.ghost) { color: var(--ghost); text-shadow: var(--ghost-shine); animation: spook-float 6s ease-in-out infinite; }
.spook:where(.pumpkin) { color: var(--accent-2); text-shadow: var(--blob-shine); animation: spook-flicker 3.2s steps(1) infinite; }
.spook:where(.bat) {
  color: var(--bat);
  animation: spook-flit 9s ease-in-out infinite, spook-flap 0.3s ease-in-out infinite alternate;
}
.spook:where(.spider) { color: var(--bat); transform-origin: 50% 0; animation: spook-swing 3.6s ease-in-out infinite alternate; }
.spook:where(.witch) { font-size: 20px; color: var(--blob); text-shadow: var(--blob-shine); animation: ws-bob 2.4s ease-in-out infinite alternate; }
@keyframes spook-float { 50% { translate: 0 -12px; opacity: 0.6; } }
@keyframes spook-flicker { 40% { opacity: 0.8; } 44% { opacity: 1; } 72% { opacity: 0.65; } 76% { opacity: 1; } }
@keyframes spook-flit { 25% { translate: 18px -12px; } 50% { translate: 34px 6px; } 75% { translate: 10px 14px; } }
@keyframes spook-flap { to { scale: 1 0.6; } }
@keyframes spook-swing { from { rotate: -6deg; } to { rotate: 6deg; } }
.spooks { display: none; }
@media (min-width: 1180px) {
  .page:has(> .spooks) { position: relative; }
  .page:has(> .hero.big) > .spooks { display: block; position: absolute; inset: 0; grid-column: 1 / -1; pointer-events: none; }
  .spooks .spook { position: absolute; margin: 0; font-size: 12px; }
  .spooks > :nth-child(odd) { right: calc(50% + 400px); }
  .spooks > :nth-child(even) { left: calc(50% + 400px); }
  .spooks > :nth-child(1) { top: 10%; }
  .spooks > :nth-child(2) { top: 19%; }
  .spooks > :nth-child(3) { top: 29%; }
  .spooks > :nth-child(4) { top: 39%; animation-delay: -1.2s; }
  .spooks > :nth-child(5) { top: 50%; animation-delay: -4s, -0.1s; }
  .spooks > :nth-child(6) { top: 60%; animation-delay: -3s; }
  .spooks > :nth-child(7) { top: 71%; animation-delay: -1.8s; }
  .spooks > :nth-child(8) { top: 81%; animation-delay: -6s, -0.2s; }
}
@media (prefers-reduced-motion: reduce) { .spook { animation: none; } }

/* Two statement cards: the nightly reflection (the one dark object on the
   page) and the vault. */
.card.dream, .card.vault { text-align: center; padding: 40px 28px; margin-top: 18px; }
.card.dream { background: var(--night); color: var(--night-ink); }
/* The dreaming Nik sleeps on the moon. The mini is squeezed to 0.8 of its
   width, so the disc behind it is drawn 1.25 times as wide as it is tall. */
.card.dream .mascot.mini {
  margin: 36px auto 44px; font-size: var(--t-head);
  color: var(--moon-ink); opacity: 1; text-shadow: none;
}
.card.dream .mascot.mini::before {
  content: ""; position: absolute; left: 50%; top: 50%; z-index: -1;
  width: 165px; height: 132px; translate: -50% -50%; border-radius: 50%;
  background: var(--moon); box-shadow: var(--moon-glow);
}
.card.vault { color: var(--fg); }
.card.dream h2, .card.vault h2 {
  margin-top: 10px; font-size: var(--d-card); letter-spacing: var(--tracking-head);
}
.card.dream p, .card.vault p { margin: 10px auto 0; max-width: 30em; font-size: var(--d-body); }
.card.dream p { color: var(--night-ink2); }
.card.vault p { color: var(--fg2); }
.card.vault .more { margin-top: 14px; }
.card.vault .more a { color: var(--accent); }

/* Bento: the long tail of tricks, two-up. */
.bento { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.bento .b {
  background: var(--card); border: var(--edge) solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card); padding: 20px 18px;
}
.bento strong { display: block; color: var(--fg); font-size: var(--t-callout); }
.bento .b > span:last-child {
  display: block; margin-top: 4px; font-size: var(--t-micro); color: var(--fg2);
}
/* A tile alone in the last row spans it rather than leave half of it empty. */
.bento .b:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* Closing invitation. */
.fin { text-align: center; padding: 56px var(--inset) 16px; }
.fin h2 { font-size: var(--d-fin); letter-spacing: var(--tracking-display); }
.fin p { margin-top: 8px; font-size: var(--t-callout); color: var(--fg2); }
.fin .cta { margin-top: 22px; display: flex; gap: var(--gap); justify-content: center; }

/* ---- prose (privacy) ---------------------------------------------------- */
.prose { padding: 8px var(--inset); }
.prose h2 { font-size: var(--t-lead); margin: 22px 0 6px; }
.prose p { margin: 8px 0; color: var(--fg); }
.prose p strong { color: var(--fg); }

/* ---- the styleguide (/admin/design) ------------------------------------- */
/* The system, rendered by the system: if a swatch looks wrong here, the token
   is wrong everywhere. */
.sg-swatches {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: var(--gap);
}
.sg-swatch {
  border: var(--edge) solid var(--line); border-radius: var(--radius);
  background: var(--card); overflow: hidden;
}
.sg-chip { height: 56px; border-bottom: var(--hairline) solid var(--line); }
.sg-chip.checker {
  /* A checkerboard under the fill, so a translucent token reads as translucent
     instead of as a lighter flat colour. */
  background-image:
    linear-gradient(45deg, var(--line-strong) 25%, transparent 25%, transparent 75%, var(--line-strong) 75%),
    linear-gradient(45deg, var(--line-strong) 25%, transparent 25%, transparent 75%, var(--line-strong) 75%);
  background-size: 14px 14px; background-position: 0 0, 7px 7px;
}
.sg-fill { width: 100%; height: 100%; }
.sg-meta { padding: 8px 10px; }
.sg-note { display: block; font-size: var(--t-kicker); color: var(--fg3); margin-top: 3px; }
.sg-name { display: block; font-family: var(--font-mono); font-size: var(--t-nano); color: var(--fg); }
.sg-val { display: block; font-family: var(--font-mono); font-size: var(--t-kicker); color: var(--fg2); margin-top: 2px; }
.sg-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap); padding: var(--inset);
}
.sg-row + .sg-row { border-top: var(--hairline) solid var(--line); }
.sg-specimen { padding: 10px var(--inset); }
.sg-specimen + .sg-specimen { border-top: var(--hairline) solid var(--line); }
.sg-specimen .sg-name { margin-bottom: 2px; }
.sg-do { color: var(--ok); font-weight: 600; }
.sg-dont { color: var(--danger); font-weight: 600; }

/* ---- responsive ---------------------------------------------------------- */
/* The panel on a narrow screen: the rail lies down across the top and scrolls,
   the way the tab bar it replaced did. Labels only — eleven glyphs in a row is
   a puzzle, and there is not room for both. The workspace becomes the page,
   and scrolls like one. */
@media (max-width: 900px) {
  .cockpit { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .rail {
    flex-direction: row; align-items: center; gap: 4px;
    padding: calc(8px + env(safe-area-inset-top)) var(--gutter) 8px;
    overflow-x: auto; scrollbar-width: none;
    border-right: none; border-bottom: var(--hairline) solid var(--line);
  }
  .rail::-webkit-scrollbar { display: none; }
  .rail-brand { padding: 0 6px 0 0; }
  .rail-brand .tag, .rail-group, .rail-foot { display: none; }
  .rail-item { flex: 0 0 auto; }
  .rail-item svg { display: none; }
  .topbar {
    height: calc(52px + env(safe-area-inset-top));
    padding-left: var(--gutter); padding-right: var(--gutter);
  }
  .topbar h1 { font-size: var(--t-subhead); }
  .topbar .note { display: none; }
  .workspace { padding: 16px var(--gutter) 32px; }
}

/* ---- responsive: the phone --------------------------------------------------------- */
@media (max-width: 480px) {
  /* Five admin tabs do not fit a phone at callout size. Shrink the label, and
     let the bar scroll rather than clip if a sixth one ever lands. */
  .seg-bar { overflow-x: auto; scrollbar-width: none; }
  .seg-bar::-webkit-scrollbar { display: none; }
  .seg { flex: 1 0 auto; font-size: var(--t-micro); padding: 7px 6px; }

  /* A text input sharing a row with its own button has nowhere left to go at
     phone width: it is flex:1 against a button that will not shrink, so the
     placeholder clips mid-word — "Host name (optional" — and stops saying
     what the field is for. Found by looking at /admin/states at 375px, which
     is the whole reason that page exists. Give the input the line and let the
     actions sit under it. */
  .cell:has(> input[type="text"]) { flex-wrap: wrap; }
  .cell:has(> input[type="text"]) > input[type="text"] { flex: 1 0 100%; }

  /* A family member is four things in one row — a pill, a face, a rename
     field and a remove action — and on a phone there is room for about two.
     Unwrapped, the face is the only child that may shrink, so it collapsed
     to a sliver and "Grace Hopper" set itself one letter per line, under the
     avatar, while "remove" was clipped by the card's own rounding.
     Nothing detected it: the text wrapped rather than clipping, and the page
     never scrolled sideways, so both of the mechanical checks were happy. It
     took looking at it.
     Let the row wrap, and give the face enough of the line that it stays a
     name rather than a column of letters. */
  .tiers { grid-template-columns: 1fr; grid-template-rows: none; }
  .tier { display: flex; flex-direction: column; grid-row: auto; }
  .tier-facts { display: flex; flex-direction: column; grid-row: auto; }

  .cell { flex-wrap: wrap; }
  .cell > .grow { flex: 1 1 60%; }
  .cell > form { flex: 0 1 auto; }

  /* A heading that is one long word, German's Nutzungsbedingungen on the
     terms page, is wider than a 320px phone's hero; it breaks at a syllable
     in the page's language rather than push the page sideways (#2667). */
  .hero h1 { font-size: var(--t-title); hyphens: auto; overflow-wrap: break-word; }
  .hero .tagline { font-size: var(--t-body); }
  .hero.big { padding: 26px var(--inset) 22px; }
  .hero.big .mascot { font-size: 52px; }
  .bento { grid-template-columns: 1fr; }
  /* The public navigation holds three links, and the longest languages set
     them wider than a 320px phone leaves beside the brand: in French the
     signed-out links total about 260px against 218px, so the last one ran
     past the edge and the page scrolled sideways (#2667). Let the links wrap
     onto a second row, right-aligned as they are on one, and let the bar
     grow to hold it, since a bar of fixed height would be run over by the
     second row. A single row keeps the 52px bar and its centring exactly. */
  .nav {
    height: auto;
    min-height: calc(52px + env(safe-area-inset-top));
    padding-top: calc(env(safe-area-inset-top) + 8px);
    padding-bottom: 8px;
  }
  .nav .links { flex-wrap: wrap; justify-content: flex-end; gap: 4px 14px; font-size: var(--t-callout); }
}

/* ── The reference ─────────────────────────────────────────────────────────
   /admin/docs. A specification read on a screen wants three things a normal
   page does not: a measure narrow enough to read prose in, a way to jump, and
   tables and code that scroll on their own rather than pushing the page
   sideways. */

.doc-wrap { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 28px; align-items: start; }

.doc-side { position: sticky; top: 16px; max-height: calc(100vh - 32px); overflow-y: auto; padding-right: 6px; }
.doc-sibs { display: flex; flex-direction: column; gap: 2px; margin-bottom: 14px; }
.doc-sib { display: block; padding: 5px 9px; border-radius: 7px; color: var(--fg2);
  text-decoration: none; font-size: var(--t-micro); }
.doc-sib:hover { background: var(--hover); color: var(--fg); }
.doc-sib.on { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }

.doc-toc { display: flex; flex-direction: column; gap: 1px; border-top: var(--hairline) solid var(--line); padding-top: 12px; }
.doc-toc a { color: var(--fg3); text-decoration: none; font-size: var(--t-nano);
  padding: 3px 9px; border-radius: 6px; line-height: 1.35; }
.doc-toc a:hover { background: var(--hover); color: var(--fg); }
/* The two levels the reference has. .toc-2 is the base indent restated rather
   than left implicit: a class in the markup with no rule anywhere reads as a
   bug to everyone who meets it, including the test that checks for exactly
   that — and the pair says "two levels" in a way one rule does not. */
.doc-toc a.toc-2 { padding-left: 9px; }
.doc-toc a.toc-3 { padding-left: 20px; }

/* The prose. Headings get their anchor on hover; scroll-margin so a jump does
   not put the heading under the top of the window. */
.doc { min-width: 0; max-width: var(--col-wide); font-size: var(--t-body); line-height: var(--lh-body); }
.doc h2, .doc h3, .doc h4 { scroll-margin-top: 18px; position: relative; }
.doc h2 { font-size: var(--t-head); margin: 1.9em 0 .6em; padding-top: .7em;
  border-top: var(--hairline) solid var(--line); letter-spacing: var(--tracking-head); }
.doc h2:first-child { border-top: 0; padding-top: 0; margin-top: 0; }
.doc h3 { font-size: var(--t-subhead); margin: 1.5em 0 .5em; }
.doc h4 { font-size: var(--t-callout); margin: 1.2em 0 .4em; color: var(--fg2); }
.doc p, .doc li { margin: .6em 0; }
.doc a { color: var(--accent); }
.doc code { background: var(--code-bg); color: var(--code-fg); padding: .1em .36em;
  border-radius: 5px; font-family: var(--font-mono); font-size: .86em; }
.doc pre { background: var(--code-bg); border: var(--hairline) solid var(--line);
  border-radius: 10px; padding: 13px 15px; overflow-x: auto; }
.doc pre code { background: none; padding: 0; font-size: var(--t-micro); line-height: 1.55; }
.doc blockquote { margin: 1em 0; padding: .1em 1em; border-left: 3px solid var(--accent);
  color: var(--fg2); }
.doc hr { border: 0; border-top: var(--hairline) solid var(--line); margin: 2em 0; }

/* Tables scroll inside themselves. A spec table is often wider than a column
   of prose and the page must not move sideways because of it. */
.doc table { display: block; overflow-x: auto; width: 100%; border-collapse: collapse;
  font-size: var(--t-micro); margin: 1em 0; }
.doc th, .doc td { border: var(--hairline) solid var(--line); padding: 7px 11px;
  text-align: left; vertical-align: top; }
.doc th { background: var(--code-bg); font-weight: 600; white-space: nowrap; }

/* ── Diagrams ──────────────────────────────────────────────────────────────
   Hand-drawn SVG that takes every colour from the design tokens, so a drawing
   is part of the palette rather than a picture pasted on top of it — and so
   the day this estate grows a dark mode, they come with it. Nothing inside a
   diagram carries a literal colour; a test refuses one. */
.diagram { margin: 1.4em 0; padding: 0; overflow-x: auto; }
.dg { width: 100%; height: auto; display: block; font-family: var(--font); }
/* Strokes take a text colour, not a hairline colour. --line is 8% ink: right
   for a rule between two rows of a table, invisible for a line that has to
   carry meaning across a drawing. That was the first version's bug — the
   diagrams rendered at full size and could not be seen at all. */
.dg-frame { fill: none; stroke: var(--line-strong); stroke-width: 1; }
.dg-box { stroke: var(--fg3); stroke-width: 1.1; fill: var(--card); }
.dg-identity { fill: var(--neutral-soft); }
.dg-spec { fill: var(--accent-soft); }
.dg-status { fill: var(--ok-soft); }
.dg-op { fill: var(--hover); }
.dg-done { fill: var(--ok-soft); }
.dg-fail { fill: var(--danger-soft); }
.dg-expire { fill: var(--neutral-soft); }
.dg-account { fill: var(--card); stroke-dasharray: 3 3; }
.dg-title { fill: var(--fg); font-size: 14px; font-weight: 650; }
.dg-label { fill: var(--fg); font-size: 12px; font-weight: 600; }
.dg-mono { fill: var(--fg2); font-size: 10.5px; font-family: var(--font-mono); }
.dg-note { fill: var(--fg3); font-size: 10px; }
.dg-caption { fill: var(--fg2); font-size: 10.5px; font-style: italic; }
.dg-line { stroke: var(--fg3); stroke-width: 1.4; fill: none; }
.dg-dash { stroke-dasharray: 4 3; }
.dg-tip { fill: var(--fg3); stroke: none; }

.doc-card-title { font-weight: 600; }
.doc-card-sum { color: var(--fg3); font-size: var(--t-micro); margin-top: 2px; }
.diagram-missing { color: var(--danger); font-family: var(--font-mono); font-size: var(--t-micro); }

@media (max-width: 860px) {
  .doc-wrap { grid-template-columns: 1fr; }
  .doc-side { position: static; max-height: none; }
  .doc-toc { display: none; }
}

/* ---- after the restoration ------------------------------------------------
   Everything above this line is the stylesheet as it stood at
   154540527181780d0a204b2f447c0181baac8f73, restored byte for byte. Below it
   are rules for public pages the retired site did not have, which is why no
   retired rule covers them and why they are not part of what "restore the
   site exactly" means. A rule that belongs to a page a visitor saw before
   retirement belongs above, not here.
   -------------------------------------------------------------------------- */

/* The two pages of the fleetctl sign-in: the Allow page, whose one choice
   sits in the hero's .cta, and the page a browser lands on when fleetctl
   has its session, which has nothing to click. One column, centred: each
   page says what the operator does next and gets out of the way. */
.signed-in {
  min-height: 100dvh; display: flex; flex-direction: column;
  justify-content: center; width: 100%; max-width: var(--col);
  margin: 0 auto; padding: 24px var(--gutter);
}
/* The CLI's name stays inline text, so `fleetctl` shares the sentence's
   baseline. An inline-flex wrapper took its baseline from the icon, which has
   none, and lifted the word 3px above the words around it. The icon is sized
   to the line and pulled below the baseline by the amount a descender takes,
   which is where a glyph-sized mark reads as part of the text. */
.signed-in .cli-name { color: var(--fg); white-space: nowrap; }
.signed-in h1 { overflow-wrap: anywhere; }
.signed-in code { font-family: var(--font-mono); font-size: .92em; font-weight: 500; }
.signed-in .terminal-logo {
  display: inline-block; height: 1em; width: auto;
  vertical-align: -.14em; margin-right: .3em;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.signed-in .terminal-cursor { animation: terminal-cursor-blink 1s step-end infinite; }
@keyframes terminal-cursor-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
/* Three beats under the mascot: the headline, the lead a little more open
   than the homepage's, then the instruction after the pause the homepage
   gives its call to action, set small and in the tertiary ink so it reads
   as the footnote it is. Equal gaps made the three lines one clump. */
/* Names the hero so it outranks `.hero.big h1`, which sets the margin. */
.signed-in .hero.big h1 { margin-bottom: 14px; }
.signed-in .tagline { line-height: 1.55; }
.signed-in .cli-next {
  margin: 30px auto 0; max-width: 30em;
  font-size: var(--t-callout); color: var(--fg3);
}
@media (prefers-reduced-motion: reduce) {
  .signed-in .terminal-cursor { animation: none; }
}

/* The tile a list's row opens with: the thing's drawing, and how it ended
   at the corner, a check, a cross or a spinner. */
.ws-row-mark { position: relative; flex: none; display: grid; place-items: center; width: 30px; height: 30px; border-radius: 9px; background: var(--wash); color: var(--mark); }
.ws-row-mark > .ws-glyph { width: 15px; height: 15px; }
.ws-row-mark.bad { background: var(--danger-soft); color: var(--danger); }
.ws-row-badge { position: absolute; right: -4px; bottom: -4px; display: grid; place-items: center; width: 15px; height: 15px; border-radius: 50%; background: var(--card); box-shadow: 0 0 0 2px var(--card); color: #fff; }
.ws-row-mark.ok .ws-row-badge { background: var(--ok); }
.ws-row-mark.bad .ws-row-badge { background: var(--danger); }
.ws-row-badge .ws-glyph { width: 9px; height: 9px; stroke-width: 3.2; }
.ws-row-mark.run .ws-row-badge { color: var(--accent); }
.ws-row-badge .ws-spin { width: 13px; height: 13px; }
/* A conversation's service, in its corner, in the service's colour. */
.ws-row-badge.whatsapp { background: var(--service-whatsapp); }
.ws-row-badge.imessage { background: var(--service-imessage); }
.ws-row-badge.voice { background: var(--service-voice); }
/* A brand mark carries no fill attribute, so the workspace's rule for an
   unfilled 24-grid drawing (18px) matches it too; this one outranks it by
   naming the workspace and the svg, so the mark sits inside its badge. */
.ws .ws-row-badge > svg.brand { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.ws .ws-copy svg { width: 14px; height: 14px; }
/* A switch at the end of the band's tabs, such as Errors only: a box that
   fills when it is on. */
.ws-head-switch { display: inline-flex; flex: none; align-items: center; gap: 8px; min-height: 40px; margin-left: auto; font-size: 14px; color: rgba(255, 255, 255, .82); text-decoration: none; white-space: nowrap; }
.ws-head-switch:hover, .ws-head-switch.on { color: var(--night-ink); }
.ws-head-box { display: grid; place-items: center; width: 16px; height: 16px; border-radius: 4px; box-shadow: inset 0 0 0 1.5px currentColor; }
.ws-head-switch.on .ws-head-box { background: var(--night-ink); color: var(--accent); box-shadow: none; }
.ws-head-box .ws-glyph { width: 12px; height: 12px; stroke-width: 3; }
/* The alarms calendar: a month of days from Monday, split by hairlines, each
   naming its first two alarms by time, with the chosen day's alarms beside
   it. A firing is drawn muted, a next time in the accent, an overdue one in
   the danger colour. On a phone a day shows a count instead. */
.ws-cal { display: flex; flex-wrap: wrap; border-block: var(--hairline) solid var(--line-strong); }
.ws-cal-month { flex: 999 1 560px; min-width: 0; }
.ws-cal-head { display: flex; align-items: center; gap: 10px; padding: 12px var(--ws-x); border-bottom: var(--hairline) solid var(--line-strong); }
.ws-cal-head h2 { margin-right: auto; font-size: 19px; font-weight: 700; letter-spacing: -0.015em; }
.ws-cal-head .ws-btn { min-width: 36px; min-height: 36px; padding: 0 12px; border-radius: 10px; background: var(--card); color: var(--fg); box-shadow: inset 0 0 0 var(--hairline) var(--line-strong); font-size: 14px; font-weight: 400; }
.ws-cal-head .ws-btn:hover { background: var(--hover); }
.ws-cal-dow, .ws-cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.ws-cal-dow { border-bottom: var(--hairline) solid var(--line-strong); }
.ws-cal-dow span { padding: 6px 8px; font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg3); }
.ws-cal-day { display: flex; flex-direction: column; gap: 3px; min-width: 0; min-height: 96px; padding: 7px 8px; border-right: var(--hairline) solid var(--line); border-bottom: var(--hairline) solid var(--line); color: inherit; text-decoration: none; }
.ws-cal-day:nth-child(7n) { border-right: 0; }
.ws-cal-day:hover { background: var(--hover); }
.ws-cal-day.out { opacity: .4; }
.ws-cal-day.on { background: var(--accent-soft); box-shadow: inset 0 -2px 0 var(--accent); }
.ws-cal-num { align-self: flex-start; min-width: 24px; height: 24px; padding: 0 5px; display: grid; place-items: center; border-radius: 999px; font-size: 13px; font-weight: 600; }
.ws-cal-day.today .ws-cal-num { background: var(--accent-grad); color: #fff; }
.ws-cal-ev { display: block; padding-left: 7px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 11.5px; line-height: 1.5; box-shadow: inset 2px 0 0 var(--accent); }
.ws-cal-ev.fired { box-shadow: inset 2px 0 0 var(--line-strong); color: var(--fg2); }
.ws-cal-ev.overdue { box-shadow: inset 2px 0 0 var(--danger); color: var(--danger); }
.ws-cal-time { font-family: var(--font-mono); font-weight: 600; }
.ws-cal-more { font-size: 11px; color: var(--fg2); padding-left: 7px; }
.ws-cal-dots { display: none; }
.ws-cal-aside { flex: 1 1 300px; min-width: 0; border-left: var(--hairline) solid var(--line-strong); }
.ws-cal + .ws-note, .ws-cal-month > .ws-note { padding: 10px var(--ws-x); }
@media (max-width: 760px) {
  .ws-cal-day { min-height: 52px; padding: 5px; align-items: center; }
  .ws-cal-ev, .ws-cal-more { display: none; }
  .ws-cal-dots { display: block; font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; color: var(--accent); }
  .ws-cal-aside { border-left: 0; border-top: var(--hairline) solid var(--line-strong); }
}
/* Text kept exactly as written, on the code's dark block. */
.ws-code-block {
  margin: 0; padding: 12px 14px; max-height: 360px; overflow: auto; border-radius: 12px;
  background: var(--code-bg); color: var(--code-fg); font-family: var(--font-mono); font-size: 12.5px; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.ws-note { margin: 8px 0 0; font-size: 12.5px; color: var(--fg2); }
.ws-subline { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 3px 0 0; font-size: 12.5px; color: var(--fg2); }
.ws-subline a { color: inherit; }
.ws-subline code { font-family: var(--font-mono); font-size: 12px; overflow-wrap: anywhere; }
/* The family's name, as People's aside reads it. */
/* Columns, as the D design lays a page out: the page's body beside an
   aside of its facts, split by a hairline, the aside under the body on a
   phone. Directly under the band they span the surface edge to edge. */
.ws-cols { display: flex; flex-wrap: wrap; align-items: stretch; }
.ws-cols-main { flex: 999 1 480px; min-width: 0; display: flex; flex-direction: column; gap: 28px; padding: 22px var(--ws-x) 40px; }
.ws-cols-aside { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 22px; padding: 22px 24px 40px; border-left: var(--hairline) solid var(--line-strong); }
.ws-main > .ws-cols { background: var(--card); }
@media (max-width: 760px) {
  .ws-cols-aside { padding: 22px var(--ws-x) 40px; border-left: 0; border-top: var(--hairline) solid var(--line-strong); }
}
/* A task, as the D design draws it: its goal and where it stands in the
   band, then a sheet edge to edge with its worker's figures, its tabs, and
   the tab. The polled body stands in the column's own flow. */
/* One of several dated entries, a rule above each but the first. */
.ws-entry { padding: 12px 0; }
.ws-entry + .ws-entry { border-top: var(--hairline) solid var(--line); }
/* The plan, as the D design marks it: a check for a step done, a half moon
   for the one in progress, and a circle for one to do. */
.ws-checklist { margin: 0; padding: 0; list-style: none; }
.ws-checklist > li { padding: 10px 0; border-top: var(--hairline) solid var(--line); overflow-wrap: anywhere; }
.ws-checklist > .d1 { padding-left: 28px; }
.ws-checklist > .d2 { padding-left: 56px; }
.ws-checklist > .d3 { padding-left: 84px; }
.ws-check-line { font-size: 14px; color: var(--fg2); }
.ws-check { display: flex; align-items: baseline; gap: 6px; font-size: 15px; }
.ws-check > span:last-child { flex: 1 1 auto; min-width: 0; }
.ws-check.done { color: var(--fg2); }
.ws-check.active { font-weight: 600; }
/* Tabs: a page's views, along its band's foot. White words, the selected
   one bold over a white rule; a glyph before a tab, and a count or a note
   after it. A switch, such as Errors only, sits at the row's end. */
.ws-tabs { display: flex; align-items: stretch; gap: 24px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.ws-tab { display: inline-flex; flex: none; align-items: center; gap: 7px; min-height: 44px; font-size: 15px; color: var(--night-ink2); text-decoration: none; white-space: nowrap; }
.ws-tab:hover { color: var(--night-ink); }
.ws-tab.on { color: var(--night-ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--night-ink); }
.ws .ws-tab svg[viewBox="0 0 24 24"] { width: 16px; height: 16px; }
.ws-tab-count { font-family: var(--font-mono); font-size: 11px; font-weight: 400; color: var(--night-ink3); }
.ws-tab.on .ws-tab-count { color: var(--night-ink2); }
.ws-tab-note { font-family: var(--font-mono); font-size: 11px; font-weight: 400; color: var(--night-warm); }
.ws-tabs-end { display: inline-flex; flex: none; align-items: center; margin-left: auto; padding-left: 12px; }
@media (max-width: 760px) { .ws-tabs { gap: 18px; } }
@media (max-width: 760px) {
  .ws-tabs { gap: 20px; }
}
/* The contacts page (#2638): a card field holding several values, one to a
   line. */

/* A stack: a page's parts one under another across its surface, each
   between hairlines; columns in it hold their parts the same way. */
.ws-stack { border-top: var(--hairline) solid var(--line-strong); }
.ws-stack > .ws-part { gap: 14px; padding: 18px var(--ws-x) 16px; border-bottom: var(--hairline) solid var(--line-strong); }
.ws-stack > .ws-cols { border-bottom: var(--hairline) solid var(--line-strong); }
.ws-stack > .ws-cols > :is(.ws-cols-main, .ws-cols-aside) { padding-block: 18px 16px; }
.ws-stack > .ws-cols .ws-part { gap: 14px; }
/* A bar chart: a column a day across a box stretched to the width, the
   busiest day the tallest, each column's title naming the day on hover;
   scaled, a scale at its left and dashed lines at a half and the whole. */
.ws-bars-plot { display: flex; gap: 8px; }
.ws-bars-scale { display: flex; flex-direction: column; justify-content: space-between; padding: 4px 0 0; min-width: 2ch; text-align: right; font-family: var(--font-mono); font-size: 10.5px; color: var(--fg3); }
.ws-bars { display: block; flex: 1; min-width: 0; width: 100%; height: 170px; border-bottom: var(--hairline) solid var(--line-strong); overflow: visible; }
.ws-bars-frame.small { margin-bottom: 14px; }
.ws-bars-frame.small .ws-bars { height: 64px; }
.ws-bars-grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; vector-effect: non-scaling-stroke; }
.ws-bar { fill: var(--accent); }
.ws-bar.own { fill: var(--accent-2); }
.ws-bar.bad { fill: var(--danger); }
.ws-bar-day { fill: transparent; }
.ws-bar-day:hover { fill: var(--hover); }
.ws-bars-times { display: flex; justify-content: space-between; padding: 6px 0 0; font-family: var(--font-mono); font-size: 11px; color: var(--fg3); }
.ws-bars-times.scaled { padding-left: calc(2ch + 8px); }
.ws-bar-keys { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg2); }
.ws-bar-key { width: 9px; height: 9px; border-radius: 2px; background: var(--accent); }
.ws-bar-key + .ws-bar-key { margin-left: 8px; }
.ws-bar-key.own { background: var(--accent-2); }
.ws-bar-key.bad { background: var(--danger); }
/* Tallies: one pill per kind across the page, each with its mark and
   count, linking the page that lists them. */
.ws-tallies { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px var(--ws-x); border-bottom: var(--hairline) solid var(--line-strong); }
.ws-tally { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px 6px 6px; border: var(--hairline) solid var(--line-strong); border-radius: 999px; font-size: 13px; color: var(--fg2); text-decoration: none; transition: background .12s, border-color .12s; }
.ws-tally:hover { background: var(--hover); border-color: var(--fg3); color: var(--fg); }
.ws-tally:active { transform: translateY(1px); }
.ws-tally-mark { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); }
.ws-tally-mark .ws-glyph { width: 14px; height: 14px; }
.ws-tally-n { font-family: var(--font-mono); font-weight: 600; color: var(--fg); }
@media (max-width: 760px) {
  .ws-tallies { padding: 12px 16px; }
}
/* Meters: names ranked by what each counted, a bar each as long as its
   share of the most, its count and what went wrong beside it. */
.ws-meters { margin: 0; padding: 0; list-style: none; }
.ws-meter { display: flex; align-items: center; gap: 12px; min-height: 34px; border-bottom: var(--hairline) solid var(--line); }
.ws-meter-name { flex: 0 0 11em; min-width: 0; font-family: var(--font-mono); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-meter-bar { flex: 1; min-width: 40px; height: 6px; border-radius: 3px; background: var(--accent-soft); overflow: hidden; }
.ws-meter-bar rect { fill: var(--accent); }
.ws-meter-n { width: 44px; text-align: right; font-family: var(--font-mono); font-size: 12px; }
.ws-meter-bad { width: 36px; text-align: right; font-family: var(--font-mono); font-size: 11px; color: var(--danger); }
@media (max-width: 760px) {
  .ws-meter-name { flex-basis: 8.5em; }
}

/* Files, as the D design's Journal and dreams draws them: the folders down
   the left, the open folder's files beside them, and the open file across
   the rest under a bar with its path, its versions and its changes. A
   narrow screen stacks them, and draws an opened file alone. */
.ws-files { display: grid; grid-template-columns: minmax(190px, 230px) minmax(190px, 250px) minmax(0, 1fr); min-height: calc(100dvh - var(--ws-head-h, 161px)); border-bottom: var(--hairline) solid var(--line-strong); }
/* The folders and the files each scroll on their own under the band, and
   the open file scrolls with the page. */
.ws-files-folders, .ws-files-list { min-width: 0; border-right: var(--hairline) solid var(--line-strong); }
.ws-files-list > .ws-note { margin: 0; padding: 10px 18px; }
@media (min-width: 761px) {
  .ws-files-folders, .ws-files-list { position: sticky; top: var(--ws-head-h, 0); align-self: start; height: calc(100dvh - var(--ws-head-h, 0px)); overflow-y: auto; overscroll-behavior: contain; }
}
.ws-files-doc { --pane-x: 32px; min-width: 0; display: flex; flex-direction: column; }
.ws-files-doc > .ws-pane-bar { position: sticky; top: var(--ws-head-h, 0); z-index: 2; }
.ws-files-doc > .ws-notice { margin: 20px 32px; }
.ws-files-path { font-family: var(--font-mono); font-size: 12px; color: var(--fg2); overflow-wrap: anywhere; }
.ws-files-steps { display: flex; align-items: center; gap: 14px; }
.ws-files-step { text-decoration: none; }
.ws-files-step.off { color: var(--fg3); }
.ws-files-place { font-family: var(--font-mono); font-size: 12px; color: var(--fg2); }
.ws-files-changes { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px; border-radius: 999px; box-shadow: inset 0 0 0 var(--hairline) var(--line-strong); color: var(--fg); font-size: 13px; text-decoration: none; }
.ws-files-changes:hover { background: var(--hover); color: var(--fg); }
.ws-files-changes.on { background: var(--fg); color: var(--card); box-shadow: none; }
.ws-diff-plus { color: var(--ok); }
.ws-diff-minus { color: var(--danger); }
.ws-files-changes.on .ws-diff-plus { color: #8fe3a8; }
.ws-files-changes.on .ws-diff-minus { color: #ffa3b1; }
/* The open file takes the pane's width. Its prose keeps the measure the
   eye can follow, and text kept as written, such as the sandbox's
   Dockerfile, runs the width and the length of the pane, scrolling with
   the page rather than in a box of its own. */
.ws-files-read { padding: 26px 32px 40px; }
.ws-files-read > .ws-detail-head { margin-bottom: 18px; }
.ws-files-read > .ws-md { font-size: 16px; line-height: 1.7; }
.ws-files-read > .ws-code-block { max-height: none; }
.ws-files-code-top { display: flex; align-items: center; gap: 12px; }
.ws-files-since { margin: 0 0 10px; font-size: 13px; color: var(--fg2); }
.ws-files-diff { border-left: 2px solid var(--line-strong); font-family: var(--font-mono); font-size: 13px; line-height: 1.7; }
.ws-files-diff > span { display: block; padding: 0 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--fg2); }
.ws-files-diff > .ws-diff-add { background: rgba(52, 199, 89, .08); color: #1d6e33; }
.ws-files-diff > .ws-diff-del { background: rgba(217, 45, 74, .07); color: #b0223b; text-decoration: line-through; }
.ws-files-diff > .ws-diff-hunk { color: var(--fg3); }
@media (max-width: 900px) {
  .ws-files { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .ws-files-list { border-right: 0; }
  .ws-files-folders { display: flex; overflow-x: auto; border-right: 0; border-bottom: var(--hairline) solid var(--line-strong); }
  .ws-files-folders .ws-pick { flex: none; height: 48px; padding: 0 14px; border-bottom: 0; }
  .ws-files-folders .ws-pick.on { box-shadow: inset 0 -3px 0 var(--accent); }
  .ws-files-folders .ws-pick-sub { display: none; }
  .ws-files.ws-files-open .ws-files-folders, .ws-files.ws-files-open .ws-files-list { display: none; }
  .ws-files:not(.ws-files-open) .ws-files-doc { display: none; }
  .ws-files-doc { --pane-x: 16px; }
  .ws-files-read { padding-left: 16px; padding-right: 16px; }
  .ws-files-doc .ws-split-back { display: inline-flex; }
}

/* The Nik's Markdown, drawn as reading prose: a file, a skill's SKILL.md,
   a task's report. Its front matter sits above it as the text's facts, and
   a block that is not one map as written. */
.ws-md { max-width: 76ch; font-size: 14.5px; line-height: 1.6; overflow-wrap: anywhere; }
.ws-md > * + * { margin-top: .7em; }
.ws-md h1, .ws-md h2, .ws-md h3, .ws-md h4 { margin-top: 1.2em; line-height: 1.25; letter-spacing: -0.01em; }
.ws-md > :first-child { margin-top: 0; }
.ws-md h1 { font-size: 22px; }
.ws-md h2 { font-size: 18px; }
.ws-md h3, .ws-md h4 { font-size: 15px; }
.ws-md ul, .ws-md ol { padding-left: 1.4em; }
.ws-md li + li { margin-top: .25em; }
.ws-md li > input[type="checkbox"] { margin-right: .4em; }
.ws-md a { color: var(--accent); }
.ws-md-image { text-decoration: underline dotted; }
.ws-md code { padding: 1px 5px; border-radius: 5px; background: var(--neutral-soft); color: var(--fg); font-family: var(--font-mono); font-size: .88em; }
.ws-md pre { padding: 12px 14px; border-radius: var(--radius-sm); background: var(--code-bg); color: var(--code-fg); overflow-x: auto; }
.ws-md pre code { padding: 0; background: none; color: inherit; font-size: 12.5px; }
.ws-md blockquote { padding-left: 12px; border-left: 3px solid var(--line-strong); color: var(--fg2); }
.ws-md hr { border: 0; border-top: var(--hairline) solid var(--line-strong); }
.ws-md table { border-collapse: collapse; font-size: 13.5px; display: block; overflow-x: auto; }
.ws-md th, .ws-md td { padding: 6px 12px; border-bottom: var(--hairline) solid var(--line-strong); text-align: left; }
.ws-md .ws-md-meta { margin: 0 0 1em; padding: 6px 14px; border-radius: 0; background: none; border-left: 3px solid var(--accent-soft); font-family: var(--font-mono); font-size: 12px; color: var(--fg2); white-space: pre-wrap; }
.ws-md .ws-md-facts { margin: 0 0 1.2em; padding: 10px 14px; border-left: 3px solid var(--accent-soft); background: var(--neutral-soft); font-size: 13px; line-height: 1.5; }
.ws-md .ws-md-facts .ws-json-text { max-height: none; padding: 0; border-radius: 0; background: none; color: inherit; font: inherit; }
.ws-md .ws-md-facts .ws-json-arr { padding-left: 1.6em; }
.ws-md .ws-md-facts li + li { margin-top: 0; }
.ws-fold > .ws-file-text { padding: 12px 0; border-top: var(--hairline) solid var(--line); }
.ws-section > .ws-md { padding: 4px 0 8px; }
.ws-section > .ws-note { margin: 0; padding: 10px var(--ws-x) 0; }
.ws-section > .ws-private { display: flex; margin: 0; padding: 14px var(--ws-x); font-size: 13px; color: var(--fg3); }
.ws-section > .ws-private > .ws-glyph { padding: 0; background: none; }

/* The files page (#2643): a file's text drawn as Markdown, the text as the
   Nik wrote it under a fold, and the diff from the version before, its
   added and removed lines marked. */
.ws-file-text {
  margin: 0; padding: 16px var(--ws-x); font-family: var(--font-mono); font-size: 13px; line-height: 1.6;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.ws-diff-add { color: var(--ok); }
.ws-diff-del { color: var(--danger); }
.ws-diff-hunk { color: var(--code-dim); }

/* The media page (#2644), as the D design draws it: the files as tiles in a
   grid, each a thumbnail over its caption, beside one file's card with the
   file itself held to the column and the screen. */
.ws-split.gallery { grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); }
.ws-split.gallery > .ws-split-list { position: static; align-self: stretch; max-height: none; overflow: visible; }
.ws-split.gallery > .ws-split-detail { position: sticky; top: 0; gap: 16px; padding: 22px 24px 32px; }
.ws-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
.ws-tile { display: flex; flex-direction: column; gap: 8px; min-width: 0; padding: 12px; border-right: var(--hairline) solid var(--line); border-bottom: var(--hairline) solid var(--line); background: var(--card); color: var(--fg); text-decoration: none; }
.ws-tile:hover { background: var(--hover); }
.ws-tile.on { background: var(--accent-soft); box-shadow: inset 0 -2px 0 var(--accent); }
.ws-tile-picture { display: grid; place-items: center; width: 100%; height: 96px; border-radius: 8px; object-fit: cover; background: var(--neutral-soft); color: var(--fg2); }
.ws-tile-picture .ws-glyph { width: 22px; height: 22px; stroke-width: 1.6; }
.ws-tile-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 13px; line-height: 1.35; overflow-wrap: anywhere; }
.ws-tile-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono); font-size: 11px; color: var(--fg3); }
.ws-shown { display: grid; place-items: center; }
.ws-shown img, .ws-shown video { display: block; max-width: 100%; max-height: 60vh; border-radius: 10px; }
.ws-shown audio { width: 100%; }
/* The card's facts read as rows, each label at its start and value at its
   end, in the narrow column. */

/* A record's line on the alarms, media, and memories pages, and a
   message's text and what the Nik understood of its media. */
.ws-said { margin-top: 8px; font-size: 14.5px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.ws-media-text { margin-top: 10px; padding: 8px 11px 9px; border-radius: 12px; background: var(--well); }
.ws-media-text .ws-said { margin-top: 3px; }
.ws-media-text-label { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 12px; font-weight: 600; color: var(--fg2); }
.ws-media-text-label .ws-glyph { width: 14px; height: 14px; }

/* Talk (#2635), as the D design draws it: the conversations, one
   conversation as a chat, and the entry the address selects, side by side
   under the band and each scrolling on its own. The chat scrolls from the
   bottom, so the newest message is in view and a refresh keeps the place. */
.ws-chat {
  display: grid; grid-template-columns: minmax(220px, 280px) minmax(0, 1fr); grid-template-areas: "list thread";
  height: max(480px, calc(100dvh - var(--ws-head-h, 161px)));
  border-bottom: var(--hairline) solid var(--line-strong); background: var(--card);
}
.ws-chat.paned { grid-template-columns: minmax(200px, 260px) minmax(0, 1fr) minmax(280px, 380px); grid-template-areas: "list thread pane"; }
.ws-chat > .ws-chat-list { grid-area: list; }
.ws-chat > .ws-chat-thread { grid-area: thread; }
.ws-chat > .ws-chat-pane { grid-area: pane; }
.ws-chat-list { --pane-x: 18px; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; border-right: var(--hairline) solid var(--line-strong); }
.ws-chat-list .ws-notice { margin: 12px; }
.ws-chat-thread { --pane-x: 22px; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.ws-chat-none { padding: 28px; }
.ws-chat-back { display: none; font-size: 14px; font-weight: 600; }
.ws-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 0 16px; }
.ws-filters-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--fg3); }
.ws-filter { display: inline-flex; align-items: center; gap: 6px; min-height: 36px; font-size: 14px; color: var(--fg2); text-decoration: none; }
.ws-filter.on { color: var(--fg); font-weight: 600; }
.ws-filter > .ws-glyph { width: 14px; height: 14px; color: var(--fg3); }
.ws-filter.on > .ws-glyph { color: var(--accent); }
.ws-filter-box .ws-glyph { width: 11px; height: 11px; stroke-width: 3; }
.ws-filter-box { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; box-sizing: border-box; border: 1.5px solid var(--fg3); border-radius: 4px; font-size: 11px; color: var(--accent-ink); }
.ws-filter.on .ws-filter-box { border: 0; background: var(--accent); }
.ws-chat-scroll { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column-reverse; }
.ws-chat-msgs { display: flex; flex-direction: column; gap: 16px; padding: 20px 28px; }
.ws-chat-msgs > .ws-pager { padding: 0; }
.ws-chat-hidden { align-self: center; font-size: 12.5px; color: var(--fg3); }
.ws-chat-day { display: flex; align-items: center; gap: 12px; font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--fg3); }
.ws-chat-day::before, .ws-chat-day::after { content: ""; flex: 1; height: var(--hairline); background: var(--line-strong); }
.ws-chat-turn { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.ws-chat-turn.person { align-items: flex-end; }
.ws-chat-meta { font-family: var(--font-mono); font-size: 11px; color: var(--fg2); }
.ws-chat-meta a { color: inherit; text-decoration: none; }
.ws-chat-meta a:hover { color: var(--accent); text-decoration: underline; }
.ws-chat-nik { color: var(--accent); font-weight: 600; }
.ws-chat-meta .ws-chat-act { margin-left: 10px; color: var(--accent); }
.ws-chat-bubble { max-width: min(70%, 64ch); padding: 10px 14px; border-radius: 18px 18px 18px 6px; background: var(--accent-soft); font-size: 15px; line-height: 1.5; overflow-wrap: anywhere; }
.ws-chat-bubble .ws-said { margin-top: 0; font-size: inherit; }
/* What the Nik heard in a voice note or saw in a photo is part of the
   bubble, not a box inside it: a labelled run under a hairline when the
   message has words of its own. */
.ws-chat-bubble .ws-media-text { margin: 0; padding: 0; border-radius: 0; background: none; }
.ws-chat-bubble > * + .ws-media-text { margin-top: 8px; padding-top: 8px; border-top: var(--hairline) solid var(--line-strong); }
.ws-chat-turn.person .ws-chat-bubble { border-radius: 18px 18px 6px 18px; background: var(--fg); color: var(--accent-ink); }
.ws-chat-turn.person .ws-chat-bubble > * + .ws-media-text { border-top-color: var(--night-ring); }
.ws-chat-turn.person :is(.ws-media-text-label, .ws-note) { color: var(--night-ink2); }
.ws-chat-turn.on .ws-chat-bubble { box-shadow: 0 0 0 2px var(--accent); }
/* A Nik turn's tool calls, as the D design draws them: a quiet thread on a
   rail under the Nik's name, smaller and lighter than what it wrote, each
   call a small drawing of what it works on, its tool, and why, a spinner
   on one still running, the chosen one underlined. */
.ws-chat-calls { display: flex; flex-direction: column; margin: 0 0 0 4px; padding: 0; list-style: none; max-width: 100%; border-left: 2px solid var(--accent-soft); }
.ws-chat-call { display: flex; align-items: center; gap: 7px; min-height: 26px; padding: 0 10px; font-family: var(--font-mono); font-size: 12px; color: var(--fg2); text-decoration: none; }
.ws-chat-call:hover .ws-chat-tool { color: var(--accent); }
.ws-chat-call.on .ws-chat-tool { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.ws-chat-mark { flex: none; display: grid; place-items: center; width: 14px; height: 14px; color: var(--fg3); }
.ws .ws-chat-mark svg[viewBox] { width: 13px; height: 13px; }
.ws-chat-tool { flex: none; font-weight: 500; color: var(--fg2); }
.ws-chat-why { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg3); }
.ws-chat-event { align-self: center; max-width: 80%; font-size: 13px; text-align: center; }
.ws-chat-event > p:first-child { display: inline-flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px; }
.ws-chat-event a { font-weight: 600; text-decoration: none; }
.ws-chat-event a:hover, .ws-chat-event.on a { text-decoration: underline; }
.ws-chat-event-body { margin-top: 4px; color: var(--fg2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* A list beside one entry of it, as Contacts draws it in the D design: the
   rows on the left, the entry drawn whole on the right. On a phone the
   index is the list and an entry's page the entry, with a way back. */
.ws-split { display: grid; grid-template-columns: minmax(280px, 400px) minmax(0, 1fr); align-items: start; border-bottom: var(--hairline) solid var(--line-strong); background: var(--card); }
.ws-split > .ws-split-list { position: sticky; top: 0; max-height: 100dvh; overflow-y: auto; border-right: var(--hairline) solid var(--line-strong); }
.ws-split-list .ws-notice { margin: 12px; }
.ws-split-detail { display: flex; flex-direction: column; gap: 22px; min-width: 0; padding: 24px 32px 40px; }
/* The head of what a split shows beside its list: a face or a tile, what
   kind of thing it is over its name, where it stands beside the name, a
   line under it, and a link at the right. */
.ws-detail-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; }
.ws-detail-mark { flex: none; display: grid; }
.ws-detail-text { flex: 1 1 220px; min-width: 0; }
.ws-detail-kicker { margin: 0 0 4px; font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); }
.ws-detail-status { display: inline-flex; align-items: center; }
.ws-detail-name { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
.ws .ws-detail-title { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; min-width: 0; margin: 0; font-size: 22px; line-height: 1.2; font-weight: 700; letter-spacing: -0.02em; overflow-wrap: anywhere; }
.ws-detail-title code { font-size: .92em; font-weight: 600; }
.ws-detail-title code.ws-detail-code { font-size: 13px; font-weight: 500; color: var(--fg2); }
.ws-detail-lead { margin-top: 6px; font-size: 14.5px; color: var(--fg2); }
.ws-detail-lead > p { margin: 0; }
.ws-detail-lead a { text-decoration: none; }
.ws-detail-action { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 14px; }
.ws-detail-action a { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; text-decoration: none; }
.ws-detail-action a:hover { text-decoration: underline; }
.ws-detail-aside { font-size: 13px; color: var(--fg2); }
/* Parts side by side, as many to a row as fit. */
.ws-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 24px; }
/* A fact of several values, one to a line. */
.ws-values { margin: 0; padding: 0; list-style: none; }
.ws-split-back { display: none; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; }
.ws-split-back::before { content: ""; width: 16px; height: 16px; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m12%2019-7-7%207-7M19%2012H5%22%20%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m12%2019-7-7%207-7M19%2012H5%22%20%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; }
/* The shell page: each session in the list by what it was for, its command
   and how it ended, and the selected one as its terminal. */
.ws-term { margin: 0; max-height: 70vh; overflow: auto; padding: 18px 20px; border-radius: 12px; background: var(--code-bg); color: var(--code-fg); font-family: var(--font-mono); font-size: 13px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
.ws-term-prompt, .ws-term-caret { color: var(--night-accent); }
.ws-term-caret { animation: terminal-cursor-blink 1s step-end infinite; }
@media (prefers-reduced-motion: reduce) { .ws-term-caret { animation: none; } }
/* The tool calls page: each call in the list by its mark, its tool and
   what it ran during, its input on one line, and when and how long; the
   selected one with its facts, input and output. */
.ws-lines { margin: 0; padding: 0; list-style: none; }
.ws-line { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 10px; padding: 7px 0; border-top: var(--hairline) solid var(--line); font-size: 14px; }
.ws-line a { min-width: 0; text-decoration: none; overflow-wrap: anywhere; }
.ws-line a:hover { text-decoration: underline; }
.ws-line-end { font-size: 12.5px; color: var(--fg2); }
.ws-line code { font-family: var(--font-mono); font-size: 13px; overflow-wrap: anywhere; }
@media (max-width: 760px) {
  .ws-split { display: block; }
  .ws-split > .ws-split-list { position: static; max-height: none; border-right: 0; }
  .ws-split.index .ws-split-detail, .ws-split:not(.index) .ws-split-list { display: none; }
  .ws-split-back { display: inline-flex; }
  .ws-split-detail { padding: 16px var(--ws-x) 28px; }
}
/* Find: a page's search box, among its band's actions, a field set into
   the sky. */
.ws-find { display: flex; align-items: center; gap: 8px; min-height: 40px; width: min(300px, 100%); padding: 0 12px; border-radius: 12px; background: var(--night-field); box-shadow: inset 0 0 0 var(--edge) var(--night-ring); color: var(--night-ink); text-shadow: none; }
.ws .ws-find svg[viewBox] { flex: none; width: 15px; height: 15px; }
.ws-find input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--night-ink); font: inherit; font-size: 15px; }
.ws-find input::placeholder { color: var(--night-ink2); }
.ws-find:focus-within { box-shadow: inset 0 0 0 var(--edge) var(--night-ink); }
@media (max-width: 760px) { .ws-find { width: 100%; } }
/* A table across the surface: a header of small words, rows split by
   hairlines, numbers and dates in mono. */
.ws-table-wrap { overflow-x: auto; }
.ws-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.ws-table th { padding: 10px; border-bottom: var(--hairline) solid var(--line-strong); font-size: 12px; font-weight: 500; text-align: left; color: var(--fg2); }
.ws-table td { padding: 10px; border-bottom: var(--hairline) solid var(--line); vertical-align: top; }
.ws-table th:first-child, .ws-table td:first-child { padding-left: var(--ws-x); }
.ws-table th:last-child, .ws-table td:last-child { padding-right: var(--ws-x); }
.ws-table tbody tr:hover { background: var(--hover); }
.ws-table tbody tr:has(a[href]) { cursor: pointer; }
.ws-table tbody tr:has(a[href]):active { background: var(--accent-soft); }
.ws-table a { text-decoration: none; }
.ws-table a:hover { text-decoration: underline; }
.ws-table-num { font-family: var(--font-mono); font-size: 12px; color: var(--fg2); white-space: nowrap; }
.ws-table-prose { line-height: 1.45; white-space: pre-line; overflow-wrap: anywhere; }
.ws-table-code { font-family: var(--font-mono); font-size: 13px; white-space: normal; overflow-wrap: anywhere; }
.ws-table-quiet, .ws-table-quiet a { color: var(--fg2); }
.ws-table-end { text-align: right; }
.ws-table-amount { font-family: var(--font-mono); font-size: 12px; white-space: nowrap; }
.ws-table .ws-table-amount { text-align: right; }
.ws-table-title { line-height: 1.45; white-space: normal; overflow-wrap: anywhere; }
.ws-table-title a { color: var(--fg); font-weight: 500; }
.ws-table-title a[aria-current="page"] { font-weight: 600; }
/* A table keeps room for its words and scrolls across a phone rather than
   crush them: a column of words is never narrower than a short line, a
   column of names than a long name, and a table of five columns or more
   keeps its width. */
.ws-table :is(.ws-table-prose, .ws-table-title) { min-width: 180px; }
.ws-table .ws-table-code { min-width: 160px; }
.ws-table:has(> thead th:nth-child(5)) { min-width: 640px; }
/* A part places a table among its words: its first and last columns line
   up with the part's label. */
.ws-part .ws-table :is(th, td):first-child { padding-left: 0; }
.ws-part .ws-table :is(th, td):last-child { padding-right: 0; }
.ws-table-end .confirm-body { text-align: left; }
/* The activations: today's counts across the page, then a table whose
   figures stand at its right edge, each row marked running, finished or
   failed, as the D design reads them. */
.ws-main > .ws-counts { border-top: var(--hairline) solid var(--line-strong); }
/* What an activation did: the tools it called, each beside its drawing,
   and where it happened over what woke it. */
/* The kinds of memory (#2643), each in its own colour, so the list reads
   by kind at a glance. */
/* A kind's colours: its ink, and the wash its marks sit on. */
.ws-kind { display: contents; color: var(--mark); }
[data-kind="all"] { --mark: var(--accent); --wash: var(--accent-soft); }
[data-kind="preference"] { --mark: #c43d6f; --wash: rgba(196, 61, 111, .12); }
[data-kind="personal_fact"] { --mark: #7c5cd6; --wash: rgba(124, 92, 214, .13); }
[data-kind="relationship_fact"] { --mark: #3a6fd8; --wash: rgba(58, 111, 216, .12); }
[data-kind="standing_decision"] { --mark: #0f8378; --wash: rgba(15, 131, 120, .12); }
[data-kind="open_loop"] { --mark: #c07a14; --wash: var(--warn-soft); }
[data-kind="closed_loop"] { --mark: var(--ok); --wash: var(--ok-soft); }
[data-kind="procedure"] { --mark: #5d6b82; --wash: rgba(93, 107, 130, .13); }
[data-kind="retraction"], [data-kind="other"] { --mark: var(--fg2); --wash: var(--line); }
/* The colours of a skill's mark: one for each built-in skill, and a few a
   learned skill takes by its name. */
[data-kind="rose"] { --mark: #b23a62; --wash: rgba(196, 61, 111, .12); }
[data-kind="amber"] { --mark: #8c5a12; --wash: rgba(217, 140, 33, .16); }
[data-kind="violet"] { --mark: #6d43c9; --wash: rgba(124, 92, 214, .13); }
[data-kind="blue"] { --mark: #2f5fc4; --wash: rgba(58, 111, 216, .12); }
[data-kind="slate"] { --mark: #3d3552; --wash: rgba(61, 53, 82, .10); }
[data-kind="teal"] { --mark: #0d7a68; --wash: rgba(15, 131, 120, .13); }
[data-kind="cyan"] { --mark: #0f6f8c; --wash: rgba(15, 111, 140, .12); }
[data-kind="indigo"] { --mark: #4146b8; --wash: rgba(65, 70, 184, .12); }
[data-kind="green"] { --mark: #24773f; --wash: rgba(36, 119, 63, .12); }
[data-kind="olive"] { --mark: #4f6a1c; --wash: rgba(79, 106, 28, .13); }
/* How far along something is, in the band: how many of how many over a
   bar of one step for each, the done ones filled. */
.ws-progress { display: flex; flex-direction: column; gap: 8px; width: min(300px, 100%); }
.ws-progress-says { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 4px 12px; font-size: 15px; font-weight: 600; }
.ws-progress-note { font-size: 13px; font-weight: 400; color: var(--night-ink2); }
.ws-progress-bar { display: flex; gap: 4px; }
.ws-progress-step { flex: 1; height: 8px; border-radius: 3px; background: var(--night-wash); }
.ws-progress-step.on { background: var(--night-ink); }
/* What waits on the reader, on a warm wash: its heading over its rows. */
.ws-needs { background: var(--warn-soft); }
.ws-needs .ws-section-kicker { color: var(--warn); }
.ws-needs > .ws-rows { border-bottom: 0; }
/* A grid of things: as many columns as fit at 300 pixels, a cell in each,
   so a row the cells do not fill keeps its columns, split by hairlines;
   each cell its mark in the colours of its kind, its title and where it
   stands, what it does, and a figure at its foot over a bar as long as its
   share of the most. */
.ws-cells { overflow: hidden; border-top: var(--hairline) solid var(--line); border-bottom: var(--hairline) solid var(--line-strong); }
.ws-cells > ul { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); margin: 0 calc(-1 * var(--hairline)) calc(-1 * var(--hairline)) 0; padding: 0; list-style: none; }
.ws-cell { min-width: 0; border-right: var(--hairline) solid var(--line); border-bottom: var(--hairline) solid var(--line); }
.ws-cell > a { box-sizing: border-box; display: flex; flex-direction: column; gap: 12px; height: 100%; padding: 20px var(--ws-x) 18px; color: inherit; text-decoration: none; }
.ws-cell > a:hover { background: var(--hover); }
.ws-cell-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.ws-cell-mark { flex: none; display: grid; place-items: center; width: 44px; height: 44px; border-radius: 13px; background: var(--wash, var(--accent-soft)); color: var(--mark, var(--accent)); }
.ws-cell-mark > .ws-glyph { width: 22px; height: 22px; }
.ws-cell-mark.brand { background: var(--card); box-shadow: 0 0 0 var(--hairline) var(--line-strong); }
.ws-cell-mark.brand svg { width: 22px; height: 22px; }
.ws-cell-mark.brand svg.ws-wordmark { width: 32px; height: 32px; }
.ws-cell-mark.brand svg.ws-app-icon { width: 100%; height: 100%; border-radius: inherit; }
.ws-cell.open .ws-cell-mark { box-sizing: border-box; background: none; border: 1.5px dashed var(--accent); color: var(--accent); }
.ws-cell-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ws-cell-title { font-size: 17px; font-weight: 700; line-height: 1.25; overflow-wrap: anywhere; }
.ws-cell-title code { font-family: var(--font-mono); font-size: 15px; }
.ws-cell-state { font-size: 13px; }
.ws-cell-says { font-size: 15px; line-height: 1.45; color: var(--fg2); }
.ws-cell-foot { display: flex; align-items: center; gap: 10px; margin-top: auto; }
.ws-cell-bar { flex: 1; min-width: 40px; height: 4px; border-radius: 2px; background: var(--accent-soft); overflow: hidden; }
.ws-cell-bar rect { fill: var(--mark, var(--accent)); }
.ws-cell-figure { flex: none; margin-left: auto; font-family: var(--font-mono); font-size: 12px; color: var(--fg2); }
/* The pane: the entry the address selects, drawn whole. */
.ws-chat-pane { --pane-x: 20px; display: grid; grid-template-columns: [edge-start] 20px [text-start] minmax(0, 1fr) [text-end] 20px [edge-end]; align-content: start; min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 20px; border-left: var(--hairline) solid var(--line-strong); }
.ws-chat-pane > * { grid-column: text; }
.ws-chat-pane > .ws-pane-bar { grid-column: edge; margin-bottom: 12px; }
.ws-chat-pane .ws-pane-bar h2 { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; font-size: 15px; font-weight: 700; }
.ws-chat-pane > .ws-facts { margin-bottom: 12px; }
@media (max-width: 1100px) {
  .ws-chat.paned { grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); }
  .ws-chat.paned .ws-chat-list { display: none; }
}
/* A phone draws one pane: the list on the index, a conversation's chat on
   its page with a way back, and the selected entry over the chat. */
@media (max-width: 760px) {
  .ws-chat, .ws-chat.paned { display: flex; flex-direction: column; height: auto; }
  .ws-chat.index .ws-chat-thread, .ws-chat.index .ws-chat-pane, .ws-chat:not(.index) .ws-chat-list { display: none; }
  .ws-chat > * { flex: none; }
  .ws-chat-back { display: inline-flex; flex-basis: 100%; padding-top: 12px; }
  .ws-chat-scroll { overflow-y: visible; }
  .ws-chat-msgs { padding: 16px var(--ws-x); }
  .ws-chat-bubble { max-width: 85%; }
  .ws-chat-pane { order: -1; border-left: 0; border-bottom: var(--hairline) solid var(--line-strong); }
}

/* Loading. fx's bar runs across the top of the window from the moment a
   request goes out, and fades when it lands, so a quick answer reads as a
   click that landed rather than a flicker. A page that takes longer than a
   moment turns into its own skeleton where it stands (site/workspace.js):
   every component keeps its box, its words become bars where they sit, its
   pictures flat tiles and its drawings nothing, and the surface pulses. So
   nothing moves until the page it asked for is drawn, and then only once.
   The band keeps its sky, its words bars of light. A link within the page
   draws only the part it opens so, .ws-sk-part, and the band's words when
   the page it opens names itself anew, .ws-sk-band. .ws-skeleton draws a
   component so anywhere, as the gallery does (/family/design?loading=1). */
#fx-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 100;
  transform-origin: 0 50%; transform: scaleX(0); opacity: 0;
  background: var(--accent); pointer-events: none;
  transition: opacity var(--fast) var(--ease);
}
#fx-progress.fx-loading { opacity: 1; animation: ws-progress 8s cubic-bezier(0, .6, .3, 1) forwards; }
@keyframes ws-progress { 0% { transform: scaleX(0); } 40% { transform: scaleX(.55); } 100% { transform: scaleX(.9); } }
:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton, .ws-main.fx-loading > .ws-sk-band) :is(.ws-head-text, .ws-sky-text) > * {
  width: fit-content; max-width: 100%; color: transparent; text-shadow: none; box-shadow: none;
  background: rgba(255, 255, 255, .3); border-color: transparent; border-radius: 6px;
}
:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton, .ws-main.fx-loading > .ws-sk-band) :is(.ws-head-text, .ws-sky-text) > * * { visibility: hidden; }
:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) .ws-head > :not(.ws-head-text) { visibility: hidden; }
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) {
  pointer-events: none; user-select: none;
  animation: ws-pulse 1.1s ease-in-out infinite alternate;
}
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part),
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) * {
  color: transparent; text-shadow: none; caret-color: transparent;
  text-decoration-line: line-through; text-decoration-style: solid; text-decoration-color: var(--skeleton);
  text-decoration-thickness: .72em; text-decoration-skip-ink: none;
}
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) :is(svg, canvas) { visibility: hidden; }
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) :is(img, video) { filter: brightness(0) invert(.91); }
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) ::placeholder { color: transparent; }
/* A dark bubble waits as a light one, so its bars show and it does not
   flash dark before its words arrive. */
:is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) .ws-chat-turn.person .ws-chat-bubble { background: var(--accent-soft); }
@keyframes ws-pulse { from { opacity: 1; } to { opacity: .55; } }
@media (prefers-reduced-motion: reduce) {
  #fx-progress.fx-loading { animation: none; transform: scaleX(.9); }
  :is(:is(.ws-main.fx-loading.ws-waiting, .ws-skeleton) > :not(.ws-head, .ws-sky), .ws-main.fx-loading .ws-sk-part) { animation: none; }
}

/* A line drawing beside a tab's name or an action's, in the colour of its
   text; a tab's is quiet until the tab is chosen. */
.ws-glyph { flex: none; width: 16px; height: 16px; }
.ws-tab .ws-glyph { color: var(--fg3); }
.ws-tab.on .ws-glyph, .ws-tab:hover .ws-glyph { color: var(--accent); }
/* A skill that signs in to more than one account: each account a line
   with its mark, its address, its state and Disconnect, and a dashed line
   under them that adds another. */
.ws-logins { display: flex; flex-direction: column; gap: 8px; }
.ws-login { display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 8px 10px; border-radius: 12px; box-shadow: inset 0 0 0 var(--hairline) var(--line-strong); }
.ws-login-mark { flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; background: var(--bg); font-size: 14px; font-weight: 700; color: var(--accent); }
.ws-login-mark svg { width: 18px; height: 18px; }
.ws-login-what { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ws-login-service { font-size: 12.5px; color: var(--fg2); }
.ws-login-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14.5px; font-weight: 500; }
.ws-login-name em { font-style: normal; font-weight: 400; color: var(--fg3); }
.ws-login-state, .ws-login-action { flex: none; display: flex; align-items: center; gap: 8px; }
.ws-login-action form { margin: 0; }

/* The band stays at the top of the window while the page scrolls, and the
   tabs and the lists that stick do so under it (--ws-head-h, measured by
   workspace.js). A phone keeps the navigation at the top instead. */
@media (min-width: 761px) {
  .ws-main > .ws-head { position: sticky; top: 0; z-index: 20; }
  .ws-split > .ws-split-list { top: var(--ws-head-h, 0); max-height: calc(100dvh - var(--ws-head-h, 0px)); }
  .ws-split.gallery > .ws-split-detail { top: var(--ws-head-h, 0); }
}

/* Settings, a section to a tab: the form holds every section, so a save
   sends them all, and the ones of other tabs are drawn hidden. */
.ws-settings[hidden], .ws-settings-section[hidden] { display: none; }
/* The conversations tab: each conversation under its mark, its name over
   where it is, and what the Nik may do there. */

/* A comet that circles while something runs: a ring whose ink fades from
   its head to nothing, with a bright dot at the head, over a faint track,
   in the colour of its text. */
.ws-spin, :is(.ws-btn, .ws-tab, .ws-files-changes, .ws-files-step).ws-pending::after, .ws-btn.fx-loading::after, .ws-searching::before {
  --spin: 14px; --ring: 2.5px;
  position: relative; display: inline-block; flex: none; width: var(--spin); height: var(--spin); vertical-align: -2px;
  border-radius: 50%; background: conic-gradient(from 90deg, transparent 0 10%, currentColor 100%);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring)), #000 calc(100% - var(--ring) + .5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring)), #000 calc(100% - var(--ring) + .5px));
  animation: nik-spin .85s linear infinite;
}
.ws-spin::before { content: ""; position: absolute; inset: 0; border-radius: 50%; box-shadow: inset 0 0 0 var(--ring) currentColor; opacity: .14; }
/* A control on its way: the link the reader chose, until the page it asked
   for is drawn, and a button whose form is sending. A button, a tab, or a
   chip carries the comet after its words. */
.ws-pending { cursor: progress; }
:is(.ws-btn, .ws-tab, .ws-files-changes, .ws-files-step).ws-pending::after, .ws-btn.fx-loading::after { content: ""; --spin: 12px; --ring: 2px; }
.ws-btn.fx-loading { pointer-events: none; }
/* A search field fetching the results for the words typed: the comet
   takes its glyph's place in the glyph's own cell, so nothing beside it
   moves, and the results it will replace fade. */
.ws-searching::before { content: ""; grid-area: mark; --spin: 18px; color: var(--accent); }
.ws-searching > svg[viewBox] { visibility: hidden; }
:is(#find-results, #search-results)[aria-busy="true"] { opacity: .55; transition: opacity var(--fast) var(--ease); }
.ws-pill .ws-spin { --spin: 11px; --ring: 2px; }
.ws-chat-mark .ws-spin, .ws-tick .ws-spin { --spin: 13px; }
@media (prefers-reduced-motion: reduce) { .ws-spin { animation-duration: 2.4s; } }

/* People (family_people.gsx): the family at the top of one list with
   everyone the Nik knows under it, each member as a card of who they are,
   their role, and each way they reach the Nik under its service's mark. */
.ws-face { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%; background: var(--accent-grad); color: var(--accent-ink); font-size: 14px; font-weight: 700; }
.ws-face.s30 { width: 30px; height: 30px; font-size: 12px; }
.ws-face.s56 { width: 56px; height: 56px; font-size: 20px; }
.ws-face.home { border-radius: 12px; background: var(--accent-soft); color: var(--accent); }
.ws-face.s30.home { border-radius: 9px; }
.ws-face .ws-glyph { width: 55%; height: 55%; }
.ws-face > img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }

/* The work a Nik did, shared by activations, tool calls, shell, and tasks:
   each tool call folded to what it was for, opened to what it was passed
   and returned, drawn for what it holds; a shell command as its terminal;
   and the figures folded under Details at the end. */
.ws-fold-rows { border-block: var(--hairline) solid var(--line-strong); }
.ws-fold-row + .ws-fold-row { border-top: var(--hairline) solid var(--line); }
.ws-fold-row > summary { display: flex; align-items: center; gap: 12px; min-width: 0; padding: 12px var(--ws-x); cursor: pointer; list-style: none; }
.ws-fold-row > summary::-webkit-details-marker { display: none; }
.ws-fold-row > summary:hover { background: var(--accent-soft); }
.ws-fold-row > summary::after { content: ""; flex: none; width: 7px; height: 7px; margin-left: 2px; border-right: 1.6px solid var(--fg3); border-bottom: 1.6px solid var(--fg3); transform: rotate(-45deg); transition: transform .15s ease; }
.ws-fold-row[open] > summary::after { transform: rotate(45deg); }
.ws-fold-row-main { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ws-fold-row-title { overflow: hidden; font-size: 14.5px; font-weight: 600; white-space: nowrap; text-overflow: ellipsis; }
code.ws-fold-row-title { font-family: var(--font-mono); font-size: 13.5px; }
.ws-fold-row-sub { overflow: hidden; font-size: 12.5px; color: var(--fg2); white-space: nowrap; text-overflow: ellipsis; }
.ws-fold-row-sub code { font-family: var(--font-mono); font-size: 12px; }
.ws-fold-row-side { flex: none; font-family: var(--font-mono); font-size: 12px; color: var(--fg2); }
.ws-fold-row-body { display: flex; flex-direction: column; gap: 16px; padding: 4px var(--ws-x) 18px; }
.ws-fold-row.marked > .ws-fold-row-body { padding-left: calc(var(--ws-x) + 42px); }
/* Inside a part, as beside a split's list, the rows line up with the
   part's label rather than the page's margin. */
.ws-part .ws-fold-row > :is(summary, .ws-fold-row-body) { padding-inline: 0; }
.ws-part .ws-fold-row.marked > .ws-fold-row-body { padding-left: 42px; }
@media (max-width: 760px) {
  .ws-fold-row.marked > .ws-fold-row-body { padding-left: var(--ws-x); }
  .ws-part .ws-fold-row.marked > .ws-fold-row-body { padding-left: 0; }
}
.ws-parts { display: grid; gap: 16px; }
/* JSON as its keys beside their values. */
.ws-json { padding: 12px 14px; border-radius: var(--radius); background: var(--neutral-soft); font-size: 13.5px; line-height: 1.5; overflow-wrap: anywhere; }
.ws-json-obj { display: grid; grid-template-columns: minmax(80px, max-content) minmax(0, 1fr); gap: 6px 16px; margin: 0; }
.ws-json-pair { display: contents; }
.ws-json-pair > dt { padding-top: 1px; font-family: var(--font-mono); font-size: 12px; color: var(--fg2); }
.ws-json-pair > dd { min-width: 0; margin: 0; }
.ws-json-obj .ws-json-obj, .ws-json-arr .ws-json-obj { padding-left: 12px; border-left: 2px solid var(--line-strong); }
.ws-json-arr { display: grid; gap: 6px; margin: 0; padding-left: 1.6em; }
.ws-json-arr > li::marker { font-family: var(--font-mono); font-size: 11px; color: var(--fg3); }
.ws-json-string { color: var(--fg); }
.ws-json-number { font-family: var(--font-mono); font-size: 12.5px; color: var(--accent); }
.ws-json-bool { font-family: var(--font-mono); font-size: 12.5px; color: var(--warn); }
.ws-json-null { font-family: var(--font-mono); font-size: 12.5px; color: var(--fg3); }
.ws-json-text { max-height: 420px; margin: 0; padding: 12px 14px; overflow: auto; border-radius: var(--radius); background: var(--neutral-soft); color: var(--fg); font-family: var(--font-mono); font-size: 12.5px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
.ws-json .ws-json-text { padding: 8px 10px; background: var(--card); }
.ws-json-text.bad { background: var(--danger-soft); color: var(--danger); }
.ws-json-md { padding: 12px 16px; border-radius: var(--radius); background: var(--card); box-shadow: inset 0 0 0 var(--hairline) var(--line-strong); }
.ws-json .ws-json-md { padding: 8px 12px; }
/* A shell command as its terminal showed it. */
.ws-term-box { overflow: hidden; border-radius: var(--radius); background: var(--code-bg); }
.ws-term-box > .ws-term { max-height: 480px; border-radius: 0; }
.ws-term.bad { color: #ffb3c0; }
.ws-term-foot { display: flex; align-items: center; gap: 10px; min-height: 36px; padding: 0 10px 0 20px; border-top: 1px solid rgba(255, 255, 255, .08); font-family: var(--font-mono); font-size: 12px; color: rgba(233, 223, 247, .6); }
.ws-term-state { display: inline-flex; align-items: center; gap: 6px; }
.ws-term-state.ok { color: #7ee2a0; }
.ws-term-state.bad { color: #ffb3c0; }
.ws-term-state.run { color: var(--night-accent); }
.ws-term-state .ws-spin { width: 12px; height: 12px; }
.ws-term-foot > a { color: var(--night-accent); font-family: var(--font-mono); text-decoration: none; }
.ws-term-foot > a:hover { text-decoration: underline; }
.ws-term-foot .ws-copy { color: rgba(233, 223, 247, .75); }
.ws-term-foot .ws-copy:hover { background: rgba(255, 255, 255, .08); color: #fff; }
/* Figures a reader rarely comes for, folded at the end of the page. */
.ws-fold > .ws-section { margin-top: 18px; }
.ws-fold > summary { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; color: var(--fg2); list-style: none; }
.ws-fold > summary:hover { color: var(--fg); }
.ws-fold > summary::-webkit-details-marker { display: none; }
.ws-fold[open] > summary { margin-bottom: 12px; }
.ws-fold > summary::after { content: ""; width: 6px; height: 6px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform .15s ease; }
.ws-fold[open] > summary::after { transform: rotate(45deg); }
.ws-fold > .ws-facts { padding-inline: 16px; border: var(--hairline) solid var(--line-strong); border-radius: var(--radius-lg); background: var(--card); }

/* A thing's own page under its band when it is not two columns: its parts
   one under another, a note and a fold's line and facts at the page's
   margin, and room under the last. */
.ws-sheet { display: flex; flex-direction: column; gap: 22px; padding: 18px 0 28px; }
.ws-sheet > .ws-note { margin: 0; padding-inline: var(--ws-x); }
.ws-sheet > .ws-fold > :is(summary, .ws-facts) { margin-inline: var(--ws-x); }
/* Words beside their drawings in a row, such as the tools an activation
   called, in the code face, or what woke the Nik; and words saying there
   is nothing, lighter than those around them. */
.ws-tags { display: inline-flex; flex-wrap: wrap; gap: 4px 12px; }
.ws-tag { display: inline-flex; align-items: center; gap: 5px; }
.ws-tag.code { font-family: var(--font-mono); font-size: 12.5px; }
.ws-tag .ws-glyph { width: 13px; height: 13px; color: var(--accent); }
.ws-faint { color: var(--fg2); font-weight: 400; }
