@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/lora-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/lora-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/lora-italic-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/lora-italic-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Work Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/fonts/worksans-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Work Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/fonts/worksans-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url(/fonts/jetbrainsmono-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url(/fonts/jetbrainsmono-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --s1: 2px;
  --s2: 4px;
  --s3: 6px;
  --s4: 8px;
  --s5: 10px;
  --s6: 12px;
  --s7: 14px;
  --s8: 16px;
  --s9: 20px;
  --s10: 24px;
  --s11: 32px;
  --s12: 40px;
  --s13: 48px;

  --t1: 9px;
  --t2: 10px;
  --t3: 11px;
  --t4: 12.5px;
  --t5: 13.5px;
  --t6: 15px;
  --t7: 17px;
  --t8: 20px;
  --t9: 24px;
  --t10: 30px;
  --t11: 40px;

  /* Small text is read in bulk and wants air; a heading is one line and a
     loose one reads as two. */
  --lh1: 1.4;
  --lh2: 1.4;
  --lh3: 1.45;
  --lh4: 1.5;
  --lh5: 1.55;
  --lh6: 1.6;
  --lh7: 1.5;
  --lh8: 1.35;
  --lh9: 1.25;
  --lh10: 1.2;
  --lh11: 1.1;

  --r1: 2px;
  --r2: 4px;
  --r3: 6px;
  --r4: 8px;
  --r5: 10px;
  --r6: 12px;
  --r7: 16px;
  --r-pill: 999px;

  /* How long a state takes to arrive. Fifty-three rules across the sheets
     spelled twenty-two different durations for the one thing a hover does,
     and a screen where two neighbours settle at different moments reads as
     two screens. Long enough to be seen, short enough not to be waited for. */
  --quick: .15s ease;

  /* The three measures every product has and each had named for itself —
     `--k-page`/`--k-prose`/`--k-gutter` in the portal, a literal 1240 in nine
     of the portal's rules and in the front page's wrapper, `container` and
     `container-narrow` in the console's. They are *measures* and not steps
     (UI-42), which is why they are lengths here and not rungs; what they are
     not is one product's, so they are named once and a product that wants a
     different page retunes the name rather than inventing one.

     `--measure` and not `--page`: `--page` is the ground a page is painted
     on, and a width called the same thing as a colour is an hour lost the
     first time somebody reaches for the wrong one. */
  --measure: 1240px;
  --prose: 62ch;
  --gutter: clamp(16px, 2.4vw, 44px);
}

:root {
  color-scheme: light dark;

  --page:    oklch(0.95 0.008 85);
  --surface: oklch(0.99 0.004 85);
  --mid:     oklch(0.975 0.005 85);
  --rail:    oklch(0.965 0.006 85);
  --field:   oklch(0.96 0.006 85);
  --hover:   oklch(0.955 0.006 85);
  --chip:    oklch(0.93 0.006 85);
  --line:    oklch(0.91 0.006 85);
  --line2:   oklch(0.88 0.006 85);
  --hair:    oklch(0.93 0.006 85);
  --edge:    oklch(0.82 0.006 85);
  --ring:    oklch(0.78 0.01 85);

  --ink:   oklch(0.22 0.01 85);
  --ink2:  oklch(0.28 0.01 85);
  --ink3:  oklch(0.46 0.01 85);
  --mute:  oklch(0.58 0.01 85);
  --faint: oklch(0.62 0.01 85);
  --idle:  oklch(0.72 0.01 85);
  --rail-ink: oklch(0.55 0.012 85);

  --accent:      oklch(0.52 0.14 265);
  --accent-down: oklch(0.46 0.14 265);
  --accent-idle: oklch(0.82 0.03 265);
  --accent-ink:  oklch(0.99 0.004 265);
  --accent-text: oklch(0.38 0.1 265);
  --chip-ink:    oklch(0.33 0.09 265);
  --wash:        oklch(0.52 0.14 265 / 0.09);
  --wash-faint:  oklch(0.52 0.14 265 / 0.05);
  --accent-soft: oklch(0.52 0.14 265 / 0.12);
  --accent-line: oklch(0.52 0.14 265 / 0.4);

  /* The two colours in here that are not the design's, and must not be. A QR
     is read by a camera, not by a person, and the standard it is read against
     assumes black modules on white paper — a near-white page and a near-black
     ink are close enough to look right and far enough to cost somebody a scan
     on a dim phone. They do not swap with the scheme, and nothing but a QR may
     use them. */
  --qr-paper: oklch(1 0 0);
  --qr-ink:   oklch(0 0 0);

  --star:      oklch(0.58 0.14 145);
  --star-soft: oklch(0.94 0.05 145);
  --star-ink:  oklch(0.4 0.09 145);
  --ok-bg:     oklch(0.55 0.12 145 / 0.12);
  --ok-ink:    oklch(0.42 0.1 145);

  /* The one red. It is spent on deleting and on a deadline that has actually
     passed, and nowhere else — which is what makes it mean anything. */
  --danger:      oklch(0.5 0.16 25);
  --danger-fill: oklch(0.52 0.17 25);
  --danger-down: oklch(0.45 0.17 25);
  --danger-line: oklch(0.88 0.02 25);
  --danger-soft: oklch(0.96 0.03 25);
  --danger-wash: oklch(0.95 0.05 25);
  --danger-deep: oklch(0.45 0.13 25);
  --bad-bg:      oklch(0.55 0.18 25 / 0.1);
  --flag-line:   oklch(0.55 0.18 25 / 0.35);
  --flag-bg:     oklch(0.55 0.18 25 / 0.06);
  --flag-ink:    oklch(0.42 0.14 25);

  /* Neither the accent nor a warning: the tone a note about the product wears
     when it is neither good news nor bad. */
  --alert:     oklch(0.58 0.16 35);
  --info:      oklch(0.5 0.13 285);
  --info-soft: oklch(0.96 0.025 285);

  /* An answer written and not sent is amber: asking to be finished, neither
     the accent nor the attention sand. */
  --draft-line:     oklch(0.88 0.03 60);
  --draft-bg:       oklch(0.985 0.008 60);
  --draft-edge:     oklch(0.7 0.08 60);
  --draft-ink:      oklch(0.58 0.03 60);
  --draft-tag:      oklch(0.55 0.09 60);
  --draft-tag-line: oklch(0.82 0.06 60);

  /* The accent-tinted machine text of the manual's tables and code. */
  --doc-key:  oklch(0.3 0.06 265);
  --doc-code: oklch(0.32 0.03 265);

  /* The attention tone: sand at hue 75, one lightness tinted at five
     strengths. Waiting, Block all, an overdue count and every warning wear it,
     because none of them is an error — they are things asking to be looked at,
     and colouring them red would spend the delete colour on a first-time
     sender. They are not re-answered in the dark: a translucent tint of one
     mid lightness holds under either ground. */
  --attn-h: 75;
  --attn-c: 0.06;
  --attn-fill: oklch(0.68 var(--attn-c) var(--attn-h) / 0.168);
  --warn-bg:   oklch(0.68 var(--attn-c) var(--attn-h) / 0.224);
  --warn-line: oklch(0.68 var(--attn-c) var(--attn-h) / 0.448);
  --badge-bg:   oklch(0.68 var(--attn-c) var(--attn-h) / 0.56);
  --attn-line:  oklch(0.68 var(--attn-c) var(--attn-h) / 0.7);
  --badge-line: oklch(0.68 var(--attn-c) var(--attn-h) / 0.84);
  --warn-ink:  oklch(0.44 0.066 75);
  --warn-dot:  oklch(0.66 0.096 75);

  --solid:     oklch(0.22 0.006 85);
  --solid-ink: oklch(0.95 0.004 85);

  /* An avatar's hue comes from the address, so one person is one colour
     everywhere, for everyone. Only the lightness and chroma change with the
     scheme — the hue is the identity and must not move. */
  --av-bg: 0.87 0.05;
  --av-fg: 0.38 0.075;

  --radius: 10px;

  --serif: 'Lora', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
  --sans:  'Work Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono:  'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Shadows are for what floats. Nothing in a column has one. */
  --desk:   0 1px 2px oklch(0.2 0.01 85 / 0.06), 0 24px 60px oklch(0.2 0.01 85 / 0.10);
  --lift:   0 1px 3px oklch(0.2 0.01 85 / 0.12);
  --card:   0 10px 32px oklch(0.2 0.01 85 / 0.08);
  --tip:    0 12px 30px oklch(0.2 0.01 85 / 0.24);
  --tip-sm: 0 8px 20px oklch(0.2 0.01 85 / 0.25);
  --float:  0 10px 28px oklch(0.2 0.01 85 / 0.12);
  --knob:   0 1px 3px oklch(0.2 0.01 85 / 0.25);
  --pop:   0 14px 36px oklch(0.2 0.01 85 / 0.18);
  --sheet: 0 24px 60px oklch(0.2 0.01 85 / 0.3);
  --slide: -18px 0 44px oklch(0.2 0.01 85 / 0.14);
  --shadow: 0 1px 2px oklch(0.2 0.01 85 / 0.06), 0 20px 50px oklch(0.2 0.01 85 / 0.09);
  --scrim: oklch(0.25 0.01 85 / 0.45);
  --veil:  oklch(0.2 0.01 85 / 0.16);

  /* The stage a picture fills the screen on (UIC-20): a warm near-black in
     both schemes — the picture is the light there, so the dark scheme has
     nothing to invert — its controls a veil of the ground over the picture,
     their hairline a veil of the ink. And the paper a drawing is contained
     on: white in both schemes, because white is what it was drawn on. */
  --stage: oklch(0.13 0.01 85);
  --stage-ink: oklch(0.98 0.005 85);
  --stage-veil: oklch(0.1 0.01 85 / 0.5);
  --stage-line: oklch(0.98 0.005 85 / 0.28);
  --paper: oklch(0.99 0.003 85);
}


@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --page:    oklch(0.155 0.006 85);
    --surface: oklch(0.195 0.006 85);
    --mid:     oklch(0.185 0.006 85);
    --rail:    oklch(0.17 0.006 85);
    --field:   oklch(0.22 0.006 85);
    --hover:   oklch(0.235 0.006 85);
    --chip:    oklch(0.25 0.006 85);
    --line:    oklch(0.27 0.006 85);
    --line2:   oklch(0.31 0.006 85);
    --hair:    oklch(0.25 0.006 85);
    --edge:    oklch(0.38 0.006 85);
    --ring:    oklch(0.44 0.01 85);

    --ink:   oklch(0.94 0.004 85);
    --ink2:  oklch(0.88 0.004 85);
    --ink3:  oklch(0.72 0.008 85);
    --mute:  oklch(0.58 0.008 85);
    --faint: oklch(0.55 0.008 85);
    --idle:  oklch(0.45 0.008 85);
    --rail-ink: oklch(0.62 0.01 85);

    --accent:      oklch(0.72 0.08 265);
    --accent-down: oklch(0.78 0.09 265);
    --accent-idle: oklch(0.38 0.04 265);
    --accent-ink:  oklch(0.18 0.01 265);
    --accent-text: oklch(0.8 0.09 265);
    --chip-ink:    oklch(0.82 0.08 265);
    --wash:        oklch(0.72 0.08 265 / 0.17);
    --wash-faint:  oklch(0.72 0.08 265 / 0.1);
    --accent-soft: oklch(0.72 0.08 265 / 0.24);
    --accent-line: oklch(0.72 0.08 265 / 0.45);

    --star:      oklch(0.76 0.13 145);
    --star-soft: oklch(0.27 0.04 145);
    --star-ink:  oklch(0.84 0.1 145);
    --ok-bg:     oklch(0.7 0.12 145 / 0.16);
    --ok-ink:    oklch(0.8 0.1 145);

    --danger:      oklch(0.72 0.14 25);
    --danger-fill: oklch(0.68 0.16 25);
    --danger-down: oklch(0.74 0.16 25);
    --danger-line: oklch(0.4 0.08 25);
    --danger-soft: oklch(0.28 0.05 25);
    --danger-wash: oklch(0.3 0.07 25);
    --danger-deep: oklch(0.78 0.12 25);
    --bad-bg:      oklch(0.65 0.18 25 / 0.16);
    --flag-line:   oklch(0.65 0.16 25 / 0.45);
    --flag-bg:     oklch(0.65 0.16 25 / 0.12);
    --flag-ink:    oklch(0.8 0.12 25);

    --alert:     oklch(0.62 0.16 35);
    --info:      oklch(0.76 0.11 285);
    --info-soft: oklch(0.28 0.05 285);

    --draft-line:     oklch(0.35 0.05 60);
    --draft-bg:       oklch(0.22 0.02 60);
    --draft-edge:     oklch(0.55 0.08 60);
    --draft-ink:      oklch(0.7 0.05 60);
    --draft-tag:      oklch(0.75 0.09 60);
    --draft-tag-line: oklch(0.45 0.07 60);

    --doc-key:  oklch(0.82 0.07 265);
    --doc-code: oklch(0.8 0.04 265);

    --warn-ink: oklch(0.85 0.07 75);
    --warn-dot: oklch(0.74 0.1 75);

    --solid:     oklch(0.94 0.004 85);
    --solid-ink: oklch(0.2 0.006 85);

    --av-bg: 0.36 0.06;
    --av-fg: 0.85 0.06;

    --desk:   0 1px 2px oklch(0.05 0.01 85 / 0.3), 0 24px 60px oklch(0.05 0.01 85 / 0.4);
    --lift:   0 1px 3px oklch(0.05 0.01 85 / 0.4);
    --card:   0 10px 32px oklch(0.05 0.01 85 / 0.3);
    --tip:    0 12px 30px oklch(0.05 0.01 85 / 0.5);
    --tip-sm: 0 8px 20px oklch(0.05 0.01 85 / 0.5);
    --float:  0 10px 28px oklch(0.05 0.01 85 / 0.4);
    --knob:   0 1px 3px oklch(0.05 0.01 85 / 0.5);
    --pop:   0 14px 36px oklch(0.05 0.01 85 / 0.5);
    --sheet: 0 24px 60px oklch(0.05 0.01 85 / 0.6);
    --slide: -18px 0 44px oklch(0.05 0.01 85 / 0.45);
    --shadow: 0 1px 2px oklch(0.05 0.01 85 / 0.3), 0 20px 50px oklch(0.05 0.01 85 / 0.3);
    --scrim: oklch(0.05 0.01 85 / 0.6);
    --veil:  oklch(0.05 0.01 85 / 0.35);
    --stage: oklch(0.13 0.01 85);
    --stage-ink: oklch(0.98 0.005 85);
    --stage-veil: oklch(0.1 0.01 85 / 0.5);
    --stage-line: oklch(0.98 0.005 85 / 0.28);
    --paper: oklch(0.99 0.003 85);
  }
}
:root[data-theme="dark"] {
    --page:    oklch(0.155 0.006 85);
    --surface: oklch(0.195 0.006 85);
    --mid:     oklch(0.185 0.006 85);
    --rail:    oklch(0.17 0.006 85);
    --field:   oklch(0.22 0.006 85);
    --hover:   oklch(0.235 0.006 85);
    --chip:    oklch(0.25 0.006 85);
    --line:    oklch(0.27 0.006 85);
    --line2:   oklch(0.31 0.006 85);
    --hair:    oklch(0.25 0.006 85);
    --edge:    oklch(0.38 0.006 85);
    --ring:    oklch(0.44 0.01 85);

    --ink:   oklch(0.94 0.004 85);
    --ink2:  oklch(0.88 0.004 85);
    --ink3:  oklch(0.72 0.008 85);
    --mute:  oklch(0.58 0.008 85);
    --faint: oklch(0.55 0.008 85);
    --idle:  oklch(0.45 0.008 85);
    --rail-ink: oklch(0.62 0.01 85);

    --accent:      oklch(0.72 0.08 265);
    --accent-down: oklch(0.78 0.09 265);
    --accent-idle: oklch(0.38 0.04 265);
    --accent-ink:  oklch(0.18 0.01 265);
    --accent-text: oklch(0.8 0.09 265);
    --chip-ink:    oklch(0.82 0.08 265);
    --wash:        oklch(0.72 0.08 265 / 0.17);
    --wash-faint:  oklch(0.72 0.08 265 / 0.1);
    --accent-soft: oklch(0.72 0.08 265 / 0.24);
    --accent-line: oklch(0.72 0.08 265 / 0.45);

    --star:      oklch(0.76 0.13 145);
    --star-soft: oklch(0.27 0.04 145);
    --star-ink:  oklch(0.84 0.1 145);
    --ok-bg:     oklch(0.7 0.12 145 / 0.16);
    --ok-ink:    oklch(0.8 0.1 145);

    --danger:      oklch(0.72 0.14 25);
    --danger-fill: oklch(0.68 0.16 25);
    --danger-down: oklch(0.74 0.16 25);
    --danger-line: oklch(0.4 0.08 25);
    --danger-soft: oklch(0.28 0.05 25);
    --danger-wash: oklch(0.3 0.07 25);
    --danger-deep: oklch(0.78 0.12 25);
    --bad-bg:      oklch(0.65 0.18 25 / 0.16);
    --flag-line:   oklch(0.65 0.16 25 / 0.45);
    --flag-bg:     oklch(0.65 0.16 25 / 0.12);
    --flag-ink:    oklch(0.8 0.12 25);

    --alert:     oklch(0.62 0.16 35);
    --info:      oklch(0.76 0.11 285);
    --info-soft: oklch(0.28 0.05 285);

    --draft-line:     oklch(0.35 0.05 60);
    --draft-bg:       oklch(0.22 0.02 60);
    --draft-edge:     oklch(0.55 0.08 60);
    --draft-ink:      oklch(0.7 0.05 60);
    --draft-tag:      oklch(0.75 0.09 60);
    --draft-tag-line: oklch(0.45 0.07 60);

    --doc-key:  oklch(0.82 0.07 265);
    --doc-code: oklch(0.8 0.04 265);

    --warn-ink: oklch(0.85 0.07 75);
    --warn-dot: oklch(0.74 0.1 75);

    --solid:     oklch(0.94 0.004 85);
    --solid-ink: oklch(0.2 0.006 85);

    --av-bg: 0.36 0.06;
    --av-fg: 0.85 0.06;

    --desk:   0 1px 2px oklch(0.05 0.01 85 / 0.3), 0 24px 60px oklch(0.05 0.01 85 / 0.4);
    --lift:   0 1px 3px oklch(0.05 0.01 85 / 0.4);
    --card:   0 10px 32px oklch(0.05 0.01 85 / 0.3);
    --tip:    0 12px 30px oklch(0.05 0.01 85 / 0.5);
    --tip-sm: 0 8px 20px oklch(0.05 0.01 85 / 0.5);
    --float:  0 10px 28px oklch(0.05 0.01 85 / 0.4);
    --knob:   0 1px 3px oklch(0.05 0.01 85 / 0.5);
    --pop:   0 14px 36px oklch(0.05 0.01 85 / 0.5);
    --sheet: 0 24px 60px oklch(0.05 0.01 85 / 0.6);
    --slide: -18px 0 44px oklch(0.05 0.01 85 / 0.45);
    --shadow: 0 1px 2px oklch(0.05 0.01 85 / 0.3), 0 20px 50px oklch(0.05 0.01 85 / 0.3);
    --scrim: oklch(0.05 0.01 85 / 0.6);
    --veil:  oklch(0.05 0.01 85 / 0.35);
    --stage: oklch(0.13 0.01 85);
    --stage-ink: oklch(0.98 0.005 85);
    --stage-veil: oklch(0.1 0.01 85 / 0.5);
    --stage-line: oklch(0.98 0.005 85 / 0.28);
    --paper: oklch(0.99 0.003 85);
}
@media (prefers-color-scheme: dark) and (max-width: 640px) {
  :root:not([data-theme="light"]) {
    --page:    oklch(0.1 0.004 85);
    --surface: oklch(0.125 0.005 85);
    --mid:     oklch(0.115 0.005 85);
    --rail:    oklch(0.11 0.004 85);
    --field:   oklch(0.16 0.005 85);
    --hover:   oklch(0.175 0.005 85);
    --chip:    oklch(0.19 0.005 85);
    --line:    oklch(0.3 0.006 85);
    --line2:   oklch(0.34 0.006 85);
    --hair:    oklch(0.28 0.006 85);

    --ink:   oklch(0.985 0.002 85);
    --ink2:  oklch(0.94 0.003 85);
    --ink3:  oklch(0.82 0.006 85);
    --mute:  oklch(0.7 0.008 85);
    --faint: oklch(0.64 0.008 85);
  }
}
@media (max-width: 640px) {
  :root[data-theme="dark"] {
    --page:    oklch(0.1 0.004 85);
    --surface: oklch(0.125 0.005 85);
    --mid:     oklch(0.115 0.005 85);
    --rail:    oklch(0.11 0.004 85);
    --field:   oklch(0.16 0.005 85);
    --hover:   oklch(0.175 0.005 85);
    --chip:    oklch(0.19 0.005 85);
    --line:    oklch(0.3 0.006 85);
    --line2:   oklch(0.34 0.006 85);
    --hair:    oklch(0.28 0.006 85);

    --ink:   oklch(0.985 0.002 85);
    --ink2:  oklch(0.94 0.003 85);
    --ink3:  oklch(0.82 0.006 85);
    --mute:  oklch(0.7 0.008 85);
    --faint: oklch(0.64 0.008 85);
  }
}

/* One look for every control on every surface (UIC-8), on the house tokens.
   Nothing here defines a colour or a length of its own: the tokens come first
   (`tokens::preamble`) and the application's own sheet comes after, and may
   add to a control but never redraws one.

   The shape is the mailbox's, which is the one in this product worth copying:
   the label above the box and quiet, the box a clean rounded rectangle on the
   surface, and the accent spent on focus rather than on decoration.

   **One name per thing** (UIC-10). The `.field`/`.input`/`.k-field`/`.k-input`
   aliases this file carried are gone: there is one control in the tier now, it
   spells one class, and an application's sheet that wants to add to it spells
   the same one. */

/* ── a field: a label, a box, a hint ─────────────────────────────────────── */

.ui-field {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin: 0;
  position: static;
}

.ui-label {
  display: block;
  position: static;
  margin: 0;
  font-family: inherit;
  font-size: var(--t4);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink3);
  pointer-events: auto;
}

.ui-hint {
  margin: 0;
  font-size: var(--t4);
  color: var(--mute);
  text-wrap: pretty;
}

.ui-input {
  width: 100%;
  box-sizing: border-box;
  height: 38px;
  padding: 0 var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  /* On the surface, not on `--field`. UIC-8 says so in words — "the box is a
     clean rounded rectangle on the surface" — and the mailbox it was taken
     from draws it that way; the sheet had it a shade grey, which is the rule
     and the rendering disagreeing. */
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t5);
  outline: none;
  transition: border-color 0.15s;
}

/* A price or a size reads in the figures' face: a column of them lines up. */
.ui-input.mono {
  font-family: var(--mono);
}

textarea.ui-input {
  height: auto;
  min-height: 5rem;
  padding: var(--s5) var(--s6);
  line-height: 1.5;
  resize: vertical;
}

select.ui-input {
  height: 38px;
  padding: 0 var(--s5);
}

.ui-input:focus {
  border-color: var(--accent-line, var(--accent));
}

.ui-input:disabled {
  background: var(--chip);
  color: var(--mute);
}

/* A leading glyph, and the room the box makes for it. Measured from the top
   because a field with a hint is taller, and from the bottom the glyph lands
   in the hint instead of in the box. */
.ui-field-icon {
  position: absolute;
  left: var(--s6);
  top: 27px;
  color: var(--mute);
  pointer-events: none;
  line-height: 1;
}
.ui-field.has-icon {
  position: relative;
}
.ui-field.has-icon .ui-input {
  padding-left: calc(var(--s10) + var(--s2));
}

/* A refused field, marked where it is (UI-29): the problem script hangs its
   sentence off the field itself. */
.is-invalid .ui-input,
.is-invalid .sel {
  border-color: var(--danger);
}
.ui-field-error {
  margin: 0;
  font-size: var(--t4);
  font-weight: 600;
  color: var(--danger);
  text-wrap: pretty;
}

/* A box you tick reads along its label rather than above it. */
.ui-field-check {
  flex-direction: row;
  align-items: center;
  gap: var(--s5);
}
.ui-field-check > .ui-label {
  order: 2;
  color: var(--ink2);
}
.ui-field-check .ui-checkbox {
  order: 1;
  width: auto;
  height: auto;
  flex: none;
}

/* UI-37: a price is one box — the amount stretches, the currency sits at its
   right edge. */
.ui-field.money {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--s5);
}
.ui-field.money > .ui-label {
  flex-basis: 100%;
}
.ui-field.money .money-amount {
  flex: 1 1 8rem;
}
.ui-field.money .money-cur {
  flex: 0 0 auto;
  width: auto;
  min-width: 5.2rem;
}
.ui-field.money .money-cur:disabled {
  color: var(--mute);
}
.ui-field.money > .ui-hint {
  flex-basis: 100%;
}

/* The media library's field is a strip of pictures, not a box (GAP-216). */
.ui-field.media-field > .ui-label {
  font-size: var(--t4);
}

/* ── a rectangular press: a form's, or a page's ──────────────────────────── */

.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  border-radius: var(--radius-btn, calc(var(--radius) - 3px));
  padding: .55rem .9rem;
  font-size: .8rem;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid transparent;
  transition: opacity .15s, background-color .15s, border-color .15s;
  background: none;
}
.ui-btn.is-primary {
  background: var(--btn-fill, var(--solid));
  color: var(--btn-ink, var(--solid-ink));
  border-color: var(--btn-line, transparent);
}
.ui-btn.is-primary:hover { opacity: .82; }
.ui-btn.is-plain { border-color: var(--line); color: var(--ink); }
.ui-btn.is-plain:hover { background: var(--field); }
.ui-btn.is-danger { border-color: var(--line); color: var(--danger); }
.ui-btn.is-danger:hover { background: var(--danger-soft); border-color: var(--danger); }
.ui-btn.is-block { width: 100%; }
.ui-btn.is-small { padding: .3rem .6rem; font-size: .72rem; }
.ui-btn .ui-btn-icon { margin-right: .5rem; }

/* ── a round press whose label unfurls (UI-25) ───────────────────────────── */

/* At rest it is a *circle*, not an oval: `--ui-round-size` is both the height
   and the minimum width, and the horizontal padding is set so that a single
   1em icon lands exactly on that width. Padding alone cannot do it — the old
   `.55rem .7rem` made the box 2.4rem wide and 2.1rem tall, so every icon
   button in the product was subtly egg-shaped. */
.ui-round {
  /* A knob, not a token: it is this control's own size, and an application
     turns it down for a dense row (`.row .ui-round`). Its default is a step
     off the ladder like everything else. */
  --ui-round-size: var(--s12);
  /* Minus the 1px border on each side: `min-width` is a floor, so a content
     box even slightly wider than it wins, and forgetting the border left
     every one of these two pixels wider than it was tall. */
  --ui-round-pad: calc((var(--ui-round-size) - 1rem - 2px) / 2);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  height: var(--ui-round-size);
  min-width: var(--ui-round-size);
  padding-inline: var(--ui-round-pad);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  font-size: .8rem;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: border-color .2s, background .2s, color .2s, box-shadow .2s;
}
.ui-round:hover,
.ui-round:focus-visible {
  border-color: var(--ink);
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.07);
}
.ui-round .ui-btn-icon { font-size: 1rem; }

/* The scheme press (UIC-18). A ring with a glyph in it, the same object
   whether it is a link the server answers or a button a script does — which
   is the only difference between the two surfaces that wear it, and not a
   difference anybody looking at it can see.

   Its own size and not `.ui-round`'s: this one sits in a masthead beside
   words rather than in a row of controls, and it is drawn at the height of
   the text it stands next to. */
.ui-theme {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--s11);
  height: var(--s11);
  border: 1px solid var(--line2);
  border-radius: var(--r-pill);
  background: none;
  color: var(--ink);
  font-size: var(--t5);
  line-height: 1;
  cursor: pointer;
}
.ui-theme:hover { border-color: var(--ink); }

/* The label unfurls rather than appearing: `max-width` is what a transition
   can actually animate, and the margin keeps it off the icon as it opens. */
.ui-round .ui-btn-label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width .35s cubic-bezier(.4, 0, .2, 1),
              margin-left .35s cubic-bezier(.4, 0, .2, 1);
  margin-left: 0;
}
.ui-round:hover .ui-btn-label,
.ui-round:focus-visible .ui-btn-label,
.ui-round.is-open .ui-btn-label { max-width: 14rem; margin-left: .5rem; }
.ui-round.is-closed .ui-btn-label { max-width: 0; margin-left: 0; }

/* One that always shows its label is a pill, not a circle, so it gets the
   breathing room a circle does not need. */
.ui-round.is-open { padding-inline: calc(var(--ui-round-pad) + .15rem); }

.ui-round.is-primary {
  background: var(--ink);
  color: var(--surface);
  border-color: var(--ink);
}
.ui-round.is-primary:hover { box-shadow: 0 2px 12px rgb(0 0 0 / 0.16); }
.ui-round.is-danger { color: var(--danger); border-color: var(--danger); }
.ui-round.is-danger:hover { background: var(--danger-soft); border-color: var(--danger); }

/* One that cannot be pressed has to look it. A primary that is off keeps the
   ink of a live button otherwise, which is a button lying about itself — and
   the lie is loudest exactly where it matters, on a Save with nothing to save. */
.ui-round:disabled { opacity: .4; cursor: default; }
.ui-round:disabled:hover { border-color: var(--line); box-shadow: none; }
.ui-round.is-danger:disabled:hover { background: none; border-color: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .ui-round, .ui-round .ui-btn-label { transition: none; }
}

/* ── a pill: small, outlined, inline ─────────────────────────────────────── */

.ui-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  height: 34px;
  padding: 0 var(--s7);
  border: 1px solid var(--line2);
  border-radius: var(--r5);
  background: var(--surface);
  color: var(--ink2);
  font-size: var(--t5);
  font-weight: 600;
  white-space: nowrap;
}
.ui-pill:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.ui-pill svg {
  width: 15px;
  height: 15px;
  flex: none;
}
.ui-pill-n {
  font-family: var(--mono);
  font-size: var(--t3);
  font-weight: 500;
  color: var(--mute);
}
.ui-pill:hover .ui-pill-n {
  color: inherit;
}
.ui-pill.is-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}
.ui-pill.is-primary:hover {
  color: var(--accent-ink);
}
.ui-pill.is-primary .ui-pill-n {
  color: inherit;
}
/* A lozenge: an action on a document rather than a filter in a bar. */
.ui-pill.is-small {
  height: 34px;
  padding: 0 var(--s8);
  border-radius: var(--r-pill);
  font-size: var(--t5);
}

/* ── the press that finishes a form ──────────────────────────────────────── */

.ui-submit {
  display: flex;
  align-items: center;
  gap: var(--s5);
  margin-left: auto;
  padding: var(--s6) var(--s9);
  border-radius: var(--r6);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--t6);
  font-weight: 600;
}
.ui-submit:hover {
  background: var(--accent-down);
}
/* The second way of doing the same thing, offered beside the first: the same
   button, not filled. Two filled buttons on one card is two primary actions,
   which is none. */
.ui-submit.is-plain {
  background: var(--surface);
  border: 1px solid var(--line2);
  color: var(--ink2);
  font-weight: 500;
}
.ui-submit.is-plain:hover {
  background: var(--chip);
  border-color: var(--accent);
  color: var(--accent);
}
.ui-submit svg {
  width: 17px;
  height: 17px;
  flex: none;
}
.ui-submit.is-block {
  justify-content: center;
  margin-left: 0;
  width: 100%;
}

/* ── a modal's committing action (KODU-25) ───────────────────────────────── */

.ui-modal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s4);
  min-height: 40px;
  padding: 0 var(--s9);
  border: 1px solid var(--line2);
  border-radius: var(--r5);
  background: var(--surface);
  color: var(--ink2);
  font-family: inherit;
  font-size: var(--t5);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.ui-modal:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.ui-modal.is-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}
.ui-modal.is-primary:hover {
  background: var(--accent-down);
  border-color: var(--accent-down);
  color: var(--accent-ink);
}

/* ── words alone ─────────────────────────────────────────────────────────── */

.ui-text-btn {
  color: var(--accent);
  font-size: var(--t5);
}
.ui-text-btn:hover {
  text-decoration: underline;
}

/* The surfaces a screen is made of (UIC-11), on the house tokens and defining
   nothing of its own.

   These are the shapes all three applications drew for themselves and each
   named separately — `k-quiet`/`pb-note`/`cn-note`, `k-group`/`cn-set`,
   `k-card`, `k-panel`/`pb-modal` — which is why a screen was still a
   `format!` template: there was nothing here to call. Each rule below is the
   union of the three, taking the mailbox's measure where they disagreed. */

/* ── what the screen says for itself ─────────────────────────────────────── */

.ui-note {
  margin: 0;
  font-size: var(--t4);
  line-height: var(--lh4);
  color: var(--mute);
  text-wrap: pretty;
}

/* News, in a box, in one of three weathers. Amber is the commonest and the
   one worth spelling out: nothing was lost, the form came back with what was
   typed still in it, and red would say otherwise. */
.ui-say {
  margin: 0;
  padding: var(--s5) var(--s7);
  border: 1px solid transparent;
  border-radius: var(--r5);
  font-size: var(--t5);
  line-height: var(--lh5);
  text-wrap: pretty;
}
.ui-say.is-warn {
  border-color: var(--warn-line);
  background: var(--warn-bg);
  color: var(--warn-ink);
}
.ui-say.is-wrong {
  border-color: var(--danger-line);
  background: var(--danger-soft);
  color: var(--danger);
}
.ui-say.is-done {
  border-color: var(--star-soft);
  background: var(--ok-bg);
  color: var(--ok-ink);
}

/* ── what the screen says at its foot (UIC-24) ───────────────────────────── */

/* Where the notices stand: fixed at the foot wherever the page drew them,
   clear of a phone's safe area, over every surface (the overlay is 110, the
   drawer 130). The strip takes the pointer; the space around it does not. */
.ui-notices {
  position: fixed; left: 0; right: 0; z-index: 140;
  bottom: max(var(--s9), calc(env(safe-area-inset-bottom) + var(--s9)));
  display: flex; flex-direction: column; align-items: center; gap: var(--s4);
  padding: 0 var(--gutter);
  pointer-events: none;
}

/* The mailbox's strip (PBOX-25), now everybody's: the dark solid rather than
   the red, because it is a thing that happened and not a thing to fix here —
   the thing to fix is marked where it is. The dot says which weather. */
.ui-notice {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--s6);
  width: min(560px, 100%);
  padding: var(--s6) var(--s6) var(--s6) var(--s8);
  border-radius: var(--r5);
  background: var(--solid);
  color: var(--solid-ink);
  box-shadow: var(--sheet);
  animation: ui-notice-in .22s ease-out;
}
.ui-notice.is-leaving { animation: ui-notice-out .2s ease-in forwards; }
.ui-notice-dot {
  flex: none; width: var(--s5); height: var(--s5); border-radius: 50%;
  transform: translateY(var(--s3));
  background: var(--solid-ink); opacity: .45;
}
.ui-notice.is-wrong .ui-notice-dot { background: var(--danger-fill); opacity: 1; }
.ui-notice.is-warn .ui-notice-dot { background: var(--warn-dot); opacity: 1; }
.ui-notice.is-done .ui-notice-dot { background: var(--star); opacity: 1; }
.ui-notice-body { flex: 1; min-width: 0; }
.ui-notice-said, .ui-notice-more {
  margin: 0;
  font-size: var(--t5);
  line-height: var(--lh5);
  text-wrap: pretty;
}
.ui-notice-said { font-weight: 600; }
.ui-notice-more { opacity: .78; }
.ui-notice-close {
  flex: none; width: var(--s10); height: var(--s10); padding: 0; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: none;
  color: inherit; font: inherit; font-size: var(--t7); line-height: 1;
  opacity: .7; cursor: pointer;
  transform: translateY(calc(var(--s1) * -1));
}
.ui-notice-close:hover { opacity: 1; }
.ui-notice-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@keyframes ui-notice-in {
  from { opacity: 0; transform: translateY(var(--s4)); }
  to { opacity: 1; transform: none; }
}
@keyframes ui-notice-out {
  to { opacity: 0; transform: translateY(var(--s3)); }
}
@media (prefers-reduced-motion: reduce) {
  .ui-notice, .ui-notice.is-leaving { animation: none; }
}

/* ── one word about the thing beside it ──────────────────────────────────── */

.ui-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s1) var(--s4);
  border-radius: var(--r4);
  background: var(--chip);
  color: var(--ink3);
  font-size: var(--t3);
  font-weight: 600;
  white-space: nowrap;
}
.ui-chip.is-live { background: var(--ok-bg); color: var(--ok-ink); }
.ui-chip.is-held { background: var(--chip); color: var(--mute); }
.ui-chip.is-owed { background: var(--warn-bg); color: var(--warn-ink); }

/* ── a run of controls under one label ───────────────────────────────────── */

/* A column, because a form is filled in in order, and a measure rather than
   the full width, because a line of text nobody can follow to the end is not
   a form anybody finishes. */
.ui-group {
  display: flex;
  flex-direction: column;
  gap: var(--s8);
  max-width: 560px;
}
.ui-group > .ui-label {
  margin: 0;
}

/* ── a surface ───────────────────────────────────────────────────────────── */

.ui-card {
  padding: clamp(var(--s8), 1.4vw, var(--s9));
  border: 1px solid var(--line);
  border-radius: var(--r7);
  background: var(--surface);
}

/* ── the surface a whole job sits on ─────────────────────────────────────── */

/* UI-14: `ui::overlay`. The lightbox is one of these too — it adds a stage
   and a zoom, not a way of covering the page. Here in the tier and not in
   any one application's sheet, because the components that wear it are the
   tier's: until 2026-09-08 these rules lived in Platform's theme sheet,
   which only the console loads, and the question `ui::confirm` asks stood
   unstyled at the foot of the mailbox's document, off the screen — a press
   that did nothing anybody could see (LLM-32). */
.overlay {
  position: fixed; inset: 0; z-index: 110;
  background: var(--surface);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.overlay-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; gap: 1rem;
  padding: 1rem 1.5rem;
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.overlay-body { padding: 1.5rem; }
/* One scrollbar, one wheel: the page behind stays where it was. */
body.overlay-open { overflow: hidden; }

/* UI-22: the question a destructive press asks. A card over a dimmed page
   rather than the whole surface — the page behind is the thing being decided
   about, and covering it would take the answer away with it. */
.overlay.ask {
  /* Over the drawer's 120 and the dock's 60: it is a question about whatever
     is on screen, and one of the things it is asked about is a drawer being
     closed. */
  z-index: 130;
  display: grid; place-items: center; padding: 1.5rem;
  background: color-mix(in oklab, var(--ink) 55%, transparent);
}
.overlay.ask > .overlay-body {
  width: min(28rem, 100%);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: 0 12px 32px rgb(0 0 0 / .22);
}
.ask-says { margin-top: .5rem; font-size: .875rem; color: var(--ink3); }
.ask-acts { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.5rem; }

/* ── the card a whole screen is filled in on ─────────────────────────────── */

/* `nowrap` is load-bearing: the moment a sheet gives this a height, a
   wrapping column folds its tail off the right-hand edge. */
.ui-form {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: var(--s9);
  margin: 0 auto;
  padding: clamp(var(--s8), 1.4vw, var(--s9));
  border: 1px solid var(--line);
  border-radius: var(--r7);
  background: var(--surface);
}

.ui-form-head {
  display: flex;
  align-items: center;
  gap: var(--s6);
}
/* The heading takes the room and the way out is pushed to the far edge. */
.ui-form-head > .ui-h2 { flex: 1; min-width: 0; }

/* The press stands on a line of its own under a rule, with whatever the form
   has to say about itself opposite it. */
.ui-form-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s6);
  padding-top: var(--s8);
  border-top: 1px solid var(--hair);
}
.ui-form-aside {
  font-size: var(--t4);
  color: var(--mute);
  text-wrap: pretty;
}

/* Two or three fields that belong to one fact, sharing a line and going back
   under each other where there is no room. */
.ui-field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s5);
}
/* `min-width: 0` is not decoration: without it one long unbreakable value —
   a pasted URL, an IBAN — sets the field's floor and pushes its neighbour off
   the row. Kodu's `.k-row` learned this the hard way and the tier inherits
   the lesson. */
.ui-field-row > .ui-field { flex: 1 1 12rem; min-width: 0; }

/* ── headings, at the house sizes ────────────────────────────────────────── */

.ui-h1 {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--t8);
  line-height: var(--lh8);
  font-weight: 500;
  color: var(--ink);
}
.ui-h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--t7);
  line-height: var(--lh7);
  font-weight: 500;
  color: var(--ink);
}

/* The arrangements (UIC-6, UI-18), on the house tokens and defining no colour
   and no length of its own.

   The frame fills the browser and does not scroll: `html` and `body` stay put
   and each column scrolls itself. That is the one thing the mailbox, the
   console and the portal already agreed on and each had written out
   separately.

   **The widths are the application's.** The mailbox wants 84px of rail and
   296 of list, the console wants 56 and a fourth column for a section's
   properties, the portal wants two columns and no list — so a frame wears the
   application's own class beside `ui-frame` and the application's sheet writes
   `grid-template-columns` on that, per `data-depth`. A tier that decided the
   widths would be deciding what a screen is about. */

/* The page is the container (UIC-23): every question about width — an
   application's own queries, the layer's `d-` twins — is asked of the
   body's own width and not the window's, so a page narrowed by the dock
   (UIC-13) is a phone to everything on it. */
body { container-type: inline-size; }

.ui-frame {
  display: grid;
  width: 100%;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  position: relative;
  background: var(--surface);
}

/* Every column is a flex column that can shrink: without the two `min-*`
   zeroes a long word in one of them widens the track and pushes the others
   off the screen, which is the bug every one of these three surfaces has had
   at least once. */
.ui-col {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ui-rail {
  background: var(--rail);
  border-right: 1px solid var(--line);
  /* Visible, not hidden: a rail holds popovers that must be allowed out of
     it, and it is narrow enough never to need to scroll sideways. */
  overflow: visible;
}

.ui-list {
  background: var(--surface);
  border-right: 1px solid var(--line);
  overflow-y: auto;
}

.ui-pane {
  background: var(--surface);
  overflow-y: auto;
}

/* The fourth column: what is chosen inside the pane, when that is a different
   thing from what the pane is about. On the far side, so the reading goes
   rail → list → the thing → the part of it. */
.ui-props {
  background: var(--surface);
  border-left: 1px solid var(--line);
  overflow-y: auto;
}

/* The manual, inside the thing it documents.

   Three applications drew this surface and named it three ways — `pb-doc-*`,
   `k-doc*`, `cn-doc-*` — over one arrangement. Each rule below is the union of
   the three, taking the mailbox's measure where they disagreed (UIC-8). What
   is *not* here is where the surface sits in a given frame: the head's padding
   and the body's measure are the application's, hung on its own class beside
   the tier's. */

/* ── the head: what you are reading, and the books ───────────────────────── */

.ui-doc-head {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--s8);
  padding: var(--s8) clamp(var(--s8), 3vw, var(--s11)) 0;
  border-bottom: 1px solid var(--line);
}
.ui-doc-titlerow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s9);
  flex-wrap: wrap;
}
.ui-doc-titled {
  display: flex;
  align-items: baseline;
  gap: var(--s7);
  flex-wrap: wrap;
  min-width: 0;
}
.ui-doc-title {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(var(--t9), 3vw, var(--t10));
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* Where the file lives, printed as it is spelled: a path put through
   `text-transform` is a path nobody can type. It stands beside the title,
   because it is what the title is *of*. */
.ui-doc-where {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t2);
  letter-spacing: 0.05em;
  color: var(--mute);
  overflow-wrap: anywhere;
}

.ui-doc-acts {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  min-width: 0;
}

/* A book is a word with a line under it when it is the one you are reading —
   no box, no tint. */
.ui-doc-tabs { display: flex; gap: clamp(var(--s7), 2vw, var(--s10)); flex-wrap: wrap; }
.ui-doc-tab {
  padding: 0 0 var(--s6);
  font-size: var(--t5);
  color: var(--ink3);
  white-space: nowrap;
  box-shadow: inset 0 -2px 0 transparent;
}
.ui-doc-tab:hover { color: var(--ink); }
.ui-doc-tab.is-on { color: var(--ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent); }

/* ── the document, read two ways ─────────────────────────────────────────── */

.ui-doc-body { display: flex; flex-direction: column; }
.ui-doc-md, .ui-doc-raw { max-width: 880px; }

/* A class that sets `display` outranks the browser's own rule for `hidden`,
   so everything here that is shut says so again. */
.ui-doc-raw[hidden], .ui-doc-md[hidden], .ui-doc-acts .ui-pill[hidden] { display: none; }

.ui-doc-raw {
  margin: 0;
  padding: var(--s9) var(--s10);
  border: 1px solid var(--hair);
  border-radius: var(--r4);
  background: var(--mid);
  font-family: var(--mono);
  font-size: var(--t4);
  line-height: 1.7;
  color: var(--ink2);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.ui-doc-md h1 {
  margin: 0 0 var(--s7);
  font-family: var(--serif);
  font-size: var(--t9);
  font-weight: 500;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.ui-doc-md h2 {
  margin: var(--s11) 0 var(--s6);
  font-family: var(--serif);
  font-size: var(--t8);
  font-weight: 500;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.ui-doc-md h3 {
  margin: var(--s9) 0 var(--s5);
  font-family: var(--serif);
  font-size: var(--t6);
  font-weight: 500;
  color: var(--ink);
}
.ui-doc-md p { margin: 0 0 var(--s6); font-size: var(--t5); line-height: 1.65; color: var(--ink3); }
.ui-doc-md ul, .ui-doc-md ol { margin: 0 0 var(--s7); padding-left: var(--s9); }
.ui-doc-md li { margin-bottom: var(--s3); font-size: var(--t5); line-height: 1.65; color: var(--ink3); }
.ui-doc-md strong { color: var(--ink2); font-weight: 600; }
.ui-doc-md a { color: var(--accent); }
.ui-doc-md a:hover { text-decoration: underline; }
.ui-doc-md hr { margin: var(--s9) 0; border: 0; border-top: 1px solid var(--hair); }
.ui-doc-md > :last-child { margin-bottom: 0; }
.ui-doc-md blockquote {
  margin: 0 0 var(--s7);
  padding: var(--s6) var(--s8);
  border-left: 2px solid var(--accent-line);
  background: var(--wash);
  font-size: var(--t5);
  line-height: 1.65;
  color: var(--ink2);
}
.ui-doc-md .code, .ui-doc-md code {
  padding: var(--s1) var(--s2);
  border-radius: var(--r2);
  background: var(--hover);
  font-family: var(--mono);
  font-size: var(--t3);
  color: var(--ink2);
}
.ui-doc-md .md-code {
  margin: 0 0 var(--s8);
  padding: var(--s6) var(--s8);
  border: 1px solid var(--hair);
  border-radius: var(--r4);
  background: var(--mid);
  font-family: var(--mono);
  font-size: var(--t3);
  line-height: 1.75;
  color: var(--doc-code);
  overflow-x: auto;
}
.ui-doc-md .md-code code { padding: 0; background: none; font-size: inherit; }

/* A two-column reference table is rows of key and description, baseline
   aligned, the mono column at a fixed minimum width. A wider one stays a
   table. The width is on the element because the Markdown reader is what
   knows it, and the sheet is what draws it. */
.ui-doc-md .md-table {
  width: 100%;
  margin: 0 0 var(--s8);
  border: 1px solid var(--hair);
  border-radius: var(--r4);
  border-collapse: collapse;
  overflow: hidden;
  text-align: left;
}
.ui-doc-md .md-table th, .ui-doc-md .md-table td {
  padding: var(--s5) var(--s7);
  border-bottom: 1px solid var(--hair);
  vertical-align: baseline;
  font-size: var(--t4);
  line-height: 1.5;
  color: var(--ink3);
}
.ui-doc-md .md-table th {
  font-family: var(--mono);
  font-size: var(--t1);
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}
.ui-doc-md .md-table tr:last-child td { border-bottom: 0; }
.ui-doc-md .md-table[data-cols="2"],
.ui-doc-md .md-table[data-cols="2"] thead,
.ui-doc-md .md-table[data-cols="2"] tbody { display: block; }
.ui-doc-md .md-table[data-cols="2"] tr {
  display: flex;
  align-items: baseline;
  gap: var(--s8);
  padding: var(--s5) var(--s7);
  border-bottom: 1px solid var(--hair);
}
.ui-doc-md .md-table[data-cols="2"] tr:last-child { border-bottom: 0; }
.ui-doc-md .md-table[data-cols="2"] th,
.ui-doc-md .md-table[data-cols="2"] td { padding: 0; border-bottom: 0; }
.ui-doc-md .md-table[data-cols="2"] th:first-child,
.ui-doc-md .md-table[data-cols="2"] td:first-child { flex: none; min-width: 246px; }
.ui-doc-md .md-table[data-cols="2"] td:first-child {
  font-family: var(--mono);
  font-size: var(--t3);
  color: var(--doc-key);
}
.ui-doc-md .md-table[data-cols="2"] th:last-child,
.ui-doc-md .md-table[data-cols="2"] td:last-child { flex: 1; min-width: 0; }

/* ── the pages of a book ─────────────────────────────────────────────────── */

.ui-doc-pills {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s7);
}
/* A page is a place, not a link: outlined until it is the open one. */
.ui-doc-pill {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: var(--t4);
  color: var(--ink3);
  white-space: nowrap;
}
.ui-doc-pill:hover { border-color: var(--accent); color: var(--accent); }
.ui-doc-pill.is-on { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent); }

.ui-doc-section {
  margin: var(--s4) 0 var(--s8);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--hair);
  font-family: var(--mono);
  font-size: var(--t2);
  letter-spacing: 0.12em;
  color: var(--mute);
}

/* ── the Test panel ──────────────────────────────────────────────────────── */

.ui-doc-test { max-width: 880px; margin-bottom: var(--s10); }
.ui-doc-strip {
  height: 36px;
  padding: 0 var(--s8);
  border: 1px dashed var(--line2);
  border-radius: var(--r-pill);
  background: var(--surface);
  display: inline-flex;
  align-items: center;
  gap: var(--s4);
  align-self: flex-start;
  cursor: pointer;
  color: var(--ink3);
  font-family: var(--sans);
  font-size: var(--t4);
}
.ui-doc-strip:hover { border-color: var(--accent); color: var(--accent); }
.ui-doc-strip[hidden], .ui-doc-card[hidden], .ui-doc-x-body[hidden] { display: none; }

.ui-doc-card {
  border: 1px solid var(--line);
  border-radius: var(--r6);
  background: var(--surface);
  box-shadow: var(--card);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ui-doc-card-head {
  flex: none;
  padding: var(--s5) var(--s6) var(--s5) var(--s8);
  border-bottom: 1px solid var(--hair);
  display: flex;
  align-items: center;
  gap: var(--s6);
  flex-wrap: wrap;
}
.ui-doc-card-head p {
  margin: 0;
  flex: 1;
  min-width: 0;
  font-size: var(--t4);
  line-height: 1.45;
  color: var(--ink3);
}
.ui-doc-card-body { display: flex; gap: var(--s9); padding: var(--s8); flex-wrap: wrap; }
.ui-doc-ask {
  flex: none;
  width: 320px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--s7);
}
.ui-doc-said { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: var(--s3); }
.ui-doc-field { display: flex; flex-direction: column; gap: var(--s3); }
.ui-doc-in {
  width: 100%;
  height: 36px;
  padding: 0 var(--s5);
  border: 1px solid var(--line2);
  border-radius: var(--r4);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--t4);
}
.ui-doc-in:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
textarea.ui-doc-in { height: auto; padding: var(--s5); line-height: 1.6; resize: vertical; }

/* The label a reference wears over a box: a word, in the mono face, quiet. */
.ui-doc-label {
  font-family: var(--mono);
  font-size: var(--t2);
  letter-spacing: 0.08em;
  color: var(--mute);
  text-transform: uppercase;
}
.ui-doc-empty { margin: 0; padding: var(--s3) 0; font-size: var(--t4); color: var(--mute); }

/* ── one exchange: what was asked, and what came back ────────────────────── */

.ui-doc-xs { display: flex; flex-direction: column; gap: var(--s4); }
.ui-doc-x { border: 1px solid var(--hair); border-radius: var(--r4); overflow: hidden; }
.ui-doc-x-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border: 0;
  background: var(--rail);
  cursor: pointer;
  text-align: left;
  font-family: var(--sans);
}
.ui-doc-x-head:hover { background: var(--hover); }
.ui-doc-x-chev { font-family: var(--mono); font-size: var(--t2); color: var(--mute); }
.ui-doc-x-title {
  font-family: var(--mono);
  font-size: var(--t3);
  color: var(--doc-key);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ui-doc-x-part { padding: var(--s4) var(--s5); border-top: 1px solid var(--hair); }
.ui-doc-x-part.is-in { background: var(--mid); }
.ui-doc-x-tag { font-family: var(--mono); font-size: var(--t1); letter-spacing: 0.1em; padding-bottom: var(--s2); }
.ui-doc-x-tag.is-out { color: var(--accent-down); }
.ui-doc-x-tag.is-in { color: var(--ok-ink); }
.ui-doc-x-text {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t3);
  line-height: 1.6;
  color: var(--doc-code);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 320px;
  overflow: auto;
}
/* The one row the panel adds after a gated write: the way to the form,
   offered rather than opened — a window after an await is a popup. */
.ui-doc-x-part .ui-doc-x-open { margin-top: var(--s1); }

/* ── on a phone ──────────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  /* A reference row stacks: 246px of mono key beside a description on a 360px
     screen leaves the description a column of single words. */
  .ui-doc-md .md-table[data-cols="2"] tr { display: block; }
  .ui-doc-md .md-table[data-cols="2"] th:first-child,
  .ui-doc-md .md-table[data-cols="2"] td:first-child { min-width: 0; padding-bottom: var(--s2); }
  /* A wider table is the one thing in the manual that cannot reflow, so it
     scrolls inside itself rather than widening the page. */
  .ui-doc-md .md-table { display: block; overflow-x: auto; }

  .ui-doc-ask { width: 100%; }

  /* A 28px pill and a 24px tab are a pointer's targets, not a finger's. The
     tab keeps its underline at the bottom of the box, so the height it gains
     goes above the word. */
  .ui-doc-acts .ui-pill, .ui-doc-ask .ui-pill, .ui-doc-pill { min-height: 44px; }
  .ui-doc-tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
    padding: var(--s6) var(--s2);
  }
}

/* The dock (UIC-13, UIC-14): a surface that opens beside the page rather than
   over it, on the house tokens and defining nothing of its own but its width.

   On a desk the page moves aside and both are readable at once — which is the
   point of asking something while looking at the thing you are asking about.
   On a phone there is no beside, so it takes the screen.

   Three shares, not open and shut (UIC-14): the page, half each, and the
   whole screen. The whole contract with the page is one attribute on `<html>`,
   which carries which share it is. A surface's own sheet can join in —
   narrowing a map, moving a sticky bar — by matching `[data-dock="half"]`
   without knowing the dock's script exists. */

:root {
  /* A knob, not a token: how wide the dock is at the share it is on, which a
     surface may turn down. The script writes this one as pixels when the
     reader has dragged the boundary (UIC-15). */
  --ui-dock-w: 0px;
  /* Neither side goes below this. A column narrower than this is one nobody
     reads — the drag stops here rather than squeezing a half out of use, and
     a window with no room for two of them is not draggable at all. */
  --ui-dock-min: 420px;
}

/* Beside the page the dock opens at the floor — a column, not half the
   window — and the reader drags it wider from there (UIC-15); a dragged
   width is written over this one as pixels. Full is the screen. */
[data-dock="half"] { --ui-dock-w: var(--ui-dock-min); }
[data-dock="full"] { --ui-dock-w: 100%; }

/* ── the handle on the rim the panel comes out of ────────────────────────── */

/* A tab against the right edge, not a button floating in the corner: an arrow
   pointing the way the panel will travel and the two letters saying what is
   behind it. Small and quiet — everything it covers is something somebody
   came for. It sits below the header band every surface keeps its own face
   in, which is the knob a surface turns if its own is taller. */
.ui-dock-handle {
  position: fixed;
  top: var(--ui-dock-handle-top, 76px);
  right: 0;
  z-index: 61;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) var(--s4) var(--s3) var(--s3);
  border: 1px solid var(--line);
  border-right: 0;
  border-radius: var(--r4) 0 0 var(--r4);
  background: var(--surface);
  color: var(--mute);
  font-family: var(--mono);
  font-size: var(--t2);
  font-weight: 600;
  letter-spacing: .06em;
  cursor: pointer;
  box-shadow: var(--float);
  transition: color .2s, background .2s, padding-right .2s;
}
.ui-dock-handle:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  padding-right: var(--s6);
}
.ui-dock-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ui-dock-handle[hidden] { display: none; }
.ui-dock-handle-arrow { flex: none; }
.ui-dock-handle-word { line-height: 1; }

/* ── and the same again as a row in the surface's own menu ───────────────── */

/* Nothing floats over the page. The dock is reached from wherever that
   surface already keeps "Assistant", so the control sits among the menu's
   other rows and inherits their shape: the class the surface passes does the
   dressing, and these rules only lay the row out and place the share on the
   right of it, where a value belongs in a list of settings. */
.ui-dock-toggle {
  display: flex;
  align-items: center;
  gap: var(--s5);
  width: 100%;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ui-dock-toggle-word { flex: 1; min-width: 0; }

/* ── the panel ───────────────────────────────────────────────────────────── */

.ui-dock {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 60;
  width: var(--ui-dock-w);
  height: 100dvh;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface);
  /* The hairline is the edge; no shadow (2026-09-07: *"dont use drop shadow
     for the assistant"*) — the dock is a column beside the page, not a
     sheet lying on it. */
  border-left: 1px solid var(--line);
}
/* Only once the share has settled, and never under a hand (UIC-15): a page
   that draws itself already open would otherwise slide out on every
   navigation, and an animated width chases a dragging pointer rather than
   following it. */
[data-dock-ready]:not([data-dock-drag]) .ui-dock {
  transition: width .28s cubic-bezier(.4, 0, .2, 1);
}

/* ── the boundary, dragged ──────────────────────────────────────────────── */

/* The line itself, on the panel's own edge: a hit area wide enough for a
   hand, drawn as nothing until it is reached for. The two arrows in the head
   move the same boundary in steps; this is it dragged. */
.ui-dock-grip {
  position: absolute;
  top: 0;
  /* Twelve pixels, half of them over the page: a boundary somebody has to
     aim at is one they miss, and a hand is not a pixel. */
  left: -6px;
  width: 12px;
  height: 100%;
  z-index: 1;
  cursor: col-resize;
  touch-action: none;
  background: none;
}
.ui-dock-grip:hover, .ui-dock-grip:focus-visible {
  background: var(--accent);
  outline: none;
}
/* Nothing to drag when the panel is the screen, or when the screen has no
   room for two readable halves. */
[data-dock="full"] .ui-dock-grip { display: none; }
@media (max-width: 839px) {
  .ui-dock-grip { display: none; }
}

/* A shut dock is shut. `display: flex` above is an author rule and the
   browser's own `[hidden] { display: none }` is not, so without this line the
   panel stays on the screen with the attribute set — covering the page it is
   supposed to sit beside. */
.ui-dock[hidden] { display: none; }

.ui-dock-head {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: var(--s5);
  padding: var(--s8) var(--s8) var(--s6);
  border-bottom: 1px solid var(--hair);
}
.ui-dock-titled { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s2); }
/* The name, and whatever small thing the filling keeps beside it. A row, so
   the two sit on one line; the name may shrink and the small thing may not,
   because a picker that has lost its own width is one nobody can read. */
.ui-dock-name { display: flex; align-items: baseline; gap: var(--s3); min-width: 0; }
.ui-dock-title { margin: 0; font-size: var(--t6); font-weight: 600; color: var(--ink); }
.ui-dock-said { margin: 0; font-size: var(--t4); line-height: var(--lh4); color: var(--mute); }

/* The boundary, with a hand on each side of it. Two small arrows, together,
   so the pair reads as one control with two directions rather than as two
   unrelated buttons that happen to sit next to each other. */
.ui-dock-edge { flex: none; display: flex; align-items: center; gap: var(--s1); }
/* The two presses in the corner (UIC-21): the whole screen, and shut. The
   first is a switch and shows it — pressed, it is the way back beside the
   page. */
.ui-dock-full,
.ui-dock-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s11);
  height: var(--s11);
  border: 0;
  border-radius: var(--r3);
  background: none;
  color: var(--mute);
  cursor: pointer;
}
.ui-dock-full svg, .ui-dock-close svg { width: 16px; height: 16px; }
.ui-dock-full:hover,
.ui-dock-close:hover { background: var(--hover); color: var(--ink); }
.ui-dock-full[aria-pressed="true"] { background: var(--hover); color: var(--ink); }
/* A press the filling keeps beside its own name wears the head's manner,
   like the two in the corner (2026-09-09): their size, no ring and no ground
   until the hand is on it, and centred on the name rather than hung from
   its baseline — a ringed circle beside a title read as a badge, not as a
   press. */
.ui-dock-name .ui-round {
  --ui-round-size: var(--s11);
  align-self: center;
  border-color: transparent;
  background: none;
  color: var(--mute);
  box-shadow: none;
}
.ui-dock-name .ui-round:hover,
.ui-dock-name .ui-round:focus-visible {
  border-color: transparent;
  background: var(--hover);
  color: var(--ink);
  box-shadow: none;
}

/* ── what was said before ────────────────────────────────────────────────── */

/* Top left, where a list of what there is belongs — the same corner the
   console, the mailbox and the portal all keep their rail in. */
.ui-dock-past-open {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s11);
  height: var(--s11);
  margin-top: calc(var(--s1) * -1);
  border: 0;
  border-radius: var(--r3);
  background: none;
  color: var(--mute);
  cursor: pointer;
}
.ui-dock-past-open:hover { background: var(--hover); color: var(--ink); }
.ui-dock-past-open[aria-expanded="true"] { background: var(--hover); color: var(--ink); }
/* The filling's own press beside it (UIC-21): the same quiet square, so the
   corner reads as one row of two — the list, and a fresh one. */
.ui-dock-fresh {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s11);
  height: var(--s11);
  margin-top: calc(var(--s1) * -1);
  border: 0;
  border-radius: var(--r3);
  background: none;
  color: var(--mute);
  cursor: pointer;
}
.ui-dock-fresh:hover { background: var(--hover); color: var(--ink); }
.ui-dock-fresh .icon { width: var(--s5); height: var(--s5); }

/* Bounded, and the conversation keeps the rest: a list that pushed the
   composer off the bottom is one nobody opens twice. */
.ui-dock-past {
  flex: none;
  max-height: 50dvh;
  overflow-y: auto;
  padding: var(--s5) var(--s8) var(--s6);
  background: var(--page);
  border-bottom: 1px solid var(--line);
}
.ui-dock-past[hidden] { display: none; }
.ui-dock-past-head {
  margin: 0 0 var(--s4);
  font-size: var(--t3);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--faint);
}
.ui-dock-past-body { display: flex; flex-direction: column; gap: var(--s1); }

.ui-dock-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--s8); }
.ui-dock-foot { flex: none; padding: var(--s6) var(--s8) var(--s8); border-top: 1px solid var(--hair); }


/* ── the page moves aside ────────────────────────────────────────────────── */

/* On the padding rather than on a transform: a transform would move the page
   without resizing it, so a column that scrolls would keep the width it had
   and hide its right-hand edge under the dock. The frame is `width: 100%`, so
   padding on the body is what narrows it.

   Nothing is done to the body while the dock is shut, and that is deliberate:
   a `scrollbar-gutter` here would have been a quiet fifteen pixels off the
   width of every page of every surface, dock or no dock, which is the sort of
   thing a shared sheet must not do. */
[data-dock="half"] body { padding-right: var(--ui-dock-w); }

/* At full there is nothing to narrow: padding the body by its own width would
   collapse the page rather than move it, so the panel simply covers it and
   the page underneath stops scrolling. */
[data-dock="full"] body { overflow: hidden; }

/* Armed one frame after the dock settles, and dropped while the boundary is
   under a hand: a page that arrives already narrowed has not *moved*, and
   animating it on arrival is the twitch on every navigation and every redraw
   (UIC-15). */
@media (prefers-reduced-motion: no-preference) {
  [data-dock-ready]:not([data-dock-drag]) body {
    transition: padding-right .28s cubic-bezier(.4, 0, .2, 1);
  }
}

/* ── a phone has no beside ───────────────────────────────────────────────── */

@media (max-width: 640px) {
  /* Both open shares are the screen: half of a phone is not a column anybody
     can read. */
  [data-dock="half"] { --ui-dock-w: 100%; }
  .ui-dock { border-left: 0; }
  /* Nothing moves: the dock is the screen, and padding the body would leave
     the page scrolled sideways underneath it. */
  [data-dock="half"] body { padding-right: 0; overflow: hidden; }
}

/* ── the dock as the page (UIC-22) ───────────────────────────────────────── */

/* Where the dock *is* the page (`ai.<base>`) it stands in the flow instead of
   fixed beside it: nothing to move aside, no share to walk. The head, the
   fold, the body and the foot are the same four things, laid out as columns
   where the window has room — the fold on the left as the list of what there
   is, the foot on the right as what the assistant can reach — and stacked on
   a phone, the fold covering the conversation when it is asked for, as it
   does in the panel. The page hands it the height it has to fill. */
[data-dock="page"] body { padding-right: 0; }
.ui-dock.is-page {
  position: relative;
  top: auto;
  right: auto;
  z-index: auto;
  width: 100%;
  height: 100%;
  min-height: 0;
  border-left: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas: "head" "body" "foot";
}
.ui-dock.is-page > .ui-dock-head { grid-area: head; }
.ui-dock.is-page > .ui-dock-body { grid-area: body; }
.ui-dock.is-page > .ui-dock-foot { grid-area: foot; }
/* The fold covers the conversation from the head down until a row is chosen
   — the same cover it is in the panel, drawn over the body's area. */
.ui-dock.is-page > .ui-dock-past {
  grid-area: body;
  z-index: 2;
  align-self: start;
  max-height: 100%;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 840px) {
  /* Two columns: the fold stands as the list on the left, always, and the
     press that opened it as a cover is put away. A dock with nothing behind
     it keeps the one column. */
  .ui-dock.is-page:has(> .ui-dock-past) {
    grid-template-columns: 280px minmax(0, 1fr);
    grid-template-areas: "past head" "past body" "past foot";
  }
  .ui-dock.is-page > .ui-dock-past,
  .ui-dock.is-page > .ui-dock-past[hidden] {
    display: block;
    grid-area: past;
    z-index: auto;
    align-self: stretch;
    max-height: none;
    overflow-y: auto;
    border-bottom: 0;
    border-right: 1px solid var(--line);
  }
  .ui-dock.is-page > .ui-dock-head > .ui-dock-past-open { display: none; }
}
@media (min-width: 1200px) {
  /* Three: and the foot — what the assistant can reach — as a column on the
     right, beside the conversation rather than under it. */
  .ui-dock.is-page {
    grid-template-columns: minmax(0, 1fr) 320px;
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas: "head foot" "body foot";
  }
  .ui-dock.is-page:has(> .ui-dock-past) {
    grid-template-columns: 280px minmax(0, 1fr) 320px;
    grid-template-areas: "past head foot" "past body foot";
  }
  .ui-dock.is-page > .ui-dock-foot {
    border-top: 0;
    border-left: 1px solid var(--line);
    overflow-y: auto;
  }
}

/* The gallery (UIC-20, out of KODU-69): one strip that snaps a picture at a
   time, the arrows and the count over it, the thumbnails and the captions
   under it, and the same strip filling the screen in a <dialog>. Scroll-snap
   does the leafing, so a finger, a wheel and the keyboard all work before any
   script does; the script adds the arrows, the jump from a thumbnail, the
   count and the full-screen view. */

/* Never wider than the column it stands in: a caption that cannot break —
   an address, say — would otherwise widen the album to its own length and
   push the picture past the screen's edge, and a strip turning to a longer
   caption jumped wider on every turn (2026-09-07, on a phone). */
.ui-gallery { min-width: 0; max-width: 100%; }
.ui-gal-main {
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 480px;
  background: var(--chip);
}
.ui-gal-track, .ui-light-track {
  display: flex;
  width: 100%;
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.ui-gal-track::-webkit-scrollbar, .ui-light-track::-webkit-scrollbar, .ui-gal-thumbs::-webkit-scrollbar { display: none; }
.ui-gal-track:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ui-gal-shot {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  cursor: zoom-in;
}
.ui-gal-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The arrows over the picture: shown when the pointer is over it, always on a
   screen with no pointer. */
.ui-gal-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: color-mix(in oklab, var(--surface) 88%, transparent);
  color: var(--ink);
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s ease, border-color .15s ease, color .15s ease;
}
.ui-gal-arrow.is-prev { left: var(--s3); }
.ui-gal-arrow.is-next { right: var(--s3); }
.ui-gal-arrow svg { width: 18px; height: 18px; }
.ui-gal-arrow:hover { border-color: var(--accent); color: var(--accent); }
.ui-gal-main:hover .ui-gal-arrow, .ui-gal-arrow:focus-visible { opacity: 1; }
@media (hover: none) { .ui-gal-arrow { opacity: 1; } }

/* Full screen, with the count on it: one press for "see it big" and "which
   one am I on", in the corner a thumb reaches. */
.ui-gal-full {
  position: absolute;
  right: var(--s3);
  bottom: var(--s3);
  display: flex;
  align-items: center;
  gap: var(--s2);
  height: 32px;
  padding: 0 var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--surface) 88%, transparent);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}
.ui-gal-full svg { width: 16px; height: 16px; }
.ui-gal-full:hover { border-color: var(--accent); color: var(--accent); }
.ui-gal-count, .ui-light-count {
  font-family: var(--mono);
  font-size: var(--t3);
  font-variant-numeric: tabular-nums;
}

/* One caption under the strip, the one for the picture in view; the rest
   are there for a reader with no script, who sees the first alone. */
/* `contain: inline-size`: the row's own width is the album's, never its
   words' — a caption's minimum width is otherwise its longest unbreakable
   run, and that is what an address is. */
.ui-gal-captions { margin: 0; min-height: 1.5em; min-width: 0; contain: inline-size; overflow-wrap: anywhere; }
.ui-gal-caption { display: none; margin: 0; color: var(--ink2); font-size: var(--t4); line-height: 1.5; text-wrap: pretty; }
.ui-gal-caption.is-on { display: block; }

.ui-gal-thumbs {
  display: flex;
  gap: var(--s2);
  overflow-x: auto;
  scrollbar-width: none;
}
/* The ring is an inset outline rather than a shadow around the box: the row
   clips nothing that way, and it needs no room kept for it. */
.ui-gal-thumb {
  flex: none;
  width: 84px;
  aspect-ratio: 3 / 2;
  border-radius: var(--r4);
  overflow: hidden;
  opacity: .7;
  outline: 2px solid transparent;
  outline-offset: -2px;
  transition: opacity .15s ease, outline-color .15s ease;
}
.ui-gal-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ui-gal-thumb:hover { opacity: 1; }
.ui-gal-thumb.is-on { opacity: 1; outline-color: var(--accent); }

/* The dots, for a strip that turns by itself: one a picture, the one in view
   filled and a touch larger, each a press to its picture. */
.ui-gal-dots { display: flex; justify-content: center; gap: var(--s3); padding: var(--s2) 0; }
.ui-gal-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--line);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.ui-gal-dot:hover { background: var(--ink3); }
.ui-gal-dot.is-on { background: var(--accent); transform: scale(1.25); }

/* Until the script is there, the controls it serves are not either: the
   strip scrolls on its own and every picture is a link to its file, and a
   dot that leads nowhere is not drawn. */
.ui-gallery:not(.is-live) .ui-gal-arrow, .ui-gallery:not(.is-live) .ui-gal-full, .ui-gallery:not(.is-live) .ui-gal-dots { display: none; }

/* Drawings rather than photographs: contained on paper, not cropped to the
   frame — in the strip, in the thumbnails and on the dark full screen alike,
   since white paper is what they were drawn on whichever scheme the page is
   in. */
.ui-gallery.is-paper .ui-gal-shot img, .ui-gallery.is-paper .ui-gal-thumb img { object-fit: contain; background: var(--paper); }
.ui-gallery.is-paper .ui-gal-shot img { padding: var(--s4); }
.ui-gallery.is-paper .ui-light-shot { background: var(--paper); padding: var(--s6); }

/* The full-screen view: the whole viewport, the picture contained in it, the
   same arrows in the stage's ink, the count at the foot. The stage is a warm
   near-black in both schemes — the picture is the light there. */
.ui-light {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--stage);
  color: var(--stage-ink);
}
.ui-light::backdrop { background: var(--stage); }
.ui-light-shot {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: contain;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  /* A press closes it, the way a press opened it. */
  cursor: zoom-out;
}
.ui-light .ui-gal-arrow, .ui-light-close {
  opacity: 1;
  border-color: var(--stage-line);
  background: var(--stage-veil);
  color: var(--stage-ink);
}
.ui-light .ui-gal-arrow:hover, .ui-light-close:hover { border-color: var(--stage-ink); color: var(--stage-ink); }
.ui-light-close {
  position: absolute;
  top: var(--s3);
  right: var(--s3);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid;
  border-radius: 50%;
  cursor: pointer;
}
.ui-light-close svg { width: 20px; height: 20px; }
.ui-light-count {
  position: absolute;
  left: 50%;
  bottom: var(--s4);
  transform: translateX(-50%);
  padding: var(--s1) var(--s3);
  border-radius: var(--r-pill);
  background: var(--stage-veil);
}
html:has(.ui-light[open]) { overflow: hidden; }
@media (prefers-reduced-motion: reduce) { .ui-gal-track, .ui-light-track { scroll-behavior: auto; } }

/* ── the editor's manner (KODU-110, the tier's since 2026-09-09) ─────────── */

/* A section: a rule over it, its number and name down the left in the
   serif, its controls to the right. The same drawing Kodu's *Lisa kuulutus*
   makes, so a settings surface on any door reads as one of that family.
   No box and no ground: the rule is what separates one from the next. */
.ui-part {
  display: grid;
  grid-template-columns: minmax(0, 180px) minmax(0, 1fr);
  gap: var(--s8) clamp(var(--s10), 3vw, var(--s13));
  padding-block: clamp(var(--s9), 2.4vw, var(--s11));
  border-top: 1px solid var(--line2);
}
.ui-part:first-child { padding-top: var(--s4); border-top: 0; }
.ui-part-no {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s3);
  margin: 0;
  font-family: var(--serif);
  font-size: var(--t7);
  font-weight: 500;
  line-height: 1.2;
  color: var(--ink);
}
.ui-part-n {
  font-family: var(--mono);
  font-size: var(--t3);
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--mute);
}
/* The foot of the column: what one press will do, and the press. */
.ui-sheet-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
  flex-wrap: wrap;
  padding-top: clamp(var(--s9), 2.4vw, var(--s11));
  border-top: 1px solid var(--line2);
}
/* A phone reads the name over its controls rather than beside them. */
@media (max-width: 640px) {
  .ui-part { grid-template-columns: minmax(0, 1fr); gap: var(--s5); }
}

/* The storefront's stylesheet and the shared controls, served at /theme.css.

   The palette and the type it names are theme::tokens, which the console and
   the mail app wear too (UI-17). Nothing here defines a colour of its own: a
   rule that reached past a token would be the one surface that stops following
   the design. The section markers below are what make it navigable. */

/* ── reset + base ───────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--body-size, 16px);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; color: inherit; }
h1, h2, h3, p { margin: 0; }
code, .mono-num, .code { font-family: var(--mono); }

/* ── layout ─────────────────────────────────────────────────────────────── */

.container { width: 100%; max-width: var(--container, 1360px); margin-inline: auto; padding-inline: 1.75rem; }
.container-narrow { width: 100%; max-width: 720px; margin-inline: auto; padding-inline: 1.75rem; }
.container-tight { width: 100%; max-width: 420px; margin-inline: auto; padding-inline: 1.75rem; }
/* A phone's sign-in is mostly card: the desktop's 4rem of vertical air and
   1.75rem gutters ate a third of an iPhone mini's screen (2026-09-01). */
@media (max-width: 480px) {
  .container-tight { padding-inline: 1rem; }
  .center-page, .center-page-start { padding-block: 1.25rem; }
  .container-tight .panel { padding: 1rem; }
}
.center-page { min-height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding-block: 3rem; }
.center-page-start { min-height: 100%; display: flex; flex-direction: column; justify-content: center; padding-block: 4rem; }

.header {
  position: sticky; top: 0; z-index: 10;
  background: color-mix(in oklab, var(--surface) var(--nav-alpha, 85%), transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  max-width: var(--nav-max, none); margin-inline: auto;
}
.header-inner { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding-block: 1.25rem; }
.brand-group { display: flex; align-items: center; gap: .75rem; min-width: 0; }
.brand { font-weight: 600; letter-spacing: -0.01em; font-size: 1rem; }
/* The company's mark beside its name, pressed as one thing wherever comm.ee
   is written. */
.brand-home { display: inline-flex; align-items: center; gap: .45em; color: inherit; text-decoration: none; }
.brand-mark { display: inline-flex; width: 1.15em; height: 1.15em; }
.brand-mark svg { width: 100%; height: 100%; }
.brand-logo { display: block; height: 1.6rem; width: auto; max-width: 12rem; object-fit: contain; }
/* Only the owner sees it, so it is quiet until reached for. */
.console-link { --ui-round-size: 2rem; opacity: .55; transition: opacity .2s; }
.console-link:hover, .console-link:focus-visible { opacity: 1; }
.nav { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
.nav a { color: var(--ink3); font-size: 0.875rem; transition: color .15s; }
.nav a:hover { color: var(--ink); }
.nav-links, .nav-extras { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }

/* Past a handful of pages the rest wait under "More" — a sheet, not a wall. */
.nav-more { position: relative; }
.nav-more summary {
  list-style: none; cursor: pointer; color: var(--ink3); font-size: 0.875rem;
  transition: color .15s; user-select: none;
}
.nav-more summary::-webkit-details-marker { display: none; }
.nav-more summary::after { content: " ▾"; font-size: 0.7em; }
.nav-more summary:hover, .nav-more[open] summary { color: var(--ink); }
.nav-more-sheet {
  position: absolute; left: 0; top: calc(100% + 0.75rem); z-index: 30;
  display: flex; flex-direction: column; gap: 0.15rem;
  min-width: 13rem; max-height: 60vh; overflow-y: auto;
  padding: 0.6rem; background: var(--surface);
  border: 1px solid var(--line); border-radius: 0.6rem;
  box-shadow: 0 12px 34px rgb(0 0 0 / 0.12);
}
.nav-more-sheet a { padding: 0.35rem 0.6rem; border-radius: 0.4rem; }
.nav-more-sheet a:hover { background: var(--field); }

/* ── the header's theme answers (THEME-9). The defaults wear no class, so an
   unthemed header is exactly the block above. ─────────────────────────────── */

.header.nav-static { position: static; }
.header.nav-noline { border-bottom-color: transparent; }
.header.navpad-snug .header-inner { padding-block: .55rem; }
.header.navpad-tall .header-inner { padding-block: 2rem; }

.header.navlinks-left .nav, .header.navlinks-center .nav { flex: 1; }
.header.navlinks-left .nav .nav-links { flex: 1; justify-content: flex-start; }
.header.navlinks-center .nav .nav-links { flex: 1; justify-content: center; }

.header.nav-beside .header-inner { justify-content: flex-start; gap: 2rem; }
.header.nav-beside .nav { flex: 1; }
.header.nav-beside .nav .nav-extras { margin-left: auto; }
.header.nav-centre .header-inner,
.header.nav-stack .header-inner { flex-direction: column; gap: .6rem; }
.header.nav-centre .header-inner { align-items: center; text-align: center; }
.header.nav-stack .header-inner { align-items: flex-start; }
.header.nav-centre .nav, .header.nav-stack .nav { width: 100%; }
.header.nav-centre .nav .nav-links { flex: 1; justify-content: center; }
.header.nav-stack .nav .nav-links { flex: 1; justify-content: flex-start; }
.header.nav-centre .nav .nav-extras, .header.nav-stack .nav .nav-extras { margin-left: auto; }

.header.navbg-ink { background: color-mix(in oklab, var(--solid) var(--nav-alpha, 85%), transparent); color: var(--solid-ink); }
.header.navbg-ink .nav a { color: color-mix(in oklab, var(--solid-ink) 72%, transparent); }
.header.navbg-ink .nav a:hover, .header.navbg-ink .nav a.active { color: var(--solid-ink); }
.header.navbg-accent { background: color-mix(in oklab, var(--accent) var(--nav-alpha, 85%), transparent); color: var(--accent-ink); }
.header.navbg-accent .nav a { color: color-mix(in oklab, var(--accent-ink) 75%, transparent); }
.header.navbg-accent .nav a:hover, .header.navbg-accent .nav a.active { color: var(--accent-ink); }
.header.navbg-clear { background: transparent; backdrop-filter: none; border-bottom-color: transparent; }

.footer { margin-top: 5rem; border-top: 1px solid var(--line); padding-block: 2rem; color: var(--mute); font-size: 0.75rem; max-width: var(--footer-max, none); margin-inline: auto; }

.sb-root { max-width: var(--sections-max, none); margin-inline: auto; }

.section { margin-top: calc(3.5rem * var(--space, 1)); }
.section-tight { margin-top: calc(2.5rem * var(--space, 1)); }

/* ── type ───────────────────────────────────────────────────────────────── */

/* An eyebrow is a label on a machine reading, so it is set in the mono face
   at the size the design gives it, not in small caps of the running text. */
.eyebrow { font-family: var(--mono); text-transform: uppercase; letter-spacing: .08em; font-size: .65rem; color: var(--mute); }
/* Headings are the serif, running text is the sans. The pairing is the whole
   look — a page set entirely in one of them reads as a form. */
.h1 { font-family: var(--serif); font-size: clamp(1.75rem, 3.5vw, var(--h1-max, 2.5rem)); font-weight: 500; letter-spacing: -0.02em; line-height: 1.15; }
.h2 { font-family: var(--serif); font-size: calc(var(--h1-max, 2.5rem) / 2); font-weight: 500; letter-spacing: -0.015em; }
.h3 { font-size: .95rem; font-weight: 600; letter-spacing: -0.01em; }
.lede { margin-top: .75rem; color: var(--ink3); max-width: 42rem; }
.muted { color: var(--ink3); }
.faint { color: var(--mute); font-size: .8rem; }
.tiny { font-size: .7rem; }
.mono-num { font-variant-numeric: tabular-nums; }
.link { color: var(--accent); text-decoration: underline; text-underline-offset: .2em; }
.link:hover { opacity: .8; }
.code { background: var(--field); border-radius: 5px; padding: .1rem .4rem; font-size: .85em; }

/* ── spacing helpers ────────────────────────────────────────────────────── */

.mt-1 { margin-top: .25rem; } .mt-2 { margin-top: .5rem; } .mt-3 { margin-top: .75rem; }
.mt-4 { margin-top: 1rem; } .mt-5 { margin-top: 1.25rem; } .mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; } .mt-10 { margin-top: 2.5rem; }
.mb-4 { margin-bottom: 1rem; } .mb-6 { margin-bottom: 1.5rem; }

/* ── grid / cards ───────────────────────────────────────────────────────── */

.grid-products { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
@media (min-width: 640px) { .grid-products { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid-products { grid-template-columns: repeat(3, 1fr); } }

.grid-2 { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
@media (min-width: 640px) { .grid-2 { grid-template-columns: repeat(2, 1fr); } }

.card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  background: var(--surface);
  transition: border-color .15s;
}
.card:hover { border-color: var(--mute); }
.card-raised { background: var(--field); box-shadow: var(--shadow); }

.card-media { aspect-ratio: 4 / 3; width: 100%; border-radius: var(--radius-img, calc(var(--radius) - 3px)); background: var(--field); object-fit: cover; }
.card-media-empty { aspect-ratio: 4 / 3; width: 100%; border-radius: var(--radius-img, calc(var(--radius) - 3px)); background: var(--field); display: flex; align-items: center; justify-content: center; color: var(--mute); font-size: .75rem; }

.price { margin-top: .75rem; font-weight: 600; }

.empty { padding: 4rem 0; text-align: center; color: var(--mute); font-size: .875rem; }
.empty-box { border: 1px dashed var(--line); border-radius: var(--radius); padding: 3.5rem 1.5rem; text-align: center; }

.cards-stack > * + * { margin-top: var(--gap, 1.25rem); }

/* ── forms ──────────────────────────────────────────────────────────────── */

.panel { border: 1px solid var(--line); border-radius: var(--radius); padding: 1.5rem; background: var(--field); box-shadow: var(--shadow); }
.form > * + * { margin-top: 1rem; }
/* The field, the label, the hint and the box are the UI tier's now (UIC-8):
   one definition of what a control looks like, served in front of this sheet
   and shared with the mailbox, the portal and the front page. What is left
   here is the storefront's own, which is themed by the customer and not by
   us (UI-42). */
.ui-label { display: block; }

/* The buttons are the UI tier's now (UIC-8, UIC-10) — `.ui-btn`, `.ui-round`,
   `.ui-pill`, `.ui-submit`, `.ui-modal`, `.ui-text-btn`, served in front of
   this sheet. What was `.btn`/`.btn-primary`/`.rb` here is one definition
   there, and this sheet's remaining rules point at the new names. */

/* ── badges / pills / alerts ────────────────────────────────────────────── */

.badge { display: inline-flex; align-items: center; border-radius: 999px; padding: .1rem .55rem; font-size: .65rem; font-weight: 600; letter-spacing: .02em; }
.badge-neutral { background: var(--chip); color: var(--ink3); }
.badge-warning { background: var(--warn-bg); color: var(--warn-ink); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }
.badge-success { background: var(--star-soft); color: var(--star); }
.badge-info { background: var(--info-soft); color: var(--info); }
.badge-primary { background: var(--solid); color: var(--solid-ink); }

.pill { display: inline-flex; align-items: center; gap: .4rem; border: 1px solid var(--line); border-radius: 999px; padding: .3rem .7rem; font-size: .75rem; }

.alert { padding: .65rem .9rem; border-radius: calc(var(--radius) - 3px); font-size: .85rem; }
.alert-warning { background: var(--warn-bg); color: var(--warn-ink); }
.alert-danger { background: var(--danger-soft); color: var(--danger); }
/* UI-33: the one way on out of a refusal, in the banner that states it. */
.alert a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: .2em; }

/* UI-29: what the server refused, under the control it refused. The banner is
   left for what has no field. */
.is-invalid .sel { border-color: var(--danger); }

/* ── misc components ───────────────────────────────────────────────────── */
/* A separator between two ways of signing in. It only means anything with
   something on both sides, so it lives inside the block that holds them. */
.signin-or { margin: 1rem 0; color: var(--mute); }
.link-button { border: 0; background: none; padding: 0; font: inherit; color: var(--accent); cursor: pointer; text-decoration: underline; }

.row { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; font-size: .875rem; }
.grow { flex: 1; }

.stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: .75rem; }
@media (min-width: 640px) { .stats-grid { grid-template-columns: repeat(5, 1fr); } }
.stat { border: 1px solid var(--line); border-radius: var(--radius); padding: .875rem 1rem; }
.stat-value { font-size: 1.5rem; font-weight: 600; }
.stat-label { font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink3); }

.center { text-align: center; }
.flex { display: flex; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: .5rem; }
.gap-3 { gap: .75rem; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

details > summary { cursor: pointer; list-style: none; }
details > summary::-webkit-details-marker { display: none; }

/* ── the staged picture strip ───────────────────────────────────────────── */
/*
   Shared: the product's pane form and the section's properties column both
   stage pictures the same way, and both write them only on Save (UI-20).
*/

.product-thumb { position: relative; width: 4.5rem; height: 4.5rem; }
.product-thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: 6px; background: var(--field); }
.product-thumb button {
  position: absolute; top: -.4rem; right: -.4rem;
  width: 1.35rem; height: 1.35rem; padding: 0; font-size: .7rem; line-height: 1;
  border-radius: 999px; background: var(--surface); border-color: var(--line);
}
.thumb-cover {
  position: absolute; left: 0; bottom: 0; right: 0;
  background: rgb(0 0 0 / 0.55); color: #fff; font-size: .55rem;
  text-align: center; padding: .1rem 0; border-radius: 0 0 6px 6px;
  text-transform: uppercase; letter-spacing: .06em;
}

/* The staged picture list, wherever it is staged (UI-20). It needed two class
   names to beat the floating label; the label is above the box now (UIC-8),
   so there is nothing left to beat. */
.media-strip { display: flex; flex-wrap: wrap; gap: .75rem; margin-bottom: .75rem; }

/* ── storefront: lightbox extras (thumbs, zoom) ────────────────────────── */

.lightbox-thumbs { display: flex; gap: .6rem; padding-top: .75rem; flex-wrap: wrap; }
.lightbox-thumb {
  width: 4rem; height: 4rem; object-fit: cover; border-radius: 6px;
  opacity: .5; cursor: pointer; border: 2px solid transparent;
  transition: opacity .15s, border-color .15s;
}
.lightbox-thumb:hover { opacity: .85; }
.lightbox-thumb.active { opacity: 1; border-color: var(--ink); }

/* ── storefront: chat widget ────────────────────────────────────────────── */

.chat-widget-toggle {
  position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 90;
  width: 3.25rem; height: 3.25rem; border-radius: 999px; border: none;
  background: var(--solid); color: var(--solid-ink);
  font-size: 1.3rem; cursor: pointer; box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: center;
}
.chat-widget-dot { position: absolute; top: .1rem; right: .1rem; width: .6rem; height: .6rem; border-radius: 999px; background: var(--danger); }

.chat-widget-panel {
  position: fixed; right: 1.5rem; bottom: 5.25rem; z-index: 90;
  width: min(22rem, calc(100vw - 3rem)); max-height: min(30rem, calc(100vh - 8rem));
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); overflow: hidden;
}
.chat-widget-header { padding: .75rem 1rem; border-bottom: 1px solid var(--line); font-weight: 600; font-size: .85rem; display: flex; justify-content: space-between; align-items: center; }
.chat-widget-body { flex: 1; overflow-y: auto; padding: .75rem 1rem; display: flex; flex-direction: column; gap: .5rem; min-height: 8rem; }
.chat-widget-bubble { max-width: 80%; padding: .5rem .7rem; border-radius: var(--radius); font-size: .82rem; }
.chat-widget-bubble-me { align-self: flex-end; background: var(--solid); color: var(--solid-ink); }
.chat-widget-bubble-them { align-self: flex-start; background: var(--field); }
.chat-widget-footer { border-top: 1px solid var(--line); padding: .6rem; }
.chat-widget-footer form { display: flex; gap: .4rem; }

/* ── layout slots ───────────────────────────────────────────────────────── */

/* A slot holds sections, so it needs no styling of its own beyond saying
   where it ends and the page begins. The sections inside carry their own
   arrangement and placement, exactly as a page's do (LAYOUT-2). */
.layout-slot { display: flex; flex-direction: column; }
.layout-header { margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
.layout-footer { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--line); }

/* LAYOUT-7: where the page goes, on a layout drawn on its own. It is an
   outline rather than a box with a fill, because it is the one thing on the
   page that is not there — a solid tile reads as content. */
.sb-placeholder {
  display: flex; align-items: center; justify-content: center; gap: .6rem;
  margin: 1.25rem 0; padding: 2.5rem 1rem;
  border: 2px dashed var(--line); border-radius: var(--radius);
  color: var(--mute); font-size: .85rem; letter-spacing: .01em;
}
.sb-placeholder-icon { width: 1.1rem; height: 1.1rem; }

/* Current page in the site nav. */
.nav a.active { color: var(--ink); font-weight: 600; }
.brand.active { text-decoration: none; }

/* ── gallery (masonry) ──────────────────────────────────────────────────── */

/* A grid rather than CSS columns.
   `columns` balances: with four products across three tracks it fills two and
   leaves the third empty, so a small shop rendered as a lopsided half-page.
   `auto-fill` uses every track there is room for, and `align-items: start`
   keeps each tile at its picture's own height rather than stretching it to
   the tallest in the row — which is the part of masonry worth having. */
.gallery {
  display: grid; gap: 2.5rem; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
/* A tile is an `<a>` to the product's own page (COMM-20) and behaves like one
   for anybody who asks the browser for a link — so it wears no link colour and
   no underline of its own. */
.gallery-item {
  display: block; width: 100%; position: relative;
  border: none; padding: 0; background: none; cursor: pointer;
  color: inherit; text-align: left; text-decoration: none;
}
/* A minimum height and a background so an image that has not loaded, or a
   product that has none, still occupies the space it will take — otherwise
   the masonry columns collapse and the page jumps as pictures arrive. */
.gallery-item img {
  width: 100%; height: auto; display: block; border-radius: var(--radius-img, 2px);
  min-height: 8rem; background: var(--field);
}
.gallery-empty {
  aspect-ratio: 4 / 5; border-radius: 2px; background: var(--field);
  display: flex; align-items: center; justify-content: center;
  color: var(--mute); font-size: .75rem;
}
.gallery-item .gallery-caption {
  display: flex; align-items: baseline; justify-content: space-between; gap: .75rem;
  margin-top: .6rem; font-size: .8rem; text-align: left;
}
.gallery-item .gallery-caption .name { font-weight: 600; min-width: 0; }
.gallery-item .gallery-caption .name p { display: inline; margin: 0; }
.gallery-item .gallery-caption .price { margin-top: 0; color: var(--ink3); font-weight: 400; white-space: nowrap; }

/* ── properties: the badge on a card, and the bar over a wall (COMM-22) ─── */

/* Over the picture, not under the name: what a card says about a product is
   read while the eye is still on the picture. */
.gallery-badges {
  position: absolute; top: .5rem; left: .5rem; z-index: 1;
  display: flex; flex-wrap: wrap; gap: .25rem; max-width: calc(100% - 1rem);
}
.badge-soft {
  padding: .1rem .4rem; border-radius: 999px;
  background: color-mix(in oklab, var(--surface) 88%, transparent);
  color: var(--ink2); font-size: .65rem; font-weight: 600; line-height: 1.5;
  backdrop-filter: blur(4px);
}

/* Every control is a link, so a narrowed shop is an address (COMM-22). */
.facets {
  display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.25rem;
  margin-top: 1.25rem; padding-block: .75rem;
  border-block: 1px solid var(--line);
}
.facet { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
.facet-name {
  font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
  letter-spacing: .08em; color: var(--mute); margin-right: .15rem;
}
.facet-option {
  padding: .2rem .6rem; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink3);
  font-size: .78rem; transition: border-color .15s, color .15s, background .15s;
}
.facet-option:hover { border-color: var(--mute); color: var(--ink); }
.facet-option.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.facet-count { margin-left: auto; color: var(--mute); font-size: .75rem; }
.facet-clear { margin-left: .6rem; color: var(--accent); text-decoration: underline; text-underline-offset: .2em; }
.gallery-item:hover .gallery-caption .name { text-decoration: underline; text-underline-offset: .2em; }

/* ── a wall's other pages (COMM-21) ─────────────────────────────────────── */

.sb-pager { display: flex; align-items: baseline; gap: 1.25rem; margin-top: 2.5rem; }
.sb-pager-count { color: var(--mute); font-size: .8rem; }

/* ── a product's own page (COMM-20) ─────────────────────────────────────── */

.product { display: grid; gap: 2.5rem; padding-block: 3rem; }
@media (min-width: 900px) {
  .product { grid-template-columns: 1fr 1fr; gap: 3.5rem; align-items: start; }
}
.product-shot img {
  width: 100%; height: auto; display: block;
  border-radius: var(--radius-img, 2px); background: var(--field);
}
.product-thumbs { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .75rem; }
.product-thumb {
  width: 4rem; padding: 0; background: none; cursor: pointer;
  border: 1px solid transparent; border-radius: 2px;
}
.product-thumb.active { border-color: var(--ink); }
.product-thumb img { width: 100%; height: auto; display: block; border-radius: 2px; }
.product-name p { display: inline; margin: 0; }
.product-price { font-size: 1.25rem; font-weight: 600; margin-top: 1rem; }
#product-cart-slot { margin-top: 1.5rem; }
.product-desc { margin-top: 1.5rem; color: var(--ink3); }
.product-desc p + p { margin-top: .75rem; }
.product-comments {
  max-width: 44rem; margin: 0 auto 4rem;
  padding-top: 2rem; border-top: 1px solid var(--line);
}

/* ── the blog (BLOG-2, BLOG-4) ──────────────────────────────────────────── */

.post { padding-block: 3rem; }
.post-cover { width: 100%; height: auto; display: block; border-radius: 2px; margin-bottom: 2rem; }
.post-date { margin-top: .5rem; }
.post-body { margin-top: 2rem; color: var(--ink3); }
.post-body p + p { margin-top: .75rem; }
.post-comments { padding-top: 2rem; border-top: 1px solid var(--line); margin-bottom: 4rem; }

.post-list { display: grid; gap: 2.5rem; }
@media (min-width: 700px) { .post-list { grid-template-columns: 1fr 1fr; } }
.post-card { display: grid; gap: .35rem; color: inherit; text-decoration: none; }
.post-card-cover {
  width: 100%; height: auto; display: block;
  border-radius: 2px; background: var(--field); margin-bottom: .5rem;
}
.post-card-title { font-size: 1.25rem; font-weight: 600; }
.post-card-excerpt { color: var(--ink3); }

/* ── lightbox ───────────────────────────────────────────────────────────── */

/*
   The picture first, edge to edge, and everything you decide with underneath
   it in one centred column.

   This replaced a two-column layout that put the picture in a rounded card
   beside a details panel. That is a good shape for a spec sheet and the wrong
   one for a thing that is looked at: the picture was the smaller half of the
   screen, boxed, on a page background. A band the full width of the window on
   black is the picture being the subject.

   The band is 70vh so that what is below it is *visible* — a hair of the name
   and price shows under the fold, which is what says "there is more here"
   without a scroll hint that has to be drawn.
*/
/* Specificity rather than source order, so the two z-indexes cannot be
   reordered into each other by an edit above. */
.overlay.lightbox { z-index: 100; }

.lightbox-stage {
  position: relative;
  display: flex; align-items: center;
  width: 100%; height: 70vh;
  background: #000;
  /* Horizontal, not hidden: a picture wider than the window at this height is
     scrolled to rather than cropped. Vertical stays hidden so the band cannot
     grow its own scrollbar inside the page's. */
  overflow-x: auto; overflow-y: hidden;
}
.lightbox-stage img#lightbox-image {
  height: 100%; width: auto; max-width: none; margin: auto;
  flex-shrink: 0; cursor: zoom-in;
}

/* Zoomed is the whole window — the page enters browser fullscreen at the same
   moment, so this is the height of the screen rather than of the viewport
   inside the browser's chrome. `dvh` because a phone's toolbar retracts. */
.lightbox.zoomed { overflow: hidden; }
.lightbox.zoomed .lightbox-stage { height: 100dvh; }
.lightbox.zoomed .lightbox-details,
.lightbox.zoomed .lightbox-thumbs { display: none; }
.lightbox.zoomed img#lightbox-image { cursor: zoom-out; }

.lightbox-close { position: fixed; top: 1.25rem; right: 1.25rem; z-index: 2; }
/* Faint until wanted: they sit over the picture, which is the thing being
   looked at, and a control at full strength over an image is a control in
   front of it. */
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 1;
  opacity: .3; transition: opacity .2s ease-in-out;
}
.lightbox-nav:hover, .lightbox-nav:focus-visible { opacity: 1; }
.lightbox-prev { left: .9rem; }
.lightbox-next { right: .9rem; }

.lightbox-thumbs { justify-content: center; padding-inline: 1.75rem; }

.lightbox-details {
  display: grid; gap: 1rem; justify-items: center; text-align: center;
  max-width: 44rem; margin-inline: auto;
  padding: 2rem 1.75rem 4rem;
}
.lightbox-details > * { max-width: 100%; }
/* A picture has no price and nothing to buy, and an empty grid item still
   holds its gap open — which read as a hole where the price should be. */
.lightbox-details > :empty { display: none; }
.lightbox-details .name { font-weight: 600; font-size: 1.35rem; letter-spacing: -0.01em; }
.lightbox-details .name p { margin: 0; }
.lightbox-details .price { font-size: 1.1rem; font-weight: 600; }
.lightbox-details .desc { color: var(--ink3); font-size: .9rem; }
.lightbox-details .desc p + p { margin-top: .75rem; }

/* ── comments ───────────────────────────────────────────────────────────── */
/*
   A thread reads as a conversation or it reads as a log. What made this a log
   was that every line looked the same: no faces, no thread rail, a date
   instead of "an hour ago", and a form of three bare boxes that belonged to
   some other application. The rules below are the difference.
*/

.lightbox-comments {
  margin-top: 2.5rem; padding-top: 2rem;
  border-top: 1px solid var(--line);
  /* The product view centres its column; a conversation is read from the
     left edge like any other prose. */
  text-align: left;
}
.comment-title {
  display: flex; align-items: center; gap: .5rem;
  font-size: .95rem; font-weight: 600;
}
.comment-title-icon { display: inline-flex; color: var(--mute); }
.comment-empty { margin-top: .75rem; font-size: .85rem; color: var(--mute); }

.comment { display: flex; gap: .75rem; padding-block: 1.1rem; }
.comment + .comment { border-top: 1px solid var(--line); }
.comment-main { flex: 1; min-width: 0; }

/* Nobody has a face — there are no accounts — so it is the initial, on a hue
   derived from the name. Two people down a thread are then two colours,
   which is what makes a thread skimmable at all. */
.comment-avatar {
  flex-shrink: 0; width: 2.25rem; height: 2.25rem; border-radius: 999px;
  display: grid; place-items: center;
  font-size: .85rem; font-weight: 600; line-height: 1;
  color: hsl(var(--avatar-hue, 0) 45% 30%);
  background: hsl(var(--avatar-hue, 0) 60% 90%);
}
/* The shop is not one of the visitors, so it is not one of the colours. */
.comment-avatar.is-shop {
  background: var(--ink); color: var(--surface);
}
@media (prefers-color-scheme: dark) {
  .comment-avatar { color: hsl(var(--avatar-hue, 0) 70% 85%); background: hsl(var(--avatar-hue, 0) 35% 25%); }
}

.comment-head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.comment-author { font-weight: 600; font-size: .875rem; }
.comment-when { color: var(--mute); font-size: .72rem; }
.comment-badge {
  font-size: .62rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  padding: .1rem .4rem; border-radius: 999px;
  background: var(--ink); color: var(--surface);
}
.comment-body {
  margin-top: .3rem; font-size: .9rem; line-height: 1.55;
  color: var(--ink3); white-space: pre-wrap; overflow-wrap: anywhere;
}

/* One level of indentation, because there is only one level to show
   (CMNT-1). The rail is what says these answer the comment above them. */
.comment-thread {
  margin-top: .9rem; padding-left: 1rem;
  border-left: 2px solid var(--line);
}
.comment-thread .comment { padding-block: .8rem; }
.comment-thread .comment:first-child { padding-top: 0; }
.comment-thread .comment-avatar { width: 1.75rem; height: 1.75rem; font-size: .72rem; }

.comment-reply-toggle {
  margin-top: .5rem; padding: 0; border: none; background: none; cursor: pointer;
  color: var(--mute); font-size: .75rem; font-weight: 600;
}
.comment-reply-toggle:hover { color: var(--ink); text-decoration: underline; }

.comment-form { display: grid; gap: .75rem; margin-top: 1.25rem; }
.comment-thread .comment-form, .comment .comment-form { margin-top: .75rem; }
/* Name and email side by side: they are one line of who-you-are, and stacked
   they made a three-box tower taller than the comment being answered. */
.comment-form-row { display: grid; gap: .75rem; }
@media (min-width: 560px) {
  .comment-form-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.comment-form .ui-btn { justify-self: start; }
.comment-error { color: var(--danger); font-size: .78rem; }

/* CMNT-11: what replaces the form when a comment is waiting. Not an error and
   not a success — a receipt, saying what has to happen next. */
.comment-pending {
  margin-top: 1.25rem; padding: 1rem 1.1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field); font-size: .85rem;
}
.comment-pending p { margin-top: .35rem; color: var(--ink3); line-height: 1.5; }

/* ── pictures (SECT-13) ─────────────────────────────────────────────────── */

/* A picture section is one photograph at the width it was given. It is a
   `.gallery` of one, so the tile, the hover and the lightbox are the ones
   every other picture on the site uses — but not its columns: `.gallery`
   fills a row with 15rem tracks, which turned one photograph into a thumbnail
   with the rest of the row empty beside it. */
.sb-picture { margin: 0; display: block; }
.sb-picture .gallery-item > img { width: 100%; height: auto; }
/* A picture tile has no price under it, so its caption is one line and only
   worth showing when the picture has a name of its own. */
.gallery-item.is-picture .gallery-caption { justify-content: flex-start; }
.gallery-item.is-picture .gallery-caption:empty { display: none; }

/* ── cart ───────────────────────────────────────────────────────────────── */

.cart-toggle {
  display: inline-flex; align-items: center; gap: .4rem;
  border: 1px solid var(--line); border-radius: 999px;
  padding: .4rem .8rem; font-size: .8rem; background: none; cursor: pointer; color: var(--ink);
}
.cart-toggle:hover { background: var(--field); }
.cart-count { font-variant-numeric: tabular-nums; font-weight: 600; }

/* UI-14's overlay and UI-22's question are the tier's (`ui/assets/surfaces.css`,
   2026-09-08): every application serves them with `commee_ui::STYLE`, this
   sheet included. Only the lightbox's own z-index stays here, above. */

/* COMM-17: the order, as two halves that fit on one screen — what is being
   bought, and where it is going. They stack on a phone, basket first, because
   that is the half you check before you start typing. */
.checkout {
  display: grid; gap: 2rem;
  max-width: 68rem; margin: 0 auto;
}
@media (min-width: 900px) {
  .checkout { grid-template-columns: minmax(0, 22rem) minmax(0, 1fr); align-items: start; }
}
.checkout-basket {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field); padding: 1.25rem;
}
.checkout-line {
  display: flex; gap: .75rem; align-items: flex-start;
  padding-block: .9rem; border-bottom: 1px solid var(--line);
}
.checkout-line img, .checkout-line-blank {
  width: 3.5rem; height: 3.5rem; object-fit: cover; border-radius: 4px; flex-shrink: 0;
}
.checkout-line-blank { background: var(--line); }
.checkout-line .info { flex: 1; min-width: 0; font-size: .82rem; }
.checkout-line .info .name { font-weight: 600; }
.checkout-line .line-total { font-size: .82rem; font-weight: 600; white-space: nowrap; }
.checkout-total {
  display: flex; justify-content: space-between;
  margin-top: 1.25rem; padding-top: 1rem;
  border-top: 1px solid var(--line); font-weight: 600;
}

/* Two column-fuls of form: who to reach, and where to go. One question per
   column-heading, so neither is a wall of eight identical boxes. */
.checkout-columns { display: grid; gap: 1.5rem; }
@media (min-width: 640px) {
  .checkout-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}
.checkout-column { display: grid; gap: 1rem; align-content: start; }

/* The decision, in the corner a decision belongs in. It used to sit under the
   left-hand column, level with nothing and a screen away from the total on a
   short form. Fixed to the overlay rather than the page, because the overlay
   is what is being read. */
.checkout-actions {
  position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 2;
  display: flex; justify-content: flex-end;
}
.checkout-actions .ui-btn { box-shadow: var(--shadow); }

/* CUST-2 */
.account-doors { display: grid; gap: 1.5rem; margin-top: calc(2rem * var(--space, 1)); }
@media (min-width: 640px) {
  .account-doors { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}
.account-doors > section {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field); box-shadow: var(--shadow);
  padding: 1.5rem;
}
.account-doors .eyebrow { display: block; margin-bottom: 1.25rem; }
.account-doors .form .ui-btn { margin-top: 1.5rem; }
.h1 + .alert { margin-top: 1rem; }

.qty-stepper { display: inline-flex; align-items: center; gap: .6rem; }
.qty-stepper button {
  width: 1.75rem; height: 1.75rem; border-radius: 999px; border: 1px solid var(--line);
  background: none; color: var(--ink); cursor: pointer; font-size: .9rem; line-height: 1;
}
.qty-stepper button:hover { background: var(--field); }
.qty-stepper .n { min-width: 1.25rem; text-align: center; font-variant-numeric: tabular-nums; }

/* ── icons ──────────────────────────────────────────────────────────────── */
/*
   Font Awesome Free, vendored as SVG in projects/platform/crates/web/icons and embedded at
   compile time. No icon font, no CDN, no second request — and the glyphs take
   their colour from the text they sit in.
*/

.icon {
  width: 1em; height: 1em;
  display: inline-block; vertical-align: -0.125em;
  fill: currentColor; flex-shrink: 0;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* The rounded button moved to the tier with the rest of them (UIC-10):
   `.ui-round`, its `--ui-round-size` and the label that unfurls are one
   definition in `ui/assets/controls.css`, served in front of this sheet. */

/* The floating label is gone (UIC-8, 2026-09-04). It put the label inside the
   control's border and gave every box 3.4rem of height to clear it, which was
   this product's fourth way of drawing a field. The label stands above the box
   now, the mailbox's way, and the one definition is the UI tier's. UI-5 is
   overridden; the id is kept.

   `.field.money` and the picture strip moved there with it — both were rules
   about beating the floating label, and there is nothing left to beat. */

/* ── choice lists ───────────────────────────────────────────────────────── */
/*
   A `select` is a radio list that collapses to the chosen row, not a native
   dropdown: a dropdown cannot show a tree, cannot animate, and looks like a
   different control on every platform.

   The collapse is `:has()`, so it is correct before any script runs and stays
   correct if one never does. The script only toggles `data-open`.
*/

.choice-field { position: relative; }
.choice-field > .ui-label {
  position: absolute; top: .35rem; left: .6rem; z-index: 1;
  font-size: .62rem; pointer-events: none;
}
.choice-field.has-icon > .ui-label { left: 2.4rem; }

.sel {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
  padding: 1.6rem .55rem .45rem;
  display: grid; gap: 0;
  transition: border-color .15s;
}
.sel:hover, .sel:focus-within { border-color: var(--mute); }
.choice-field.has-icon .sel { padding-left: 2.4rem; }

.sel-multi { max-height: 17rem; overflow-y: auto; }

.sel-node {
  visibility: visible;
  transition: max-height .4s cubic-bezier(.4, 0, .2, 1), opacity .25s ease, visibility 0s;
}

/* Closed, with something chosen: every branch that does not contain the
   chosen row folds away. The ones that do — the row itself and its
   ancestors — stay, so the list still says where you are. */
.sel[data-open="false"]:has(input:checked) .sel-node:not(:has(input:checked)) {
  max-height: 0; opacity: 0; visibility: hidden;
  transition: max-height .4s cubic-bezier(.4, 0, .2, 1), opacity .2s ease,
              visibility 0s linear .4s;
}
/* `visibility: hidden` is what takes a folded row out of the tab order —
   `opacity: 0` alone leaves a control you can focus but cannot see. */

.sel-opt {
  display: flex; align-items: flex-start; gap: .55rem;
  padding: .3rem 0; cursor: pointer; font-size: .82rem; line-height: 1.45;
}
.sel-opt:hover .opt-mark { border-color: var(--ink3); }
.sel-opt input { position: absolute; opacity: 0; width: 0; height: 0; }

.opt-mark {
  width: 1.05rem; height: 1.05rem; margin-top: .12rem; flex-shrink: 0;
  border: 1px solid var(--mute); background: var(--surface);
  display: grid; place-items: center;
  transition: background .18s ease, border-color .18s ease;
}
.opt-dot { border-radius: 999px; }
.opt-box { border-radius: 5px; }

.opt-mark::after {
  content: ""; opacity: 0; transform: scale(.4);
  transition: opacity .18s ease, transform .18s cubic-bezier(.34, 1.56, .64, 1);
}
.opt-dot::after {
  width: .45rem; height: .45rem; border-radius: 999px; background: var(--surface);
}
.opt-box::after {
  width: .3rem; height: .58rem; margin-top: -.12rem;
  border: solid var(--surface); border-width: 0 2px 2px 0;
}

input:checked + .opt-mark { background: var(--ink); border-color: var(--ink); }
input:checked + .opt-mark::after { opacity: 1; transform: scale(1); }
input:checked + .opt-box::after { transform: rotate(45deg) scale(1); }
.opt-box::after { transform: rotate(45deg) scale(.4); }

input:focus-visible + .opt-mark {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

.sel-text { min-width: 0; }
.sel-value { opacity: .35; font-size: .7rem; margin-left: .4rem; }

/* Nesting, marked by the line it hangs from rather than by indentation alone. */
.sel-kids {
  margin-left: .5rem; padding-left: .75rem;
  border-left: 1px dotted var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .sel-node, .opt-mark, .opt-mark::after { transition: none; }
}

/* UI-13's group: a box that says what it holds and holds it shut. The border
   is what makes a folded group read as a lid rather than as the end of the
   form — closed, the summary is the only thing on that line. */
.ui-advanced {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--field); padding: .5rem .6rem;
}
.ui-advanced > summary {
  display: flex; align-items: center; gap: .4rem;
  font-size: .72rem; font-weight: 600; color: var(--ink3);
}
.ui-advanced > summary:hover { color: var(--ink); }
.ui-advanced > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The arrow points along the fold: right when shut, down when open. */
.ui-advanced-caret { width: .7em; height: .7em; transition: transform .3s cubic-bezier(.4, 0, .2, 1); }
.ui-advanced[open] > summary .ui-advanced-caret { transform: rotate(90deg); }
.ui-advanced-body > * + * { margin-top: 1rem; }
.ui-advanced-body { margin-top: .85rem; }

@media (prefers-reduced-motion: reduce) {
  .ui-advanced-caret { transition: none; }
}

/* A checkbox reads left to right, so it opts out of the floating label. */
.ui-field-check { position: static; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.ui-field-check > .ui-label { position: static; margin: 0; font-size: .72rem; }
.ui-field-check > .ui-hint { flex-basis: 100%; margin-top: 0; }

/* A decision, not a field (UI-36). The whole row is the hit area, the note is
   inside the control, and the box it sits in is what tells it apart from the
   prose around it. */
.sw {
  display: flex; align-items: flex-start; gap: .85rem; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); padding: .7rem .8rem;
}
.sw + .sw { margin-top: .5rem; }
.sw:hover { border-color: var(--accent-line); }
.sw:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The paint follows the checkbox itself, so a click repaints with no script
   and no reload — `is-on` is only the server's word for how it rendered. */
.sw:has(.sw-in:checked) { border-color: var(--accent-line); background: var(--accent-soft); }
.sw.is-locked { cursor: default; opacity: .72; }
.sw.is-locked:hover { border-color: var(--line); }

/* The real input carries the state and the keyboard; the track is what is
   seen. Hidden with clip rather than display:none, which would take it out of
   the tab order along with the paint. */
.sw-in {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

.sw-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .2rem; }
.sw-label { font-size: .82rem; font-weight: 500; color: var(--ink); }
.sw-note { font-size: .72rem; line-height: 1.45; color: var(--ink3); }

.sw-track {
  flex: none; width: 34px; height: 20px; margin-top: .1rem; border-radius: 10px;
  background: var(--line); transition: background .18s ease;
  display: flex; align-items: center; padding: 2px;
}
.sw-knob {
  width: 16px; height: 16px; border-radius: 50%; background: var(--surface);
  /* A knob's own shadow, not the page's card shadow: the 50px layer of
     var(--shadow) paints a smudge under every toggle on a calm surface. */
  box-shadow: 0 1px 2px oklch(0.2 0.01 85 / 0.25);
  transition: transform .18s ease;
}
.sw:has(.sw-in:checked) .sw-track { background: var(--accent); }
.sw:has(.sw-in:checked) .sw-knob { transform: translateX(14px); }

/* One of a run, so a tick rather than a track: a switch per option would say
   each could be on at once, which is the thing a run rules out. */
.sw-mark {
  flex: none; width: 20px; height: 20px; margin-top: .1rem; border-radius: 50%;
  border: 1px solid var(--line); display: flex; align-items: center; justify-content: center;
  color: var(--accent-ink); background: var(--surface);
}
.sw-mark svg { width: .7em; height: .7em; display: none; }
.sw-pick:has(.sw-in:checked) .sw-mark { background: var(--accent); border-color: var(--accent); }
.sw-pick:has(.sw-in:checked) .sw-mark svg { display: block; }

@media (prefers-reduced-motion: reduce) {
  .sw-track, .sw-knob { transition: none; }
}

/* A colour answered by pointing at it (UI-15): a run of painted chips, round
   for an accent, square for a paper. The paint is the value. */
.swatches { display: flex; flex-wrap: wrap; gap: .5rem; }
.swatch {
  width: 1.9rem; height: 1.9rem; border-radius: 7px; cursor: pointer;
  background: var(--swatch); border: 2px solid transparent;
  outline: 1px solid var(--line); outline-offset: -1px;
}
.swatch-round { border-radius: 50%; }
.swatch:has(input:checked) { border-color: var(--accent); outline-color: var(--surface); }
.swatch:focus-within { border-color: var(--accent); }
.swatch > input {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* ── sections ───────────────────────────────────────────────────────────── */
/*
   Arrangements are a closed set in the schema, so this is the whole list —
   a section cannot ask for a class that is not written here.
*/

.sb-stack { display: grid; gap: 2rem; }
/* One rule for every grid. The column counts arrive as custom properties on
   the element (SECT-7), so "three across on a phone, six on a desktop" is two
   integers rather than a class per combination — there are thirty-two, and a
   stylesheet with thirty-two near-identical rules is one nobody keeps right.
   One breakpoint, because two numbers is what an author was asked for. */
.sb-grid {
  display: grid; gap: .5rem;
  grid-template-columns: repeat(var(--cols-m, 1), minmax(0, 1fr));
}
@media (min-width: 640px) {
  .sb-grid { gap: 1rem; grid-template-columns: repeat(var(--cols-d, 3), minmax(0, 1fr)); }
}
/* A grid item is `min-width: auto` by default, so it refuses to shrink below
   its content — which pushes the last column out past its own container's
   border the moment anything inside it is wider than its track. */
.sb-children > * { min-width: 0; }

/* A gallery sizes its own tracks; inside an arranged section the section's
   count wins, so this comes after `.gallery` rather than before it. Without
   that the two grids fight and the arrangement does nothing. */
.gallery.sb-stack { grid-template-columns: 1fr; }

/* A tile in a grid is a square crop, so a wall of them is a wall rather than a
   ragged edge — the picture decides what is interesting, the grid decides the
   shape. The caption sits under it and does not enter the square. */
.gallery.sb-grid .gallery-item > img,
.gallery.sb-grid .gallery-empty {
  aspect-ratio: 1; width: 100%; height: auto; object-fit: cover;
}
.sb-heading { margin-top: 1rem; }

/* SECT-7's control: a mode, and — only when it is `grid` — two counts. */
.arrangement-field { display: grid; gap: .75rem; }
.arrangement-cols { display: flex; gap: .75rem; }
.arrangement-cols > * { flex: 1; min-width: 0; }

/* SECT-12's: two axes side by side, because vertical and horizontal are one
   question asked twice and reading them on one line says so. */
.placement-pair { display: flex; gap: .75rem; }
.placement-pair > * { flex: 1; min-width: 0; }

.tab-icon { margin-right: .4rem; color: var(--mute); }
.active-tab .tab-icon { color: inherit; }

/* A pill in a dense list is a smaller circle, not a squashed one — only
   `--ui-round-size` changes, so the padding that keeps it round follows. */
.row .ui-round, .pill .ui-round, .card .ui-round, .row-actions .ui-round { --ui-round-size: 2rem; }
.pill .ui-round { --ui-round-size: 1.5rem; }
.pill .ui-round .ui-btn-icon { font-size: .75rem; }

/* BUILD-8: a root section is as wide as the window and carries its own reading
   column. The page used to be one 720px column with everything inside it,
   which left no way to express a full-bleed band, a wide gallery, or a
   two-column container that actually reads as two columns. Nesting is
   untouched — a child is as wide as the parent arranging it. */
.page-main { width: 100%; }
/* The title and a page-level layout slot are full-bleed bands too, so they
   line up with the reading column the sections carry instead of sitting in a
   narrower one of their own — which read as a centred heading over
   left-aligned content. */
/* The same 8px a padded section carries, so the title lines up with the
   content under it rather than sitting in a gutter of its own. */
.page-band { padding-inline: 8px; }
.page-title { padding-block: 2.5rem 0.5rem; }

/* SECT-12: a section's content fills the section, and a section fills the
   cell it was given. A root section is therefore as wide as the window, and a
   container split two ways gives two halves that really are halves.
   
   The 72rem reading column that used to sit here made that untrue: two columns
   were two halves of 1152px floating in a wider page, and there was no way to
   ask for anything else. Width is now a consequence of the tree — how many
   columns the parent has — and the space around it is `padded`/`spaced`,
   which an author sets per section rather than inheriting from a rule. */
.sb-node { display: grid; grid-template-columns: minmax(0, 1fr); }
.sb-body { min-width: 0; }
.sb-padded > .sb-body { padding: 8px; }
.sb-spaced > .sb-body { margin: 8px; }
.nav .ui-round { --ui-round-size: 1.6rem; }

/* ── drawer ─────────────────────────────────────────────────────────────── */
/*
   Where a record is made, on every console screen that makes one. One panel
   per page, refilled from whichever row opened it —
   the same shape the cart already uses on the storefront, so "a form" and
   "a thing sliding in from the right" are the same idea everywhere.
*/

.drawer {
  position: fixed; inset: 0; z-index: 120;
  background: rgb(0 0 0 / 0.4);
  display: flex; justify-content: flex-end;
  animation: drawer-backdrop-in .18s ease;
}
.drawer-panel {
  width: min(34rem, 100%); height: 100%;
  display: flex; flex-direction: column;
  background: var(--surface); border-left: 1px solid var(--line);
  animation: drawer-in .22s cubic-bezier(.4, 0, .2, 1);
}

/* Arriving and leaving are the same movement, and a panel that fades in over
   a fifth of a second and then vanishes between two frames reads as a bug.
   `is-closing` is worn for exactly as long as the leaving animation, and the
   script hides the drawer when it comes off. */
@keyframes drawer-backdrop-in { from { opacity: 0; } }
@keyframes drawer-backdrop-out { to { opacity: 0; } }
@keyframes drawer-in { from { transform: translateX(1.5rem); opacity: 0; } }
@keyframes drawer-out { to { transform: translateX(1.5rem); opacity: 0; } }
.drawer.is-closing { animation: drawer-backdrop-out .18s ease forwards; pointer-events: none; }
.drawer.is-closing .drawer-panel { animation: drawer-out .18s cubic-bezier(.4, 0, .2, 1) forwards; }
@media (prefers-reduced-motion: reduce) {
  .drawer, .drawer-panel, .drawer.is-closing, .drawer.is-closing .drawer-panel { animation: none; }
}

.drawer-head {
  display: flex; align-items: center; gap: .5rem;
  padding: 1rem 1.25rem; border-bottom: 1px solid var(--line);
  background: var(--field);
}
.drawer-body { flex: 1; overflow-y: auto; padding: 1.25rem; }
.drawer-actions { display: flex; align-items: center; gap: .5rem; }
.drawer-status { font-size: .68rem; color: var(--mute); }
.drawer-status.is-saved { color: var(--star); }
/* Settings with an endpoint of their own, below the form rather than in it. */
.drawer-extra { border-top: 1px solid var(--line); padding: 1.25rem; background: var(--field); }

/* The panel is the page's only interactive surface while it is open, so the
   page behind it does not scroll under it. */
body.drawer-open { overflow: hidden; }

/* ── claiming an address ────────────────────────────────────────────────── */

.brand-tail { color: var(--accent); }

.claim-main { padding-block: clamp(1.5rem, 4vh, 2.75rem) 4rem; }
.claim-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2.25rem; align-items: start; }
@media (min-width: 62rem) {
  .claim-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 2.75rem; }
  /* The preview follows the form down: what is being typed and what it makes
     have to be on screen at the same time or neither is answering the other. */
  .claim-see { position: sticky; top: 6rem; }
}
.claim-say { min-width: 0; }
.claim-see { min-width: 0; }
.claim-see-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .5rem; }

/* The address the title is about to take, under the field that decides it. */
.claim-host { font-family: var(--mono); font-size: .8rem; color: var(--ink3); min-height: 1.4em; margin-top: -.4rem; }
.claim-host-taken { color: var(--danger); }

.claim-go { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-top: 1.5rem; }

/* Two fields that are one answer — a first name and a last name sit on one
   line at any width that has room for two. */
.pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 0 .75rem; }

/* ── the segmented chips ────────────────────────────────────────────────── */

.seg { display: inline-flex; gap: 3px; padding: 3px; border-radius: 9px; background: var(--chip); }
.seg-item {
  height: 1.55rem; padding: 0 .75rem; border: 0; border-radius: 7px;
  background: transparent; color: var(--ink3); font-size: .72rem; cursor: pointer;
  transition: background .15s, color .15s;
}
.seg-item[aria-pressed="true"],
.segf input:checked + .seg-item { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px oklch(0.2 0.01 85 / 0.08); }

/* The same chips answering a form question: a label wearing the chip, and the
   radio inside it carrying the state and the keyboard. */
.segf { display: flex; }
.segf-item { flex: 1; display: flex; position: relative; cursor: pointer; }
.segf-item .seg-item { flex: 1; display: inline-flex; align-items: center; justify-content: center; }
.segf-item:has(.sw-in:focus-visible) .seg-item { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── the template picker ────────────────────────────────────────────────── */

.picker { display: grid; grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr)); gap: .6rem; }
.pick {
  display: flex; flex-direction: column; gap: .45rem; position: relative;
  padding: .55rem; border-radius: 12px; cursor: pointer;
  border: 1.5px solid var(--line); background: var(--surface);
  transition: border-color .15s, background .15s;
}
/* Out of sight, still in the tab order and still announced: `hidden` took the
   radio out of both, which left the grid unreachable by keyboard. */
.pick-radio {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%);
}
.pick:hover { border-color: var(--mute); }
.pick:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.pick:has(input:focus-visible) { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.pick-name { font-size: .78rem; font-weight: 600; }

/* The card draws the page it makes: a bar, a hero, a row of cards. */
.pick-plan { display: flex; flex-direction: column; gap: 3px; padding: 5px; border-radius: 7px; background: var(--field); }
.pick-bar { display: flex; align-items: center; gap: 3px; }
.pick-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ink3); flex: none; }
.pick-nav { height: 2px; flex: 1; border-radius: 1px; background: var(--line); }
.pick-hero { display: flex; flex-direction: column; align-items: center; gap: 3px; padding-block: 5px; }
.pick-line { width: 60%; height: 3px; border-radius: 2px; background: var(--ink3); }
.pick-cta { width: 22%; height: 4px; border-radius: 2px; background: var(--accent); }
.pick-row { display: flex; gap: 3px; }
.pick-card { flex: 1; height: 9px; border-radius: 2px; background: var(--line); }
.pick-meta { margin-top: .5rem; }

/* A prepared look paints its own plan, so the grid reads as different sites. */
.pick-plan.tinted { background: var(--pk-bg); }
.pick-plan.tinted .pick-dot { background: var(--pk-accent); }
.pick-plan.tinted .pick-nav { background: color-mix(in srgb, var(--pk-ink) 22%, var(--pk-bg)); }
.pick-plan.tinted .pick-line { background: color-mix(in srgb, var(--pk-ink) 85%, var(--pk-bg)); }
.pick-plan.tinted .pick-cta { background: var(--pk-accent); }
.pick-plan.tinted .pick-card { background: color-mix(in srgb, var(--pk-ink) 16%, var(--pk-bg)); }

/* ── the preview ────────────────────────────────────────────────────────── */

.prev { --prev-width: 100%; }
.prev[data-segmented="phone"] { --prev-width: 20rem; }
.prev-frame {
  width: var(--prev-width); margin-inline: auto;
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; background: var(--surface); box-shadow: var(--shadow);
  transition: width .2s;
}
.prev-chrome { display: flex; align-items: center; gap: .35rem; padding: .45rem .6rem; background: var(--field); border-bottom: 1px solid var(--line); }
.prev-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--line); flex: none; }
.prev-url { flex: 1; margin-left: .35rem; font-family: var(--mono); font-size: .62rem; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prev-page { display: flex; flex-direction: column; min-height: 19rem; }
.prev-bar { display: flex; align-items: center; gap: .5rem; padding: .7rem .9rem; border-bottom: 1px solid var(--hair); }
.prev-mark {
  width: 1.35rem; height: 1.35rem; flex: none; border-radius: 5px;
  background: var(--solid); color: var(--solid-ink);
  display: flex; align-items: center; justify-content: center;
  font-size: .55rem; font-weight: 600; letter-spacing: .02em;
}
.prev-brand { font-family: var(--serif); font-size: .85rem; font-weight: 600; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prev-body { flex: 1; display: flex; flex-direction: column; }
.prev-sec { padding: 1.25rem .9rem; text-align: center; }
.prev-sec + .prev-sec { border-top: 1px solid var(--hair); }
.prev-h { font-family: var(--serif); font-size: 1rem; font-weight: 500; letter-spacing: -0.01em; }
.prev-p { margin-top: .25rem; font-size: .72rem; color: var(--ink3); line-height: 1.5; }
.prev-foot { padding: .6rem .9rem; border-top: 1px solid var(--hair); background: var(--field); font-size: .62rem; color: var(--mute); text-align: center; }

/* The chosen look tints the page being previewed (REG-11): the tint rides on
   three custom properties the script can move when the pick changes. */
.prev-page.tinted { background: var(--pv-bg); color: var(--pv-ink); }
.prev-page.tinted .prev-bar { border-bottom-color: color-mix(in srgb, var(--pv-ink) 14%, transparent); }
.prev-page.tinted .prev-mark { background: var(--pv-accent); color: var(--pv-bg); }
.prev-page.tinted .prev-h { color: var(--pv-ink); }
.prev-page.tinted .prev-p { color: color-mix(in srgb, var(--pv-ink) 72%, var(--pv-bg)); }
.prev-page.tinted .prev-sec + .prev-sec { border-top-color: color-mix(in srgb, var(--pv-ink) 12%, transparent); }
.prev-page.tinted .prev-foot {
  background: color-mix(in srgb, var(--pv-ink) 6%, var(--pv-bg));
  color: color-mix(in srgb, var(--pv-ink) 62%, var(--pv-bg));
  border-top-color: color-mix(in srgb, var(--pv-ink) 12%, transparent);
}

/* An example picture, before it exists: a placeholder in the offer's own
   colours, one wide or a grid — the shape the real section takes. */
.prev-art {
  height: 5.2rem; border-radius: 6px;
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--pv-accent, var(--accent)) 42%, var(--pv-bg, var(--surface))),
    color-mix(in srgb, var(--pv-ink, var(--ink)) 34%, var(--pv-bg, var(--surface)))
  );
}
.prev-sec > .prev-art, .prev-sec > .prev-grid { margin-bottom: .6rem; }
.prev-grid { display: grid; grid-template-columns: repeat(var(--n, 3), 1fr); gap: 4px; }
.prev-grid .prev-art { height: 2.6rem; }

/* ── a phone ────────────────────────────────────────────────────────────── */
/* Read at arm's length: everything set in rem grows together off the root,
   and the smallest print — ten-pixel capitals nobody could read — takes a
   step more on top. The owner's own --body-size keeps ruling the prose and
   gains the point everything else gained. Fields land at 1rem, which is the
   16px under which iOS zooms into every focus. */
@media (max-width: 640px) {
  html { font-size: 112.5%; }
  body { font-size: calc(var(--body-size, 16px) + 1px); }
  .ui-input { font-size: 1rem; }
  .eyebrow { font-size: .72rem; }
  .tiny { font-size: .75rem; }
  .ui-hint { font-size: .75rem; }
  .ui-label { font-size: .78rem; }
  .ui-field-error { font-size: .75rem; }
  .stat-label { font-size: .72rem; }
  .nav a, .nav-more summary { font-size: .95rem; }
  .claim-host { font-size: .9rem; }
}

/* The utility layer (UI-42). Served after every application's own sheet, so a
   class here wins the tie against a component class setting the same property.

   Every value is a step on `theme/scale.rs` or a token on `theme/tokens.rs`.
   Nothing in this file names a number or a colour of its own. */

/* ── layout ──────────────────────────────────────────────────────────────── */

.flex { display: flex; }
.stack { display: flex; flex-direction: column; }
.row { display: flex; flex-direction: row; align-items: center; }
.grid { display: grid; }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.wrap { flex-wrap: wrap; }
.nowrap { flex-wrap: nowrap; }

.start { align-items: flex-start; }
.center { align-items: center; }
.end { align-items: flex-end; }
.baseline { align-items: baseline; }
.stretch { align-items: stretch; }
.between { justify-content: space-between; }
.around { justify-content: space-around; }
.jstart { justify-content: flex-start; }
.jcenter { justify-content: center; }
.jend { justify-content: flex-end; }

/* What one child says about itself, against what its container said about
   all of them. A chip in a column is the usual case: the column stretches
   its children and the chip is as wide as its own word. */
.self-start { align-self: flex-start; }
.self-center { align-self: center; }
.self-end { align-self: flex-end; }

.grow { flex: 1 1 auto; min-width: 0; }
.none { flex: none; }
.shrink { min-width: 0; }

.block { display: block; }
.inline { display: inline; }
.iblock { display: inline-block; }
.iflex { display: inline-flex; align-items: center; }
.relative { position: relative; }
.absolute { position: absolute; }
.fixed { position: fixed; }
.sticky { position: sticky; }
.inset-0 { inset: 0; }
.clip { overflow: hidden; }
.scroll-y { overflow-y: auto; }
.scroll-x { overflow-x: auto; }

/* What a script puts an element behind. The three sheets each carried their
   own copy of this one declaration, so which one a screen got depended on
   which application was serving it. `!important` because it answers a
   `display` the element already has, and it is the only one in the layer. */
.hidden { display: none !important; }

/* A box that fills what holds it is not a measure — it is the absence of one,
   and the same three declarations were written 157 times between the sheets. */
.w-full { width: 100%; }
.h-full { height: 100%; }
.cover { object-fit: cover; }


/* ── space ───────────────────────────────────────────────────────────────── */

.gap-0 { gap: 0; }
.gap-1 { gap: var(--s1); }
.gap-2 { gap: var(--s2); }
.gap-3 { gap: var(--s3); }
.gap-4 { gap: var(--s4); }
.gap-5 { gap: var(--s5); }
.gap-6 { gap: var(--s6); }
.gap-7 { gap: var(--s7); }
.gap-8 { gap: var(--s8); }
.gap-9 { gap: var(--s9); }
.gap-10 { gap: var(--s10); }
.gap-11 { gap: var(--s11); }
.gap-12 { gap: var(--s12); }
.gap-13 { gap: var(--s13); }

.gx-0 { column-gap: 0; }
.gx-1 { column-gap: var(--s1); }
.gx-2 { column-gap: var(--s2); }
.gx-3 { column-gap: var(--s3); }
.gx-4 { column-gap: var(--s4); }
.gx-5 { column-gap: var(--s5); }
.gx-6 { column-gap: var(--s6); }
.gx-7 { column-gap: var(--s7); }
.gx-8 { column-gap: var(--s8); }
.gx-9 { column-gap: var(--s9); }
.gx-10 { column-gap: var(--s10); }
.gx-11 { column-gap: var(--s11); }
.gx-12 { column-gap: var(--s12); }
.gx-13 { column-gap: var(--s13); }

.gy-0 { row-gap: 0; }
.gy-1 { row-gap: var(--s1); }
.gy-2 { row-gap: var(--s2); }
.gy-3 { row-gap: var(--s3); }
.gy-4 { row-gap: var(--s4); }
.gy-5 { row-gap: var(--s5); }
.gy-6 { row-gap: var(--s6); }
.gy-7 { row-gap: var(--s7); }
.gy-8 { row-gap: var(--s8); }
.gy-9 { row-gap: var(--s9); }
.gy-10 { row-gap: var(--s10); }
.gy-11 { row-gap: var(--s11); }
.gy-12 { row-gap: var(--s12); }
.gy-13 { row-gap: var(--s13); }

.p-0 { padding: 0; }
.p-1 { padding: var(--s1); }
.p-2 { padding: var(--s2); }
.p-3 { padding: var(--s3); }
.p-4 { padding: var(--s4); }
.p-5 { padding: var(--s5); }
.p-6 { padding: var(--s6); }
.p-7 { padding: var(--s7); }
.p-8 { padding: var(--s8); }
.p-9 { padding: var(--s9); }
.p-10 { padding: var(--s10); }
.p-11 { padding: var(--s11); }
.p-12 { padding: var(--s12); }
.p-13 { padding: var(--s13); }

.px-0 { padding-inline: 0; }
.px-1 { padding-inline: var(--s1); }
.px-2 { padding-inline: var(--s2); }
.px-3 { padding-inline: var(--s3); }
.px-4 { padding-inline: var(--s4); }
.px-5 { padding-inline: var(--s5); }
.px-6 { padding-inline: var(--s6); }
.px-7 { padding-inline: var(--s7); }
.px-8 { padding-inline: var(--s8); }
.px-9 { padding-inline: var(--s9); }
.px-10 { padding-inline: var(--s10); }
.px-11 { padding-inline: var(--s11); }
.px-12 { padding-inline: var(--s12); }
.px-13 { padding-inline: var(--s13); }

.py-0 { padding-block: 0; }
.py-1 { padding-block: var(--s1); }
.py-2 { padding-block: var(--s2); }
.py-3 { padding-block: var(--s3); }
.py-4 { padding-block: var(--s4); }
.py-5 { padding-block: var(--s5); }
.py-6 { padding-block: var(--s6); }
.py-7 { padding-block: var(--s7); }
.py-8 { padding-block: var(--s8); }
.py-9 { padding-block: var(--s9); }
.py-10 { padding-block: var(--s10); }
.py-11 { padding-block: var(--s11); }
.py-12 { padding-block: var(--s12); }
.py-13 { padding-block: var(--s13); }

.pt-0 { padding-top: 0; }
.pt-1 { padding-top: var(--s1); }
.pt-2 { padding-top: var(--s2); }
.pt-3 { padding-top: var(--s3); }
.pt-4 { padding-top: var(--s4); }
.pt-5 { padding-top: var(--s5); }
.pt-6 { padding-top: var(--s6); }
.pt-7 { padding-top: var(--s7); }
.pt-8 { padding-top: var(--s8); }
.pt-9 { padding-top: var(--s9); }
.pt-10 { padding-top: var(--s10); }
.pt-11 { padding-top: var(--s11); }
.pt-12 { padding-top: var(--s12); }
.pt-13 { padding-top: var(--s13); }

.pb-0 { padding-bottom: 0; }
.pb-1 { padding-bottom: var(--s1); }
.pb-2 { padding-bottom: var(--s2); }
.pb-3 { padding-bottom: var(--s3); }
.pb-4 { padding-bottom: var(--s4); }
.pb-5 { padding-bottom: var(--s5); }
.pb-6 { padding-bottom: var(--s6); }
.pb-7 { padding-bottom: var(--s7); }
.pb-8 { padding-bottom: var(--s8); }
.pb-9 { padding-bottom: var(--s9); }
.pb-10 { padding-bottom: var(--s10); }
.pb-11 { padding-bottom: var(--s11); }
.pb-12 { padding-bottom: var(--s12); }
.pb-13 { padding-bottom: var(--s13); }

/* The only margins there are, and all four are `auto` or nothing. Spacing
   between siblings belongs to the container's gap: a margin that suited one
   parent overlapped the next, which is what this layer exists to stop
   (UI-42). An `auto` is not a length — it is which edge the box is pushed to,
   the one thing a gap cannot say. */
.m-0 { margin: 0; }
.mx-auto { margin-inline: auto; }
.my-auto { margin-block: auto; }
.ml-auto { margin-left: auto; }

/* Where a page stops, and where a reading inside it stops. Every product had
   both and each had named them for itself; the names are the scale's now, so
   a block that lines up with the page says so in the markup.
   **After the margins on purpose**: `m-0 measure` has to mean "no margin of
   its own, and centred", which is what an element wearing both is asking
   for. Written above them it lost the `auto` to the reset, and the foot's
   own hairline was capped at the measure and then left-aligned under bands
   that were centred. */
.measure { width: 100%; max-width: var(--measure); margin-inline: auto; }
.prose { max-width: var(--prose); }
.gutter { padding-inline: var(--gutter); }

/* ── type ────────────────────────────────────────────────────────────────── */

.t-1 { font-size: var(--t1); line-height: var(--lh1); }
.t-2 { font-size: var(--t2); line-height: var(--lh2); }
.t-3 { font-size: var(--t3); line-height: var(--lh3); }
.t-4 { font-size: var(--t4); line-height: var(--lh4); }
.t-5 { font-size: var(--t5); line-height: var(--lh5); }
.t-6 { font-size: var(--t6); line-height: var(--lh6); }
.t-7 { font-size: var(--t7); line-height: var(--lh7); }
.t-8 { font-size: var(--t8); line-height: var(--lh8); }
.t-9 { font-size: var(--t9); line-height: var(--lh9); }
.t-10 { font-size: var(--t10); line-height: var(--lh10); }
.t-11 { font-size: var(--t11); line-height: var(--lh11); }

.serif { font-family: var(--serif); }
.sans { font-family: var(--sans); }
.mono { font-family: var(--mono); }

.fw-4 { font-weight: 400; }
.fw-5 { font-weight: 500; }
.fw-6 { font-weight: 600; }
.fw-7 { font-weight: 700; }

.upper { text-transform: uppercase; }
.track { letter-spacing: 0.08em; }
.tabular { font-variant-numeric: tabular-nums; }
.tleft { text-align: left; }
.tcenter { text-align: center; }
.tright { text-align: right; }
.pretty { text-wrap: pretty; }
.balance { text-wrap: balance; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* `.nowrap` above is the flex one. These two are the text's: a line that may
   not break, and a word — a key, an address, a token — that may break
   anywhere rather than push its column wider. */
.nobr { white-space: nowrap; }
.anywhere { overflow-wrap: anywhere; }

/* ── ink ─────────────────────────────────────────────────────────────────── */

.text-ink { color: var(--ink); }
.text-ink2 { color: var(--ink2); }
.text-ink3 { color: var(--ink3); }
.text-mute { color: var(--mute); }
.text-faint { color: var(--faint); }
.text-accent { color: var(--accent); }
.text-accent-ink { color: var(--accent-ink); }
.text-solid-ink { color: var(--solid-ink); }
.text-danger { color: var(--danger); }
.text-ok { color: var(--ok-ink); }
.text-warn { color: var(--warn-ink); }
.text-info { color: var(--info); }
.text-star { color: var(--star); }

/* ── ground ──────────────────────────────────────────────────────────────── */

.bg-none { background: none; }
.bg-page { background: var(--page); }
.bg-surface { background: var(--surface); }
.bg-rail { background: var(--rail); }
.bg-mid { background: var(--mid); }
.bg-chip { background: var(--chip); }
.bg-field { background: var(--field); }
.bg-accent { background: var(--accent); }
.bg-accent-soft { background: var(--accent-soft); }
.bg-solid { background: var(--solid); }
.bg-danger-soft { background: var(--danger-soft); }
.bg-ok { background: var(--ok-bg); }
.bg-warn { background: var(--warn-bg); }
.bg-info-soft { background: var(--info-soft); }
.bg-star-soft { background: var(--star-soft); }

/* ── edges ───────────────────────────────────────────────────────────────── */

.line { border: 1px solid var(--line); }
.line-t { border-top: 1px solid var(--line); }
.line-b { border-bottom: 1px solid var(--line); }
.line-l { border-left: 1px solid var(--line); }
.line-r { border-right: 1px solid var(--line); }
.line2 { border: 1px solid var(--line2); }
.hair { border: 1px solid var(--hair); }
.hair-t { border-top: 1px solid var(--hair); }
.hair-b { border-bottom: 1px solid var(--hair); }
.line-accent { border: 1px solid var(--accent-line); }
.line-danger { border: 1px solid var(--danger-line); }
.line-warn { border: 1px solid var(--warn-line); }
.line-none { border: 0; }

.r-0 { border-radius: 0; }
.r-1 { border-radius: var(--r1); }
.r-2 { border-radius: var(--r2); }
.r-3 { border-radius: var(--r3); }
.r-4 { border-radius: var(--r4); }
.r-5 { border-radius: var(--r5); }
.r-6 { border-radius: var(--r6); }
.r-7 { border-radius: var(--r7); }
.r-pill { border-radius: var(--r-pill); }

.shadow { box-shadow: var(--shadow); }
.shadow-pop { box-shadow: var(--pop); }
.shadow-none { box-shadow: none; }

/* ── what answers a pointer ──────────────────────────────────────────────── */

/* The sheets spelled twenty-two different transitions between them, and
   fifty-three rules wanted the same one: the colours move, nothing else, and
   the eye reads the change as a state rather than as an animation. */
.transition-colors { transition: background var(--quick), border-color var(--quick), color var(--quick); }
.transition-transform { transition: transform var(--quick); }

.cursor-pointer { cursor: pointer; }
.cursor-default { cursor: default; }
.pointer-none { pointer-events: none; }
.pointer-auto { pointer-events: auto; }

.hover-text-ink:hover { color: var(--ink); }
.hover-text-accent:hover { color: var(--accent); }
.hover-text-danger:hover { color: var(--danger); }
.hover-underline:hover { text-decoration: underline; text-underline-offset: 2px; }

.hover-bg-hover:hover { background: var(--hover); }
.hover-bg-chip:hover { background: var(--chip); }
.hover-bg-surface:hover { background: var(--surface); }
.hover-bg-accent-soft:hover { background: var(--accent-soft); }

.hover-line-accent:hover { border-color: var(--accent); }
.hover-line-ink:hover { border-color: var(--ink); }
.hover-line-danger:hover { border-color: var(--danger); }

.active-bg-down:active { background: var(--accent-down); }

/* A ring the keyboard draws and the mouse does not, taking the corner of
   whatever it is put on rather than deciding one of its own. */
.focus-ring:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: inherit; }
.focus-none:focus { outline: none; }

/* Not `[disabled]`: a link and a label can be past answering too, and neither
   takes the attribute. */
.disabled { opacity: 0.5; cursor: default; pointer-events: none; }

/* ── the desk (UIC-23) ───────────────────────────────────────────────────── */

/* Mobile is the ground. A utility above is every width, and its `d-` twin
   is the desk — 641px and up, measured on the page's own width (the body is
   the container, `layouts.css`), so a page narrowed by the dock is a phone
   to it. `row d-stack` is a row on a phone and a column on a desk; the twin
   comes after the base in this file, which is what lets it win the tie.
   Only the arrangement has twins: what changes between a phone and a desk
   is where things stand, not what colour they are. */
@container (min-width: 641px) {
  .d-flex { display: flex; }
  .d-stack { display: flex; flex-direction: column; }
  .d-row { display: flex; flex-direction: row; align-items: center; }
  .d-grid { display: grid; }
  .d-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .d-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .d-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .d-block { display: block; }
  .d-iflex { display: inline-flex; align-items: center; }
  .d-hidden { display: none; }
  .d-wrap { flex-wrap: wrap; }
  .d-nowrap { flex-wrap: nowrap; }

  .d-start { align-items: flex-start; }
  .d-center { align-items: center; }
  .d-end { align-items: flex-end; }
  .d-stretch { align-items: stretch; }
  .d-between { justify-content: space-between; }
  .d-jstart { justify-content: flex-start; }
  .d-jcenter { justify-content: center; }
  .d-jend { justify-content: flex-end; }

  .d-grow { flex: 1 1 auto; min-width: 0; }
  .d-none { flex: none; }
  .d-w-full { width: 100%; }

  .d-gap-0 { gap: 0; }
  .d-gap-1 { gap: var(--s1); }
  .d-gap-2 { gap: var(--s2); }
  .d-gap-3 { gap: var(--s3); }
  .d-gap-4 { gap: var(--s4); }
  .d-gap-5 { gap: var(--s5); }
  .d-gap-6 { gap: var(--s6); }
  .d-gap-7 { gap: var(--s7); }
  .d-gap-8 { gap: var(--s8); }
  .d-gap-9 { gap: var(--s9); }
  .d-gap-10 { gap: var(--s10); }
  .d-gap-11 { gap: var(--s11); }
  .d-gap-12 { gap: var(--s12); }
  .d-gap-13 { gap: var(--s13); }
}
