@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 chat robot's pane (UI-39) — one look on every surface, the mailbox's.
   Written on the token names both palettes define; the two the console does
   not have fall back. Self-contained on purpose: the mailbox does not load
   theme.css, so the pane brings its own buttons rather than leaning on `rb`.
   Lengths are steps on the ladder (UI-42). */

/* The disc a message hangs off, and with the gap beside it the column its text
   starts in. Everything that is *about* a message rather than in one lines up
   with the text, so the two are one decision rather than two numbers to keep
   in step. */
.ai-pane {
  --ai-face: 25px;
  --ai-hang: calc(var(--ai-face) + var(--s4));
  display: flex; flex-direction: column; height: 100%; min-height: 0; background: var(--page);
}
.ai-face {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 30px; height: 30px; border-radius: 50%; font-size: var(--t4);
  background: var(--solid); color: var(--solid-ink);
  font-family: var(--serif); letter-spacing: 0.02em;
}
.ai-msg .ai-face { width: var(--ai-face); height: var(--ai-face); font-size: var(--t1); }

/* Which model is answering (LLM-28): one of the assistant's settings, drawn
   as the field it is (2026-09-09) — it stood beside the assistant's name
   before, small and quiet, and moved behind the gear with the connections.
   The section it sits in already says "Model", so the field's own label is
   for a screen reader. */
.ai-model .ui-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.ai-model .ui-input:disabled { color: var(--faint); }

.ai-msgs {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--s8) var(--s9); display: flex; flex-direction: column; gap: var(--s6);
}
.ai-msg { display: flex; gap: var(--s4); align-items: flex-end; max-width: min(720px, 92%); }
.ai-msg.is-you { align-self: flex-end; flex-direction: row-reverse; }
.ai-bubble {
  padding: var(--s4) var(--s6); border-radius: var(--r6); min-width: 0; overflow-wrap: anywhere;
  background: var(--surface); border: 1px solid var(--line);
}
.ai-msg.is-you .ai-bubble { background: var(--accent-soft); border-color: transparent; }
.ai-bubble p { margin: 0 0 var(--s4); }
.ai-bubble p:last-child { margin-bottom: 0; }
.ai-bubble code { font-family: var(--mono); font-size: var(--t4); }
/* The robot answers with documents: its tables, lists and code arrive as
   such, sized for a bubble rather than for the manual. */
.ai-bubble h1, .ai-bubble h2, .ai-bubble h3, .ai-bubble h4 {
  font-family: var(--serif); font-size: var(--t5); margin: var(--s5) 0 var(--s3);
}
.ai-bubble ul, .ai-bubble ol { margin: 0 0 var(--s4); padding-left: var(--s9); }
.ai-bubble li { margin: var(--s1) 0; }
/* A place the robot named (LLM-31): a link in the reply, to the mail beside
   the chat — drawn as one, in the accent, so the reader knows it opens. */
.ai-bubble a { color: var(--accent); text-decoration: underline; text-underline-offset: 0.15em; }
.ai-bubble a:hover { color: var(--ink); }
.ai-bubble .md-table {
  border-collapse: collapse; margin: var(--s3) 0 var(--s4); font-size: var(--t4);
  display: block; overflow-x: auto; max-width: 100%;
}
.ai-bubble .md-table th, .ai-bubble .md-table td {
  border: 1px solid var(--line); padding: var(--s2) var(--s4);
  text-align: left; vertical-align: top;
}
.ai-bubble .md-table th { background: var(--page); font-weight: 600; }
.ai-bubble .md-code {
  background: var(--page); border: 1px solid var(--line); border-radius: var(--r3);
  padding: var(--s4) var(--s5); overflow-x: auto; font-size: var(--t3); margin: var(--s3) 0 var(--s4);
}

.ai-call { max-width: min(720px, 92%); margin-left: var(--ai-hang); }
.ai-call summary { display: inline-flex; cursor: pointer; list-style: none; }
.ai-call summary::-webkit-details-marker { display: none; }
.ai-call[open] summary .ai-tool {
  background: var(--accent-soft); color: var(--accent-down, var(--accent)); border-color: transparent;
}
/* The fold's own gap: what a `<summary>` and the box under it are apart, and
   the one thing neither of them can say with a container's gap. */
.ai-call-io { margin-top: var(--s3); }
.ai-call-io pre {
  margin: 0; font-family: var(--mono); font-size: var(--t3); line-height: var(--lh3);
  white-space: pre-wrap; overflow-wrap: anywhere; max-height: 240px; overflow: auto;
  color: var(--ink);
}

/* A finished turn's working, folded behind the answer (UI-44). The pill hangs
   where the tool chips hang; everything inside keeps the place it always had,
   so opening the fold draws the thread that used to be drawn outright. */
.ai-steps > summary {
  display: inline-flex; align-items: center; gap: var(--s3);
  margin-left: var(--ai-hang); cursor: pointer; list-style: none;
}
.ai-steps > summary::-webkit-details-marker { display: none; }
.ai-steps > summary:hover .ai-steps-count { color: var(--mute); border-color: var(--accent); }
.ai-steps[open] > summary .ai-steps-count {
  background: var(--accent-soft); color: var(--accent-down, var(--accent)); border-color: transparent;
}
.ai-steps[open] > summary .ai-steps-turn { transform: rotate(90deg); }

/* What a tool answer pointed at (LLM-21), drawn the way the service's own
   grid draws it: picture on top, the title over its line, the page in a
   tab. Inline, the words place the grid inside their own bubble. */
.ai-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--s4); margin-left: var(--ai-hang); max-width: min(720px, 92%);
}
.ai-cards.is-inline { margin: var(--s4) 0; max-width: none; }
/* A card is a link and reads as a card, not as a link. */
.ai-card { text-decoration: none; }
/* The frame a picture is cut to — the same three-by-two the portal's own
   cards wear, so a drawing keeps its roof and its ground rather than the
   strip through its middle — and the wash where there is no picture. */
.ai-card-pic { aspect-ratio: 3 / 2; height: auto; background: var(--accent-soft); }
.ai-card-pic.is-blank { min-height: 96px; }

/* A form the words placed (LLM-22): the tool's own schema as fields, inside
   the bubble, required first. The controls wear the composer's look. */
/* The form is a block in the middle of prose, so it keeps a margin: what is
   above and below it are the words of a bubble and not its siblings. */
.ai-form { margin: var(--s4) 0; }
.ai-form input[type="text"], .ai-form input[type="number"],
.ai-form select, .ai-form textarea {
  font: inherit; color: var(--ink); background: var(--page);
  border: 1px solid var(--line); border-radius: var(--r3);
  padding: var(--s3) var(--s4); width: 100%;
}
.ai-form textarea { min-height: 72px; resize: vertical; }
.ai-form-said[hidden] { display: none; }
/* A press, so it says `font: inherit` — a button is the one element that does
   not take the page's face unless it is told to. The sent state is a
   `:disabled`, which is the script's doing and not a class it puts on. */
.ai-form-send { font: inherit; color: var(--accent-ink, var(--page)); }
.ai-form-send:disabled { opacity: 0.5; cursor: default; }

/* The step being worked right now: the tool's name where a finished step's
   chip will stand, with the same dots the thinking bubble beats (UI-44). */
.ai-step-now { margin-left: var(--ai-hang); }
/* Dashed while it is running, solid once it is a step that happened. */
.ai-step-now .ai-tool {
  font-size: var(--t2); font-weight: 600; color: var(--faint);
  text-transform: uppercase; letter-spacing: 0.06em;
  border-style: dashed;
}

.ai-confirm {
  display: flex; flex-direction: column; gap: var(--s5);
  max-width: min(720px, 92%); margin-left: var(--ai-hang); padding: var(--s6) var(--s7);
  border-radius: var(--r5); background: var(--warn-bg);
  border: 1px solid var(--warn-line); color: var(--warn-ink); font-size: var(--t4);
}
.ai-confirm-said {
  margin: 0; padding: var(--s3) var(--s5); border-left: 2px solid var(--warn-line);
  color: var(--ink); background: var(--surface); border-radius: 0 var(--r3) var(--r3) 0;
  overflow-wrap: anywhere;
}
.ai-confirm-args {
  margin: 0; padding: var(--s3) var(--s5); font-family: var(--mono); font-size: var(--t3);
  line-height: var(--lh3); white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 200px; overflow: auto; color: var(--ink);
  background: var(--surface); border-left: 2px solid var(--warn-line);
  border-radius: 0 var(--r3) var(--r3) 0;
}
.ai-told { margin: 0 0 0 var(--ai-hang); color: var(--mute); font-size: var(--t4); }
.ai-told.is-done { color: var(--ok-ink, var(--ink)); }
.ai-typing { white-space: pre-wrap; }
.ai-msgs .ai-error { padding: 0 0 0 var(--ai-hang); }

.ai-composer {
  display: flex; gap: var(--s4); align-items: flex-end; flex: none;
  padding: var(--s6) var(--s9) calc(var(--s7) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--surface);
}
.ai-ask {
  flex: 1; resize: none; min-height: 36px; max-height: 160px;
  padding: var(--s4) var(--s6); border-radius: var(--r6); border: 1px solid var(--line);
  background: var(--page); color: var(--ink);
  font: inherit; font-size: var(--t5); line-height: var(--lh5);
}
.ai-ask:focus { outline: none; border-color: var(--accent); }

.ai-pill {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s3);
  height: 30px; padding: 0 var(--s7); border-radius: var(--r-pill); border: 1px solid var(--line);
  background: none; color: var(--ink); font: inherit; font-size: var(--t4);
  white-space: nowrap; cursor: pointer;
}
.ai-pill:hover { border-color: var(--accent); }
.ai-pill:disabled { color: var(--faint); border-color: var(--line); cursor: default; }
.ai-pill.is-primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  font-weight: 500;
}
.ai-pill.is-primary:hover {
  background: var(--accent-down, var(--accent)); border-color: var(--accent-down, var(--accent));
}
.ai-send {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--accent);
  background: var(--accent); color: var(--accent-ink); cursor: pointer;
}
.ai-send svg { width: 14px; height: 14px; }

.ai-dots { display: inline-flex; gap: var(--s2); padding: var(--s2) var(--s1) var(--s1); }
.ai-dots i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--mute);
  animation: ai-think 1.2s infinite;
}
.ai-dots i:nth-child(2) { animation-delay: 0.2s; }
.ai-dots i:nth-child(3) { animation-delay: 0.4s; }
@keyframes ai-think { 0%, 60%, 100% { opacity: 0.25; } 30% { opacity: 1; } }

.ai-hello { max-width: 420px; margin: auto; text-align: center; color: var(--mute); }
.ai-hello strong { font-family: var(--serif); font-size: var(--t6); color: var(--ink); display: block; margin-bottom: var(--s3); }

/* The way back to the list, shown only where the list has been swapped out. */
.ai-back {
  display: none; align-items: center; justify-content: center; flex: none;
  width: 28px; height: 28px; border-radius: 50%; color: var(--ink);
}
@media (max-width: 1080px) {
  .ai-back { display: inline-flex; }
}

/* ── the pane in a dock (UIC-13) ─────────────────────────────────────────── */

/* The panel says what this is in its own head and scrolls its own body, so in
   here the pane drops its head and fills the height rather than scrolling
   twice. Narrower than a page column: bubbles take the width they are given
   and the cards stack. */
.ai-dock .ui-dock-body { padding: 0; overflow: hidden; }
.ai-dock .ai-head { display: none; }
.ai-dock .ai-pane { background: var(--surface); }
.ai-dock .ai-msgs { padding: var(--s6); gap: var(--s6); }
.ai-dock .ai-composer { padding: var(--s5) var(--s6) calc(var(--s6) + env(safe-area-inset-bottom)); }
.ai-dock .ai-msg, .ai-dock .ai-call, .ai-dock .ai-cards, .ai-dock .ai-confirm { max-width: 100%; }
.ai-dock .ai-error { padding: 0 var(--s6); }

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

/* Wide, the thread and the composer keep a reading measure in the middle
   rather than running the width of a desk: a line of an answer that is
   1400px long is one nobody reads twice. The fold's rows and the reach
   breathe as columns. */
.ai-dock.is-page .ai-msgs { padding: var(--s9) var(--s8) var(--s7); gap: var(--s7); align-items: center; }
.ai-dock.is-page .ai-msgs > * { width: 100%; max-width: 820px; }
.ai-dock.is-page .ai-msg.is-you { align-self: center; justify-content: flex-start; }
.ai-dock.is-page .ai-hello { max-width: 480px; }
.ai-dock.is-page .ai-composer { padding: var(--s6) var(--s8) calc(var(--s8) + env(safe-area-inset-bottom)); justify-content: center; }
.ai-dock.is-page .ai-ask { max-width: calc(820px - var(--s11) - var(--s4)); }
.ai-dock.is-page .ui-dock-past { padding: var(--s7) var(--s6); }
.ai-dock.is-page .ui-dock-foot { padding: var(--s7) var(--s6); }
/* The bar over the page already says what this is; the head keeps the
   gear and the line under the name. */
.ai-dock.is-page .ui-dock-title { display: none; }
/* The reach as a column (`stack gap-5` on the markup): what it reads, one
   line each. */
.pa-reach-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
/* Hairline rows, the editor's manner (UIC-25): no card under each. */
.pa-reach-row {
  display: flex; flex-direction: column; gap: var(--s1); min-width: 0;
  padding: var(--s4) 0; border-top: 1px solid var(--line);
}
.pa-reach-row:first-child { padding-top: 0; border-top: 0; }
.pa-reach-name { font-weight: 600; font-size: var(--t4); }
.pa-reach-what { color: var(--mute); font-size: var(--t3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Under the conversation rather than beside it, the reach is not drawn: a
   list under the composer took a third of a phone, and the gear in the head
   opens the same list. */
@media (max-width: 1199px) {
  .pa-reach { display: none; }
}
@media (max-width: 640px) {
  .ai-dock.is-page .ai-msgs { padding: var(--s6); }
  .ai-dock.is-page .ai-composer { padding: var(--s5) var(--s6) calc(var(--s6) + env(safe-area-inset-bottom)); }
}

/* One earlier conversation, in the fold over the current one. */
.ai-past {
  display: block; padding: var(--s3) var(--s4); border-radius: var(--r4);
  color: var(--ink); text-decoration: none; font-size: var(--t4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ai-past:hover { background: var(--hover); }
.ai-past.is-open { background: var(--accent-soft); }
.ai-past[hidden] { display: none; }
/* The way to start again, at the head of the same list: it belongs with the
   conversations because choosing between them is the same choice. */
.ai-past.is-fresh { color: var(--accent); font-weight: 600; }

/* A row of the fold (LLM-32) — `flex center gap-1` on the markup — the
   conversation, and beside it the × that forgets it, shown while the row is
   under the pointer and always where there is no pointer to hover with. */
.ai-past-row[hidden] { display: none; }
.ai-past-row > .ai-past { flex: 1; min-width: 0; }
.ai-past-x {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s9);
  height: var(--s9);
  border: 0;
  border-radius: var(--r4);
  background: none;
  color: var(--mute);
  cursor: pointer;
  opacity: 0;
}
.ai-past-x .icon { width: var(--s4); height: var(--s4); }
.ai-past-row:hover .ai-past-x, .ai-past-x:focus-visible { opacity: 1; }
.ai-past-x:hover { background: var(--danger-soft); color: var(--danger); }
@media (hover: none) { .ai-past-x { opacity: 1; } }

/* All of them, from the foot of the list, in the same quiet voice as the
   press for more — the danger is in the dialog it opens, not in the row. */
.ai-past-forget {
  align-self: flex-start;
  margin-top: var(--s2);
  padding: var(--s3) var(--s4);
  border: 0;
  border-radius: var(--r4);
  background: none;
  color: var(--mute);
  font-family: inherit;
  font-size: var(--t4);
  cursor: pointer;
}
.ai-past-forget:hover { background: var(--danger-soft); color: var(--danger); }

/* The rest of them, a page at a time (UIC-14). */
.ai-past-more {
  align-self: flex-start;
  margin-top: var(--s2);
  padding: var(--s3) var(--s4);
  border: 0;
  border-radius: var(--r4);
  background: none;
  color: var(--mute);
  font-family: inherit;
  font-size: var(--t4);
  cursor: pointer;
}
.ai-past-more:hover { background: var(--hover); color: var(--ink); }

/* ── what the robot can reach (LLM-17, LLM-19) ───────────────────────────── */

/* The surface the gear opens (UI-14, 2026-09-09): the assistant's settings,
   in the tier's overlay and the editor's manner (`ui-part`, KODU-110) like
   every other settings surface — numbered parts, hairlines, no boxes. */
/* How many are connected here, beside the reach's heading. */
.pa-count {
  padding: var(--s1) var(--s3); border-radius: var(--r-pill);
  background: var(--chip); color: var(--mute); font-size: var(--t3); font-weight: 400;
}
.pa-cx .overlay-body { max-width: 52rem; margin: 0 auto; padding-bottom: var(--s12); }
.pa-part-title { margin: 0 0 var(--s1); font-size: var(--t6); }
/* A connection is a hairline, not a box: this chat's tick when a chat is
   open, the name, the address in the mono, what it answers, and the way off
   this surface. What a row's tools are folds out under it, full width. */
.pa-eps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.pa-ep {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4) var(--s5);
  padding: var(--s5) 0; border-top: 1px solid var(--line);
}
.pa-ep:first-child { border-top: 0; padding-top: 0; }
.pa-tick input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.pa-ep-name { flex: 0 0 auto; min-width: 6rem; font-weight: 600; font-size: var(--t5); }
.pa-ep-url {
  flex: 1 1 12rem; min-width: 0; color: var(--faint); font-family: var(--mono); font-size: var(--t3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pa-ep-standing {
  padding: 0; border: 0; background: none; font: inherit; font-size: var(--t3);
  color: var(--faint); white-space: nowrap;
}
.pa-ep-standing.is-up { color: var(--ok-ink); cursor: pointer; }
.pa-ep-standing.is-up:hover { text-decoration: underline; }
.pa-ep-standing.is-down { color: var(--danger); }
/* `[hidden]` first: an explicit `display` beats the attribute's own, so
   without this line the list is open before anybody asks for it. */
.pa-tools[hidden] { display: none; }
.pa-tools {
  flex-basis: 100%; list-style: none; margin: var(--s2) 0 0; padding: 0 0 0 var(--s2);
  display: flex; flex-direction: column; gap: var(--s1);
  border-left: 2px solid var(--line);
}
.pa-tools li { display: flex; gap: var(--s4); align-items: baseline; padding-left: var(--s4); }
.pa-tool-name { font-size: var(--t3); color: var(--ink); }
.pa-tool-about {
  color: var(--faint); font-size: var(--t3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pa-ours {
  flex-direction: column; align-items: flex-start; gap: var(--s1);
  padding: var(--s4) var(--s6); text-align: left;
}
.pa-connect .ui-field { min-width: 12rem; }

/* Staged, not saved (LLM-20). One press moves a row into one of these two
   states and the next press at the foot is what makes either of them true.
   `[hidden]` needs saying for a `.ui-round`, whose own `inline-flex` beats the
   attribute's `display: none` — the same trap `.pa-tools[hidden]` is above. */
.pa-ep .ui-round[hidden], .pa-ep-going[hidden], .pa-ep-standing[hidden] { display: none; }
.pa-ep.is-going { background: var(--danger-soft); border-radius: var(--r3); padding-inline: var(--s3); }
.pa-ep.is-going .pa-ep-name, .pa-ep.is-going .pa-ep-url { text-decoration: line-through; color: var(--faint); }
.pa-ep.is-new { background: var(--accent-soft); border-radius: var(--r3); padding-inline: var(--s3); }
.pa-ep-going { margin-left: auto; color: var(--mute); font-size: var(--t3); white-space: nowrap; }
.pa-ep.is-going .pa-ep-going { color: var(--danger); }
.pa-dot[hidden] { display: none; }
.pa-dot {
  width: 7px; height: 7px; border-radius: var(--r-pill); flex: none;
  background: var(--accent);
}

/* Kodu's stylesheet, served at /kodu.css with the faces and the house palette
   in front of it (portal/css.rs).

   Two authorities. The prototype — designs/kodu/Kodu24 Portal.dc.html —
   decides what is on a screen: the brand, the four actions, the five deals,
   the operator line. The shipped mail app decides what it looks like and how
   it is arranged: the frame is the viewport at 100dvh, the pane scrolls
   itself, and the two schemes are the same token names with different values
   so nothing below the tokens ever asks which scheme it is in.

   Five places they disagree, and Postbox wins all five:

   1. Full-bleed, and the measure is capped per element. The prototype is a
      centred 1020px column on a page that scrolls. Here the frame is the
      window: the rail is a fixed column down the left and the pane takes the
      rest, and inside the pane it is the *element* that says what width it
      wants. Prose stops at --k-prose because a line of text has a length a
      person can read; a list of a hundred and eleven place names, and the
      results grid S3 brings, fill whatever they are given, because a wider
      window is more of them and not longer lines. Capping the page instead
      would leave a thin ribbon of content in the middle of a 2 400px screen
      and call it a design.
   2. A dark scheme, which neither prototype has. Mechanical: the prototype's
      `white` is var(--surface), its oklch(0.22 0.01 85) is var(--ink), its
      oklch(0.55 0.01 85) is var(--mute), and the tests in portal/css.rs are
      what prove none was missed.
   3. Shadows go. The prototype puts one under every card. Shadows are for what
      floats, and nothing in a column floats — a card is a surface with a
      hairline. --shadow is spent on the lightbox, the area popover, the chip
      menu and the two folds at the foot of the rail, which do.
   4. One navigation, down the left (KODU-18). The prototype's header and its
      underlined tab bar were a hundred and forty pixels of chrome across the
      top of every screen doing one job between them; they are the rail, which
      is what the mail app has and for the same reason. It is still the deal
      control (KODU-13) — the prototype's second one, a segmented row at the
      top of the panel, is gone from search and kept only where a deal is being
      said rather than navigated to.
   5. /theme.css is not linked. The tokens are served in front of this sheet
      instead, so a storefront rule cannot reach a portal screen.

   Nothing below the token blocks names a colour. A value the design needs that
   is not a token belongs in theme/tokens.rs, not here — with the one exception
   directly below, which is a rule rather than a shade. */

/* CTX-11: the agency accents. Kodu's own table, kept with the application
   whose rule it is: separated in lightness as well as hue, so two chips differ
   in greyscale and to an eye that does not split red from green. Amber, green
   and red are left to the statuses that mean them. The person context takes no
   slot — it wears --accent, because being yourself is the default state.

   The measures are here for the same reason a token is anywhere: the pane's
   own gutter is used by the pane and by the operator line inside it, and two
   copies of one clamp is how one edge stops being one. --k-rail is the third:
   the column's width is read by the rail, by the narrow query that lays it
   down, and by the pane that sits beside it. */
:root {
  /* Three of the portal's four measures are every product's, and the scale
     names them now (UI-42): `--gutter`, `--prose` and `--measure`. The
     portal's own names are kept as aliases rather than chased through four
     hundred rules — one line each, and what they point at is the house's.
     The rail is the fourth and stays the portal's: it is a column this
     product has and the mailbox's is 84. */
  --k-gutter: var(--gutter);
  --k-prose: var(--prose);
  --k-page: var(--measure);
  --k-rail: 96px;
  /* Flat (KODU-102): one small corner on anything that is a box — a
     picture, a card, a field, a panel, a map. Every step of the ladder is
     the same 3px, which takes the edge off a box without making it a card
     again; the pill and the circle keep their shape, because a pill that is
     not round is a rectangle with a name. */
  --r1: 3px;
  --r2: 3px;
  --r3: 3px;
  --r4: 3px;
  --r5: 3px;
  --r6: 3px;
  --r7: 3px;

  /* A listing's own tint, until there are photographs (KODU-10). Like the
     accent table above it this is a *rule* and not a shade: the hue comes from
     the element — a hash of the listing (portal/thumb.rs) — and only the
     lightness and the chroma are decided here, which is what lets the same
     listing be the same colour in both schemes.

     The chroma is near nothing on purpose. It was 0.035, which spread twelve
     cards across pink, mint, lilac and peach — a grid of colours that mean
     nothing, in a product whose whole palette is one warm neutral and one
     accent. What is left is the same per-listing hue at a strength you notice
     only when two cards are side by side, which is the only place it was ever
     doing any work.

     A lightness and a chroma, not a whole colour, which is `--av-bg`'s shape
     exactly and for a reason worth writing down: a custom property holding
     `oklch(0.92 0.035 var(--hue))` is substituted where it is *declared*, and
     `--hue` is not set on `:root` — so the token is invalid and every
     thumbnail comes out transparent, with no error anywhere. The rule
     assembles the colour instead. */
  --k-thumb-bg: 0.945 0.012;
  --k-thumb-fg: 0.68 0.03;

  /* The reader's own face at the foot of the rail (KODU-18), the same shape
     for the same reason — the hue is `ui::face::hue` over their address, so
     they are one colour in the console, the mail app and here (UI-19).
     Stronger than the thumbnails' because it is one small circle standing for
     a person rather than a grid of cards standing for nothing. */
  --k-face-bg: 0.885 0.055;
  --k-face-fg: 0.34 0.075;

  /* An unpicked rail icon (KODU-21). Its own token because the rail is the one
     place in this design where an icon and its caption have to weigh the
     *same*: the mail app holds both at a hair over half lightness, and Kodu
     borrowed --ink3 (0.45) for the icon while the caption sat at --mute
     (0.62). Measured against a shot of Postbox's rail, Kodu's darkest inactive
     icon pixel was 78/255 to Postbox's 106 — heavy drawings over faint words,
     which is the opposite of one row reading as one thing. This is Postbox's
     `--rail-ink`, arrived at the same way and kept here because it is a rule
     about this column and not a colour the rest of the portal has a use for. */
  --k-rail-ink: oklch(0.55 0.012 85);

  /* The full-screen view's ground and ink (KODU-69): a warm near-black in
     both schemes — the photograph is the light there, so the dark scheme has
     nothing to invert — its controls a veil of the ground over the picture,
     and their hairline a veil of the ink. */
  --k-light-bg: oklch(0.13 0.01 85);
  --k-light-fg: oklch(0.98 0.005 85);
  --k-light-veil: oklch(0.1 0.01 85 / 0.5);
  --k-light-line: oklch(0.98 0.005 85 / 0.28);

  /* The paper a floor plan is drawn on (KODU-77): white in both schemes,
     because the drawing was made on white and its own ground merges with
     this one, where a tinted surface would show the sheet's edge. */
  --k-paper: oklch(1 0 0);

  /* The ground, a shade lighter than the house's (2026-09-07: *"praegune on
     natukene liiga hall"*): not white, because the cards have to be lighter
     than what they stand on, but no longer a grey a reader notices. A card
     is `--surface` — lighter than this and still not white — and under the
     pointer it goes to white, the one step left above it. */
  --page: oklch(0.975 0.007 85);
  --k-card-hover: oklch(1 0 0);

  --k-accent-1: oklch(0.52 0.13 215);
  --k-accent-2: oklch(0.48 0.13 340);
  --k-accent-3: oklch(0.58 0.13 115);
  --k-accent-4: oklch(0.44 0.13 185);
  --k-accent-5: oklch(0.56 0.13 305);
  --k-accent-6: oklch(0.62 0.13 90);
}

/* Every lightness gains 0.10; hue and chroma do not move, so an agency is the
   same colour in both themes (CTX-11). */
/* The reader's choice wins (KODU-98), the way the tier's own dark table is
   written: a plain `:root` under this query answered a reader who had
   chosen light on a system that prefers dark, and once the ground was in
   this table (2026-09-07) their content went dark while the rail — the
   tier's tokens — did as it was told. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* The tint turns dark and its ink turns light; the hue does not move, so a
       listing keeps the colour a reader recognises it by. */
    --k-thumb-bg: 0.245 0.014;
    --k-thumb-fg: 0.46 0.03;

    --k-face-bg: 0.42 0.07;
    --k-face-fg: 0.93 0.035;

    /* Lighter than the ground here, which is the half of the rule that
       survives inverting everything else. */
    --k-rail-ink: oklch(0.62 0.01 85);

    /* The same four: the full-screen view is dark whichever scheme the page
       is in, because the picture is what is lit. */
    --k-light-bg: oklch(0.13 0.01 85);
    --k-light-fg: oklch(0.98 0.005 85);
    --k-light-veil: oklch(0.1 0.01 85 / 0.5);
    --k-light-line: oklch(0.98 0.005 85 / 0.28);

    /* Paper stays paper. */
    --k-paper: oklch(1 0 0);

    /* The pointer's step up, in the dark: the chip's lightness, since white
       under the pointer is a flash and not a lift. */
    --k-card-hover: oklch(0.25 0.006 85);
    /* The house's own dark ground, restated: only the light one moved. */
    --page: oklch(0.155 0.006 85);

    --k-accent-1: oklch(0.62 0.13 215);
    --k-accent-2: oklch(0.58 0.13 340);
    --k-accent-3: oklch(0.68 0.13 115);
    --k-accent-4: oklch(0.54 0.13 185);
    --k-accent-5: oklch(0.66 0.13 305);
    --k-accent-6: oklch(0.72 0.13 90);
  }
}

*, *::before, *::after { box-sizing: border-box; }

/* The frame is the viewport. Nothing outside it scrolls — the pane does its
   own scrolling, and a page-level scrollbar here would only ever be something
   overflowing that should not. */
html, body { height: 100%; overflow: hidden; }
/* The page does not pinch; the map does (KODU-60). `pan-x pan-y` is what
   holds Android's pinch-zoom off the page — the map's own svg declares
   `touch-action: none` and keeps its two fingers. */
html { touch-action: pan-x pan-y; }
/* Two rows, and the second one is usually not there: the shell, and under it
   the greeting a first visit is met with (KODU-97).

   It was `position: fixed` for an afternoon, and that was wrong twice over —
   the rail's foot is where the reader's own face hangs, and the area picker
   opens onto the whole screen with **Kinnita valik** along its bottom edge,
   so a band laid over the last eighty points of the window covered the
   account fold on every screen and the picker's only way out on one. A first
   visit is not a reason to make the page unusable until it is answered. In a
   row of its own it takes its space from the shell instead of borrowing it.

   Every other child of the body is out of the flow — the skip link is
   absolute, the assistant and the report box are fixed, the scripts draw
   nothing — so there are never three rows. */
body {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  height: 100dvh;
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t5);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  touch-action: manipulation;
}

a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
::placeholder { color: var(--faint); }
::selection { background: var(--accent-soft); color: var(--accent); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* One focus ring for the whole application, on the things a keyboard actually
   lands on. `:focus-visible` and not `:focus`, so a mouse press does not leave
   a ring behind on every card it opens — and the ring is the accent rather
   than the browser's, because the browser's is a colour this design has no
   say over and lands on top of a border it cannot see. */
:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r3);
}
/* The pane takes focus when the skip link is used, and it is not a control —
   a ring around the whole page would be startling. */
.k-pane:focus { outline: none; }

/* The first thing a keyboard meets and the last thing anybody else sees.
   Without it, reaching the results means tabbing past the brand and eight rail
   entries, on every page. */
.k-skip {
  z-index: 100;
  top: 8px;
  left: 8px;
  transform: translateY(-200%);
}
.k-skip:focus { transform: none; }

/* Somebody who has asked their machine to stop moving things has asked this
   one too. Everything in this design that moves is decoration — a toggle's
   knob, a hover — so there is nothing to keep. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── the frame ───────────────────────────────────────────────────────────── */

/* `display: grid` stays: the narrow query below turns it into a
   `column-reverse` flex column, and a utility would win that tie. */
/* The height is the body's row, not the window's: with the greeting standing
   under it the shell is shorter than the viewport by exactly the band, and
   `100dvh` here would push that band off the bottom edge. */
.k {
  display: grid;
  grid-template-columns: var(--k-rail) minmax(0, 1fr);
  width: 100%;
  min-height: 0;
}

/* Three of the prototype's nine views — Lisa kuulutus, Uus büroo and the
   terms — replace the shell rather than sitting in it: nothing to browse the
   market with while you are filling a form in, and no operator line under a
   takeover. The rail stays, because a takeover a reader cannot get out of is
   a trap; what goes is everything on it that is about the market. */
.k[data-depth="sheet"] .k-railgroup:first-of-type,
.k[data-depth="sheet"] .k-railrule,
.k[data-depth="sheet"] .k-foot { display: none; }

/* ── the assistant stands beside the page (KODU-89) ──────────────────────── */

/* The dock's whole contract with a surface is one attribute on `<html>`, and
   the narrowing it asks for is padding on the body. `.k` is `width: 100%`
   inside that body, so the frame — the rail, the pane and everything in them
   — gives way on its own and there is nothing to write for the ordinary
   screens.

   A map that has been taken full screen is the exception: it is
   `position: fixed`, so the body's padding does not reach it and it would
   lie *under* the panel. It is still the page, so it gives up the panel's
   width rather than hiding behind it — both the map door's screen and the
   listing's own map, which grow the same way. On a phone the dock is the
   whole screen and there is no beside, which is the width dock.css itself
   stops narrowing at. */
[data-dock="half"] .k-mapscreen.is-full,
[data-dock="half"] .k-mapview.is-full { right: var(--ui-dock-w); }
/* At full there is no page left to give up anything: the panel is the screen
   and the map is behind it, so the map keeps the size it had rather than
   collapsing to nothing and having to lay itself out again on the way back. */
@media (max-width: 640px) {
  [data-dock="half"] .k-mapscreen.is-full,
  [data-dock="half"] .k-mapview.is-full { right: 0; }
}

/* The map keeps its zoom in the bottom right of the drawing and the launcher
   stands in the bottom right of the window, so on any screen carrying a map
   the launcher steps left of that column rather than on top of it. */
body:has(.mp-page) .ui-dock-launcher { right: calc(var(--s12) + var(--s9)); }

/* ── the rail ────────────────────────────────────────────────────────────── */

/* Postbox's column, in this application's clothes (KODU-21): the ground and
   the edge are the rail's own tokens rather than a surface with a hairline,
   and everything on it is centred, because a narrow column of centred icons
   is what stops eight entries reading as a list of words.

   It does not scroll and must not: the account's fold hangs off the foot and
   opens to the *right* of the column, and a scroll container clips what leaves
   it. What gives way on a short window is a group of entries, which scrolls
   inside itself — so the mark, the reader's own two and the foot stay put. */
/* The direction, the gap, the padding and the right-hand edge are all turned
   round by the narrow queries at the foot of this sheet, so they stay here: a
   utility on the element wins the tie against a rule inside `@media`. */
.k-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s8);
  min-height: 0;
  padding: var(--s9) 0 var(--s8);
  overflow: visible;
  border-right: 1px solid var(--line);
}

/* A disc and the brand's first letter, and no wordmark: twenty-one points of
   serif does not fit in a column this narrow, and the name is on the door
   already. The link keeps its name through `aria-label`. */
/* `display` stays: the narrow query takes the mark out of the bar. */
/* The mark is a drawing with its own colours (`portal/mark.rs`), so the box
   around it is a box and nothing else — it was an accent-filled tile with the
   brand's initial in it, and a tile behind a drawing hides the drawing. */
.k-markglyph { display: block; width: 30px; height: 30px; }
.k-markglyph svg { display: block; width: 100%; height: 100%; }

.k-railgroup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s7);
  flex: none;
}
/* The six about the market are the ones that give way, and only they: the
   reader's own two and the foot are what a short window must not take, or the
   account button ends up below the bottom edge. */
.k-rail > .k-railgroup:first-of-type {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: none;
}
.k-rail > .k-railgroup:first-of-type::-webkit-scrollbar { display: none; }
.k-railrule { width: 36px; height: 1px; background: var(--line); }
/* What pushes the reader's own two folds to the bottom edge. */
.k-railspace { min-height: 12px; }

/* `flex: none` stays: the narrowest query gives every entry an equal share of
   the bar instead. */
.k-railitem { flex: none; cursor: pointer; }
/* Estonian is not English, and this is the one number the mail app's rail
   could not be copied at: Turuülevaade and Uusarendused are twice the length
   of Tasks and Files, and neither has a space to break at. So the column is
   ninety-six points rather than Postbox's eighty-four — wide enough for the
   longest word this portal has at ten points — and `Lisa kuulutus`, which
   does have a space, takes the second line. Truncating instead would leave
   four of the eight entries saying `Turuülevaad…`, which is a rail that has
   stopped naming the screens it opens.

   It is the label that is bounded and not the entry, so the same holds when
   the column lies down as a bar; and a word longer than the column breaks
   rather than crossing into the pane. */
.k-raillabel {
  max-width: calc(var(--k-rail) - 10px);
  line-height: 1.2;
  overflow-wrap: break-word;
}
/* The tint is on the icon box and never on the row: a full-width band behind
   a centred icon is what makes a narrow column read as a list again. */
.k-railbox { width: 44px; height: 38px; color: var(--k-rail-ink); }
.k-railbox svg { width: 20px; height: 20px; stroke-width: 1.6; }
.k-railitem:hover .k-railbox { background: var(--chip); color: var(--ink); }
.k-railitem.is-here { color: var(--accent-down); font-weight: 600; }
.k-railitem.is-here .k-railbox { background: var(--chip); color: var(--accent-down); }
/* The one filled entry, and nothing else may take the tint: publishing is the
   only thing on the rail that is not navigation. */
.k-railitem.is-primary { color: var(--accent-down); font-weight: 600; }
.k-railitem.is-primary .k-railbox,
.k-railitem.is-primary:hover .k-railbox {
  background: var(--accent);
  color: var(--accent-ink);
}
/* The plus is the one glyph on the rail that fills its box edge to edge —
   the house, the heart and the chart keep a margin inside theirs — so at the
   rail's twenty it read a size larger than its neighbours, and heavier
   still on the filled ground (2026-09-07: *"plus ikoon is too big compared
   to other icons"*). Sixteen is the same visual weight. */
.k-railitem.is-primary .k-railbox svg { width: 16px; height: 16px; }
/* The ♥'s count rides on the icon rather than on the right edge of the row:
   there is no right edge to push against in a column this narrow, and a
   number that does not widen the entry is a number a phone can keep. */
.k-railbadge {
  top: 2px; right: 3px;
  min-width: 15px; height: 15px;
  /* The size and its leading are one decision: a badge is a disc and wants a
     line that fits inside it, not the ladder's. */
  font-size: var(--t1);
  line-height: 1;
}

/* The rows inside the account's fold, which are a menu and not a rail: they
   keep the row they always were. This is one rule and the rail's is another
   on purpose — they were one, and restyling the column silently restyled
   every row in the popover. */
.k-melink {
  width: 100%;
  min-height: 36px;
  white-space: nowrap;
  cursor: pointer;
}
.k-melink:hover { background: var(--chip); color: var(--ink); }
.k-railicon svg { width: 17px; height: 17px; }

/* ── the reader's own end of it ──────────────────────────────────────────── */

.k-railfoot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s5);
  flex: none;
}

.k-me { position: relative; }
/* The face is the whole control, the way the mail app's is: a name beside it
   is the one row on the rail that could not be a word under an icon. */
.k-menow { width: 44px; height: 38px; cursor: pointer; list-style: none; }
.k-menow::-webkit-details-marker { display: none; }
.k-menow .k-railicon svg { width: 20px; height: 20px; }
.k-menow:hover { background: var(--chip); color: var(--ink); }
.k-me[open] .k-menow { background: var(--chip); color: var(--ink); }

/* Beside the foot and not above it: the column is too narrow to hold a menu,
   so the fold stands clear of it on the right and sits on the bottom edge —
   the mail app's `from-foot`, which is why `.k-rail` may not clip. */
.k-mepop {
  z-index: 40;
  bottom: 0;
  left: calc(100% + 6px);
  width: 214px;
}
/* The two lines truncate rather than wrap, and the pair stays here: the
   layer's `truncate` adds `white-space: nowrap`, which this does not want. */
.k-mewho strong {
  font-size: var(--t5);
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
}
.k-mewho span {
  font-family: var(--mono);
  font-size: var(--t3);
  color: var(--mute);
  overflow: hidden;
  text-overflow: ellipsis;
}
.k-poprule { height: 1px; margin: var(--s2) var(--s3); background: var(--hair); }
/* What the bar has no room for on a phone. One menu and not a second one for
   phones: it is drawn on every screen and shown only where the bar is short
   of width, so an entry added to the rail is added here. */
.k-meover { display: none; }
.k-meover + .k-poprule { display: none; }
/* The way out is a submit inside a form, so it is the one control here that
   is not an anchor — and it wears the same class on the element it actually
   is (UI-15's rule, kept without UI-15's components). */
.k-mesignout { display: contents; }

/* The reader's own colour, off their address and the same in all three
   applications (UI-19). The hue rides on the element; how light it is drawn
   is a token, so this rule names no shade — the same shape the listing
   thumbnails are built with, and for the same reason.

   Under `.k-menow` and not on `.k-face` alone: the agency cards further down
   this sheet give `.k-face` a `--h` of their own, and a bare rule here loses
   to whichever of the two is written last. */
.k-menow .k-face {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 30px; height: 30px; border-radius: var(--r-pill);
  background: oklch(var(--k-face-bg) var(--hue));
  color: oklch(var(--k-face-fg) var(--hue));
  font-size: var(--t3);
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* ── the pane ────────────────────────────────────────────────────────────── */

/* The one thing on the page that scrolls, and the reason the rail does not
   slide away and nothing has to guess a page height. */
.k-pane {
  --k-pane-gap: clamp(var(--s10), 2.4vw, var(--s11));
  gap: var(--k-pane-gap);
  padding: clamp(var(--s9), 2.6vw, var(--s12)) var(--k-gutter) clamp(var(--s10), 3vw, var(--s13));
  overflow-x: hidden;
  scrollbar-gutter: stable;
}

/* **The page's measure, and the way out of it** (KODU-92, widened
   2026-09-06). Everything the pane holds — the content and the foot under it
   — stops at the publish form's 1240 and centres, so a desk-wide window does
   not leave a column of text jammed against the left edge with half the glass
   empty beside it.

   `is-wide` is the door out, and it is a **class a section asks for** rather
   than a list of exceptions kept here: a band of colour, a map, a gallery
   that wants the glass says so on itself, and this sheet does not have to
   know which sections those are.

   The filter bar is the one exception spelt out, because it was flush edge to
   edge before this rule existed (KODU-27) and is drawn by a component that
   every screen shares — it is a bar across the top of the window, not a card
   on the page, and a 1240 cap would have made it a card. */
.k-pane > * {
  width: 100%;
  max-width: var(--k-page);
  margin-inline: auto;
}
/* All three properties go back, not just the cap: the filter bar runs edge to
   edge by *escaping* the pane's padding (KODU-27), so a `width: 100%` of the
   content box pins it inside the gutters it was drawn to cross — and the
   landing's spec measures exactly that overhang. */
.k-pane > .is-wide,
.k-pane > .k-panel,
.k-pane > .k-foot {
  width: auto;
  max-width: none;
  margin-inline: 0;
}

/* ── the measure, per element ────────────────────────────────────────────── */

.k-h1 {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(var(--t10), 3.2vw, var(--t11));
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: pretty;
}
.k-h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--t8);
  font-weight: 500;
  letter-spacing: -0.015em;
}

/* The design's strongest recurring signature: mono, uppercase, letter-spaced,
   in the accent, above nearly every section. */
.k-label {
  margin: 0 0 var(--s6);
  font-family: var(--mono);
  font-size: var(--t3);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.k-link:hover { text-decoration: underline; }

/* ── surfaces ────────────────────────────────────────────────────────────── */

/* A card is the tier's (`ui-card`, UIC-11). Something the pane has to say
   rather than something it holds wears it centred in what is left of the
   frame and no wider than a line worth reading — `stack mx-auto my-auto
   prose` on the element — with a step more air than a card in a column. */
.k-note { padding: clamp(var(--s9), 2vw, var(--s11)); }

.k-empty { border: 1px dashed var(--line2); }

/* ── controls ────────────────────────────────────────────────────────────── */

/* The pill, the submit, the modal's button, the text button and the field
   are the components tier's (UIC-4, `commee-ui-components`), served in front
   of this sheet; what follows here is what the portal adds on top of them. */

/* The language is a *setting*, and it is in the foot with the other one
   (KODU-98): the rail's own fold — a pill saying ET with three more behind
   it — is gone, and with it the whole `.k-lang` control this space used to
   describe. What is left of it here is the option itself, because the phone
   menu still carries the four in a row: a phone has no foot in reach
   (KODU-65). */
.k-langopt:hover { background: var(--chip); color: var(--ink); }
.k-langopt.is-on { background: var(--accent-soft); color: var(--accent); }

/* ── the search panel (SPEC 1.1) ─────────────────────────────────────────── */

/* The prototype's white card, and the one place in the portal a shadow is
   allowed on something that does not float: the panel is the page's subject
   on the landing, and lifting it off the ground is what says so. Everything
   inside it is a real form control. */
.k-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s5);
  padding: clamp(var(--s7), 1.4vw, var(--s8));
}
.k-panel > .k-row { flex-basis: 100%; }
/* The phone's furniture (KODU-64, KODU-65) — the Filtrid pill, the sheet's
   head and foot, the menu and its ✕ — is markup the desktop never shows. */
.k-fbtn, .k-fhead, .k-ffoot, .k-menu, .k-menushut { display: none; }

/* The search panel's own arrangement (KODU-28): every filter on screen,
   flowing left to right and wrapping down. The submit is off the screen
   entirely (KODU-36) — the panel applies itself, and Enter, a screen reader
   and a scriptless browser still have the sr-only button. */
.k-panel.k-stack { flex-wrap: nowrap; align-items: flex-start; }
/* A filter is just its words — no box, no background, no chevron, and no
   padding of its own either: the first word starts where the prose under it
   starts, and the row's gap is what separates the filters. Pressing the
   words is what pops the choices up. The one mark that stays is the area
   picker's way out, which is a whole screen's (KODU-25).

   The open picker is excluded outright rather than left to be overridden
   below (KODU-44). It was not: four classes and an attribute beat the modal's
   three and one, so the head that is supposed to be a screen's heading was
   flattened to a filter in a bar — unpadded against the left edge while the
   tree under it started twenty pixels in. */
.k-panel.k-stack .k-drop:not(.is-modal[open]) > .k-dropbtn {
  border: 0;
  padding: 0;
  background: none;
  box-shadow: none;
}
.k-panel.k-stack .k-dropmark { display: none; }
/* Open as a whole screen, the head's said-text stops being a word in the bar
   and the cross goes back to the right-hand edge it is the way out at. */
.k-panel.k-stack .k-drop.is-modal[open] .k-dropmark { display: flex; margin-left: auto; }
.k-panel.k-stack .k-drop.is-modal[open] .k-dropsaid { max-width: none; }
.k-panel.k-stack .k-dropbtn:hover .k-dropsaid { color: var(--accent); }
/* The label and the value are one size; what separates them is weight and
   ink. An untouched filter is only its quiet name (KODU-34), and a value —
   which only exists once something is chosen — steps forward in the panel's
   own ink. */
.k-panel.k-stack .k-dropname { font-weight: 400; color: var(--mute); }
/* Content-sized in the flow, and still capped: a filter holding five place
   names is one wide word with an ellipsis, not a row of its own. */
.k-panel.k-stack .k-dropsaid { color: var(--ink); flex: none; max-width: 260px; }

/* On the landing and on the results the panel is not a card at all — it is
   the top of the screen (KODU-23, KODU-27): pull out of the pane by exactly
   the padding the pane put there, then put the gutter back *inside*, so the
   bar reaches the glass while its controls still line up with the prose under
   them. */
.k-panel.is-flush {
  align-items: center;
  gap: var(--s4);
  margin: calc(clamp(var(--s9), 2.6vw, var(--s12)) * -1) calc(var(--k-gutter) * -1) 0;
  /* The **box** reaches the glass and the **filters inside it** stop where
     the page does (2026-09-06). The bar is the top of the screen, so its
     ground and its hairline have to run edge to edge; the controls standing
     in it are page content, and on a 2 400px monitor they were strung right
     across the glass while everything under them stopped at 1240.
     `max()` and not a wrapper: the inset is the gutter until the window is
     wider than the measure, and half the difference after that — so the
     filters line up with the listings under them at every width, with no
     element in the markup whose only job is to be narrower.

     The `100%` is the pane's content box — a percentage in a padding
     resolves against the containing block, not against this bar, which is
     two gutters wider than that box because it escapes them. Half the
     difference measured off the box lands one gutter short of where the
     listings start, and the filters stood a gutter wider than the list and
     the foot on every screen wider than the measure where the bar is on top
     (2026-09-09: the dock open on a wide desk, or a desk under 2120). The
     gutter goes back on. */
  padding: var(--s5) max(var(--k-gutter), calc((100% - var(--measure)) / 2 + var(--k-gutter)));
  border-width: 0 0 1px;
  border-radius: 0;
  /* The bar is the top of the page, not a card on it (KODU-34): it wears the
     body's own ground, and the hairline below is what says where it ends. */
  background: var(--page);
}
/* ── the filters and what they narrow (KODU-99) ──────────────────────────── */

/* `panel::browse` boxes what the filters narrow, and the box spaces what it
   holds the way the pane spaces its own children. */
.k-pane > .k-browse-body { gap: var(--k-pane-gap); }

/* ── and a column, where the window is wide enough for one (KODU-99) ─────── */

/* Three readings across, on a desk-wide screen: the rail, the filters, the
   listings. The bar is right for a laptop — it is one row of the page and the
   results start under it — but past about 1400px it was a line of eight
   controls strung across the top of a very wide screen with the listings
   starting a long way below, and the room to its right doing nothing.
   Standing the filters up puts the three columns in the order a person reads
   them: where you are, what you are narrowing by, and what you found.
   `:has()` and not a second render: it is the same markup either way, and a
   panel that had to be told which shape it was in is a screen paying for the
   breakpoint (UIC-6). Its specificity is also what wins the tie against the
   `stack` utility the pane wears, which is served after this sheet. */
/* **The listings are exactly as wide as the foot under them**, which is the
   thing to hold on to. The pane is the grid — not a wrapper round the
   filters — because the foot is the pane's child too, and a foot in a
   different column from the listings is the misalignment this whole shape
   exists to avoid. One content column holds the letterhead, the listings and
   the foot; the filters are the column beside it.

   The two `1fr` edges are the slack, so the pair sits on the page's centre
   line. Pinned to the far left the filters sat against the rail with a
   hand's width of nothing between them and the thing they narrow.

   **2120, and the number is arithmetic rather than taste.** The listings sit
   where content sits on every screen — 1240 centred — and the filters go in
   the gutter *left over*, which has to be 300 wide plus the gap. Two gutters
   of 340 either side of 1240 is 1920 of content box, plus the pane's own
   gutters and the 96 of rail and a scrollbar: about 2120. Below that there is
   no room to put a column without taking it out of the reading, so the bar is
   the right answer and the listings keep their place. A reader walking from a
   listing to a search to a büroo's page never sees the page shift under
   them.

   **And the dock is not in the window's width** (UIC-14). A media query
   measures the window; the assistant takes half of it, so at 2400 with the
   panel open the page has about 1200 to lay out in and the query still said
   "wide" — the filter gutter had nowhere to go and slid under the rail.
   There is no way to ask a query about an attribute, so the shape asks about
   it in the selector instead: with the dock at half or full there is no room
   for a third column and the bar is the right answer, which is exactly what
   the dock's own sheet says a surface may join in on. */
@media (min-width: 2120px) {
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) {
    display: grid;
    grid-template-columns:
      minmax(0, 1fr) minmax(0, var(--measure)) minmax(0, 1fr);
    column-gap: clamp(var(--s10), 2.4vw, var(--s12));
    align-items: start;
  }
  /* Everything the pane holds is content except the filters — the
     letterhead, the listings and the foot — and it all shares one column, so
     it all shares one width. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > * { grid-column: 2; }
  /* **Not the foot.** Its box spans the pane on every other screen and the
     bands *inside* it are what stop at the measure (KODU-50) — so putting
     the box in the middle column here would centre those bands inside a
     column that is itself centred with a gap beside it, and the foot would
     land twenty pixels off where the very same foot lands on a listing
     page. Spanning, it is the same foot everywhere. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-foot { grid-column: 1 / -1; }
  /* A büroo's letterhead is a row of its own above the listings (KODU-95),
     and the filters stand beside both. Spanning, not placed in the first row
     alone: that sized the row to the filters and dropped the listings an inch
     and a half below the brand. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush):has(> .k-browse-head) > .k-panel.is-flush { grid-row: 1 / span 2; }
  /* It stops being the top of the page, so it gives back the full-bleed
     ground, the hairline and the negative margins that made it one. The
     leading `.k` is also what wins the tie against the büroo letterhead's own
     rule further down, which is five classes deep and comes later.

     Not `sticky`. A column of eight filters that followed the scroll was the
     obvious next thought and it does not survive contact: the pane is what
     scrolls, so a sticky child re-sticks under anything trying to scroll one
     of its filters into view, and the filter lands somewhere else. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush {
    /* The gutter beside the reading, hugging its left edge. The listings do
       not move to make room — they are where content is on every other
       screen in the portal, so walking from a listing to a search to a
       büroo's page does not shift the page under the reader. */
    grid-column: 1;
    justify-self: end;
    /* The listings' row and no further: the foot is auto-placed in the first
       row every column leaves free, so it lands under both with one gap. */
    grid-row: 1;
    align-self: start;
    width: 300px;
    max-width: none;
    margin: 0;
    padding: 0;
    border-width: 0;
    background: none;
    align-items: stretch;
  }
  /* One filter to a line, each as wide as the column. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush .k-fstack {
    flex-direction: column;
    align-items: stretch;
  }
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush .k-drop,
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush .k-dropbtn { width: 100%; }
  /* **The chosen value stands under the name** (2026-09-07). In the bar a
     button is one line — name, value, chevron — and the value sits at the
     far end of it. In a column that end is the edge nearest the listings,
     so every value the reader had chosen was crammed against the very
     things it narrows while the column's own left half stood empty:
     *"it goes 0px to right side where kuulutused are … bring üür under
     Tehingu tüüp"*. Two lines, both at the left: the name with the chevron
     after it, and what it is set to under that. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush .k-dropbtn {
    flex-wrap: wrap;
    align-items: baseline;
    row-gap: var(--s1);
    padding-block: var(--s3);
  }
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush .k-dropname { flex: 1 1 auto; }
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) > .k-panel.is-flush .k-dropsaid {
    order: 1;
    flex: 1 1 100%;
  }
  /* **An open filter pushes the others down rather than covering them.** In
     the bar a popover hangs below the row and lands on the page; in a column
     it lands on the filters underneath it, and a filter you cannot press
     because another one is open over it is worse than no column at all. So
     in this shape the choices are in the flow — the accordion a sidebar of
     filters has always been — and they keep the bounded height they have
     everywhere, so a long list still scrolls itself rather than pushing the
     rest of the column off the screen.

     Not the area picker: `is-modal` open is the whole screen (KODU-25) and
     has nothing to do with the column it was opened from. */
  html:not([data-dock="half"]):not([data-dock="full"]) .k .k-pane:has(> .k-panel.is-flush) .k-drop:not(.is-modal) > .k-droppop {
    position: static;
    width: auto;
    min-width: 0;
    max-width: none;
    box-shadow: none;
    border-radius: 0 0 var(--r6) var(--r6);
  }
}

/* Flat, which is the other half of "no rounded stuff": nothing standing in
   the bar is a lozenge either. Compact is fewer pixels of air and never
   smaller type — every button keeps its 13.5px, and Otsi comes down to the
   38px the filters are rather than the filters coming up to it. */
.k-panel.is-flush .k-dropbtn,
.k-panel.is-flush .k-drop[open] .k-dropbtn,
.k-panel.is-flush .k-droppop { border-radius: 0; }
.k-panel.is-flush > .k-row { flex-basis: auto; }

/* **A flush panel that follows a letterhead is not the top of the page**
   (2026-09-06). The rules above are written for a bar that *is* the top: a
   negative top margin pulls it into the pane's own padding and a negative
   inline one carries it across the gutters. Standing after a bureau's brand
   it did both — climbing over the letterhead and running wider than every
   other block on a page that stops at 1240.

   No new flag for it: the condition is exactly what the selector says, and
   the bureau page is the one screen where the panel has something above it.
   What survives is the *look* — flat, no card, hairlines rather than a
   border — which is the manner the rest of the page took from the editor;
   what goes is the climbing. A rule over it as well as under, so it reads as
   a band between two lines like every other section here. */
/* The letterhead is inside the browse body now (KODU-99) and no longer a
   sibling of the panel, so the condition is "this browse has a letterhead in
   it" rather than "a letterhead is directly before this panel". Same rule,
   same reason — the two adjacency spellings are kept for anything still
   drawing the old shape. */
.k-browse-head + .k-chk + .k-fbtn + .k-panel.is-flush,
.k-bureau + .k-chk + .k-fbtn + .k-panel.is-flush,
.k-bureau + .k-panel.is-flush {
  width: 100%;
  max-width: var(--k-page);
  margin-block: 0;
  margin-inline: auto;
  padding-inline: 0;
  border-width: 1px 0;
  border-color: var(--line2);
}


/* ── chips ───────────────────────────────────────────────────────────────── */

/* Every chip, every tile and every tree node is one of these: a real input,
   off the screen but not out of the document, so a keyboard reaches it and a
   screen reader announces it. What is visible is the label next to it. */
.k-chk {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

.k-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  height: 34px;
  padding: 0 var(--s6);
  border: 1px solid var(--line2);
  border-radius: var(--r5);
  background: var(--surface);
  color: var(--ink2);
  font-size: var(--t5);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}
.k-chip:hover { border-color: var(--accent); color: var(--accent); }
.k-tick { display: none; width: 13px; height: 13px; flex: none; }
.k-chk:checked + .k-chip {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}
.k-chk:checked + .k-chip .k-tick { display: block; }
.k-chk:focus-visible + .k-chip,
.k-chk:focus-visible + .k-seg-opt,
.k-chk:focus-visible + .k-arow .k-acheck { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The deal control: a row of radios, each wearing a segment. */
.k-deal .k-seg-opt {
  font-family: var(--sans);
  font-size: var(--t5);
  padding: var(--s3) var(--s7);
  cursor: pointer;
  user-select: none;
}
.k-chk:checked + .k-seg-opt { background: var(--accent-soft); color: var(--accent); }

/* ── bands ───────────────────────────────────────────────────────────────── */

.k-num { width: 88px; }
.k-num:focus-visible { outline: none; border-color: var(--accent); }
.k-band.is-small .k-num { width: 62px; }
.k-band.is-medium .k-num { width: 80px; }
.k-band.is-large .k-num { width: 100px; }

/* ── the area picker (SPEC 1.2, KODU-25) ─────────────────────────────────── */

/* The picker fills the modal's body row: the list on the left, the map on the
   right, and the committing press across the bottom of both. It is never
   anywhere else, so it positions nothing and floats above nothing — the
   surface it is drawn on is `.k-drop.is-modal[open]`. */
.k-apop {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  grid-template-rows: minmax(0, 1fr) auto;
  min-height: 0;
}
.k-atree {
  min-height: 0;
  overscroll-behavior: contain;
  padding: var(--s6) clamp(var(--s4), 1.4vw, var(--s9));
  border-right: 1px solid var(--hair);
}
/* Half a screen is not a measure. The count sits at the end of its row, so a
   row as wide as the column puts a name and its number a hand's width apart. */
.k-atree > * { max-width: 34rem; }
/* Under both columns and against the bottom of the screen, because the press
   that finishes a full-screen surface is the last thing on it. */
.k-afoot {
  grid-column: 1 / -1;
  padding: var(--s6) clamp(var(--s7), 2vw, var(--s10));
}

/* Typing in the picker narrows it (SPEC 1.2). A hundred and eleven places is
   a list nobody scrolls, and the box the prototype drew was a placeholder
   people typed into while nothing happened. It sticks to the top of the
   list's own scroll, because a filter that scrolls away is a filter you
   cannot clear without scrolling back. */
.k-asift {
  top: -12px;
  z-index: 2;
  width: 100%;
  height: 32px;
}
.k-asift:focus { outline: none; border-color: var(--accent); }
.k-asift::placeholder { color: var(--mute); }
/* The other half of the same control, and now the very map every other
   screen draws (KODU-36): the reusable component fills the column, pressing
   a county's number pulls it full, and the tree on the left is what picks. */
.k-amap { min-height: 0; }
.k-pickmap .mp-page { position: absolute; }
/* What a press means here (KODU-44). The search's map zooms on a press and
   this one picks, so the map that is a control says which it is — over the
   drawing, out of the way of the hand doing the picking. */
.k-amaphint {
  z-index: 3;
  left: 12px;
  top: 12px;
  pointer-events: none;
}

/* ── a filter, as a button that opens (KODU-20) ───────────────────────────── */

/* The panel was forty controls in eight rows, so the answer to "what am I
   searching for" was to read all of them. A filter is one question, so it is
   one button: its name, what it is set to, and the choices inside. */
.k-dropbtn {
  display: flex;
  align-items: center;
  gap: var(--s4);
  max-width: 100%;
  min-height: 38px;
  padding: 0 var(--s5) 0 var(--s6);
  border: 1px solid var(--line2);
  border-radius: var(--r6);
  background: var(--surface);
  font-size: var(--t5);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.k-dropbtn::-webkit-details-marker { display: none; }
.k-dropbtn:hover { border-color: var(--accent-line); }
/* What it is set to, which is the half a reader is actually scanning. It
   truncates rather than wrapping: a filter button that grows to three lines
   pushes the rest of the panel about every time somebody ticks something. */
.k-dropsaid {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--mute);
}
.k-dropbtn.is-on { border-color: var(--accent-line); background: var(--chip); }
.k-dropbtn.is-on .k-dropsaid { color: var(--accent); font-weight: 600; }
.k-dropmark svg { width: 15px; height: 15px; }
.k-drop[open] .k-dropmark { transform: rotate(180deg); }
/* Open, the button and its panel are one surface — which is what makes the
   chips inside read as being *in* the button. */
.k-drop[open] .k-dropbtn {
  border-color: var(--line);
  border-bottom-color: transparent;
  border-radius: var(--r6) var(--r6) 0 0;
  background: var(--surface);
}

.k-droppop {
  position: absolute;
  z-index: 45;
  top: calc(100% - 1px);
  left: 0;
  min-width: 100%;
  width: max-content;
  max-width: min(30rem, 88vw);
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: 0 var(--r6) var(--r6) var(--r6);
  background: var(--surface);
  box-shadow: var(--shadow);
}
/* And it stops before the window does (2026-09-06). A filter with a long
   list — the types, the extras, the condition — opened a box as tall as its
   options and let it run off the bottom of the screen: the choices below the
   fold were unreachable, because the page behind it does not scroll
   (`html, body { overflow: hidden }`) and the box had no scroll of its own.
   So it is bounded and scrolls itself, which is what every other column in
   this application does.

   Not the picker: `is-modal` open *is* the screen (KODU-25), and it manages
   its own height between a fixed head and the tree under it. */
.k-drop:not(.is-modal) > .k-droppop {
  max-height: min(60dvh, 480px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* A filter near the right edge of the bar opens the other way (the drop
   script measures and says so), and the square corner moves to the corner
   that is anchored. */
.k-drop.is-flip > .k-droppop {
  left: auto;
  right: 0;
  border-radius: var(--r6) 0 var(--r6) var(--r6);
}

/* What is already chosen, each with the way to take it off. */
.k-dropnow { margin-bottom: var(--s4); }
/* The chip's padding is not symmetrical — the word sits away from the left
   edge and the cross sits close to the right — and the ladder has no
   single-side inline step. */
.k-dropchip {
  padding: var(--s1) var(--s2) var(--s1) var(--s5);
  white-space: nowrap;
}
.k-dropx:hover { background: var(--surface); color: var(--danger); }
.k-dropx svg { width: 11px; height: 11px; }

/* ── a filter that opens onto the whole screen (KODU-25) ─────────────────── */

/* Open, the fold *is* the screen: a `<details>` and not a `<dialog>`, because
   `showModal()` needs a script and this portal's budget is spent (KODU-20) —
   which is also why the head is the summary, the only thing that can shut a
   `<details>` with none. It clears the rail and stays under the skip link.

   Head and body are each fixed against the screen rather than two rows of one
   grid: a `<details>` in a current browser puts its contents in a
   `::details-content` box of its own, so a row handed to `.k-droppop` goes to
   that box instead and the picker ends up a tall popover on a blank modal. */
.k-drop.is-modal[open] {
  --k-pickhead: 56px;
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--surface);
  overscroll-behavior: contain;
}
/* The head of a screen, and the gutter is the tree's own (KODU-44): the
   heading has to start where the list under it starts, or the screen reads as
   two things that were laid out separately. */
.k-drop.is-modal[open] > .k-dropbtn {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  height: var(--k-pickhead);
  padding: 0 clamp(var(--s4), 1.4vw, var(--s9));
  border-width: 0 0 1px;
  border-radius: 0;
  border-color: var(--line);
  background: var(--surface);
  font-size: var(--t6);
}
/* And it is the screen's heading rather than a filter's quiet label
   (KODU-44): the panel's own muted 400 still reaches in here, and drew
   "Piirkonnad" as the faintest thing on a screen it names. */
.k-drop.is-modal[open] .k-dropname {
  font-family: var(--serif);
  font-size: var(--t8);
  font-weight: 500;
  letter-spacing: -0.015em;
  color: var(--ink);
}
/* Which of the two marks the head carries. Both are in the markup because
   nothing on the server knows whether the fold is up — a `<details>` is always
   sent shut — so the sheet is what picks, and it picks by state.

   Shut, this filter is standing in the bar between seven others and says what
   they say: *this opens*. Open, there is a screen to leave and the mark is the
   way out. `display: none` and not `visibility` or an opacity, so the half that
   is not showing leaves the accessibility tree with its word — a shut button
   announcing "Sulge" names a gesture that is not available. */
.k-drop.is-modal:not([open]) .k-markout { display: none; }
.k-drop.is-modal[open] .k-markshut { display: none; }
/* Open, the mark is the way out and not a chevron, so it is a target and it
   does not turn over. */
.k-drop.is-modal[open] .k-dropmark {
  margin-left: var(--s4);
  padding: var(--s4);
  border-radius: var(--r5);
  transform: none;
  color: var(--ink3);
}
.k-drop.is-modal[open] .k-dropmark:hover { background: var(--chip); color: var(--ink); }
.k-drop.is-modal[open] .k-dropmark svg { width: 16px; height: 16px; }
.k-drop.is-modal[open] > .k-droppop {
  position: fixed;
  inset: var(--k-pickhead) 0 0;
  z-index: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  width: auto;
  max-width: none;
  padding: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
/* Both rows are named, because the chips row is only drawn once something is
   chosen — left to place itself, the picker took the `auto` row on an empty
   filter and sat at the top of a modal that was blank underneath. */
.k-drop.is-modal[open] .k-dropnow {
  grid-row: 1;
  margin: 0;
  padding: var(--s5) clamp(var(--s7), 2vw, var(--s10));
  border-bottom: 1px solid var(--hair);
}
.k-drop.is-modal[open] .k-dropopts {
  grid-row: 2;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
  gap: 0;
}

/* ── the area tree ───────────────────────────────────────────────────────── */

.k-tree { margin: 0; padding: 0; list-style: none; }
.k-tree .k-tree { margin: var(--s1) 0 var(--s2) var(--s5); padding-left: var(--s5); border-left: 1px solid var(--hair); }
/* `display: flex` beats the `hidden` attribute, and a filter whose hidden
   rows are still on the screen is a filter that does nothing. Four screens in
   this tree have shipped with exactly this. */
.k-node[hidden] { display: none; }
/* The node that actually matched, as against the parents kept so it can be
   reached. */
.k-node.is-sifted > .k-arow .k-aname { color: var(--accent); }
/* The whole row is the checkbox's label (KODU-63): one press target from
   the drawn box to the count, tall enough for a thumb. */
.k-arow {
  display: flex;
  align-items: center;
  gap: var(--s4);
  min-height: 38px;
  padding: var(--s1) var(--s2);
  border-radius: var(--r4);
  cursor: pointer;
}
.k-arow:hover { background: var(--chip); }
/* Every row keeps clear of the fold's slot — on leaves too, so the counts
   stand in one column whether or not a chevron follows them. */
.k-arow { width: calc(100% - 44px); box-sizing: border-box; }
.k-acheck {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: none;
  border: 1.5px solid var(--line2);
  border-radius: var(--r3);
  background: var(--surface);
  color: var(--accent-ink);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.k-acheck .k-tick { width: 14px; height: 14px; }
.k-chk:checked + .k-arow .k-acheck { border-color: var(--accent); background: var(--accent); }
.k-chk:checked + .k-arow .k-acheck .k-tick { display: block; }
/* The fold is the chevron alone, standing where the row left room for it —
   opening a county's towns and picking the county are two targets on one
   line, never one press doing both. */
.k-akids > summary.k-afold {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 38px;
  border-radius: var(--r4);
  color: var(--ink3);
  cursor: pointer;
  list-style: none;
}
.k-afold::-webkit-details-marker { display: none; }
.k-afold:hover { background: var(--chip); color: var(--accent); }
.k-afold svg { width: 16px; height: 16px; transition: transform 0.12s ease; }
.k-akids[open] > .k-afold svg { transform: rotate(180deg); }
.k-aname {
  flex: 1;
  min-width: 0;
  color: var(--ink2);
}

/* ── the price presets and the button ────────────────────────────────────── */

.k-preset:hover { border-color: var(--accent); color: var(--accent); }
.k-preset.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

/* ── the results (SPEC 1.3) ──────────────────────────────────────────────── */

/* The narrow query below turns both of these round, so neither can move onto
   the element: a utility would win the tie and the row would stay wrapped. */
.k-rtools { margin-left: auto; flex-wrap: wrap; }

/* The two views of one search, as one switch (KODU-32). Each half is a link
   carrying the whole search, and the current one is the tinted one. */
.k-vopt { min-height: 26px; white-space: nowrap; }
.k-vopt:hover { color: var(--accent); }
.k-vopt.is-on { background: var(--chip); color: var(--accent); }

/* The search, drawn, standing where the list would (KODU-32): full width
   under the switch, tall enough to be the screen's subject, and carrying
   nothing on it but the zoom buttons — the un-sum card a pressed pin opens
   is the one thing allowed to float over it. */
.k-results.is-map .k-rhead { margin-bottom: var(--s7); }
.k-mapview {
  position: relative;
  height: calc(100vh - 250px);
  min-height: 440px;
  overflow: hidden;
  background: var(--surface);
}
.k-mapview .mp-page { position: absolute; }
/* ⤢ means the whole screen here (KODU-49), so the component's own
   pull-the-country-full button steps aside for Kodu's. */
.k-mapview .mp-all { display: none; }
.k-mapfull {
  position: absolute;
  right: 16px;
  bottom: 130px;
  z-index: 5;
  width: 36px;
  height: 36px;
  border: 1px solid var(--line);
  border-radius: var(--r3);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--t4);
  cursor: pointer;
}
.k-mapfull:hover { background: var(--page); }
.k-mapview.is-full {
  position: fixed;
  inset: 0;
  z-index: 400;
  height: auto;
  min-height: 0;
  border: 0;
  border-radius: 0;
}
/* The map view has no box (KODU-32), so its search column is an empty strip
   over the drawing: the map is dragged straight through it. The card is not in
   it — it hangs off the map itself — so nothing here has to be excepted. */
.k-mapview .mp-search { pointer-events: none; }

/* The same answer, drawn (KODU-17). It stands beside the sort and the layout
   because it is the third thing you can do to a page of results and not a
   fourth kind of control. */
.k-rtool {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  height: 32px;
  padding: 0 var(--s6);
  border-radius: var(--r6);
  background: var(--chip);
  font-size: var(--t4);
  font-weight: 600;
  color: var(--ink3);
}
.k-rtool:hover { color: var(--accent); }
.k-rtool svg { width: 15px; height: 15px; }

/* The sort, one dropdown whose face says the order in force (KODU-34). */
.k-sortdrop .k-dropsaid { flex: none; }
.k-sopt:hover { background: var(--chip); color: var(--ink); }
.k-sopt.is-on { background: var(--accent-soft); color: var(--accent); }

.k-lay { width: 30px; height: 26px; }
.k-lay svg { width: 15px; height: 15px; }
.k-lay:hover { color: var(--ink); }
.k-lay.is-on { background: var(--surface); color: var(--accent); }

/* The grid fills whatever the pane gives it — a wider window is more listings
   per row and not wider cards (S1's first departure). The gap is the widest
   step on the scale because nothing else holds the cards apart now that they
   have no frame (KODU-102). */
.k-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(236px, 1fr)); gap: var(--s11); }

/* A card and its heart are siblings, because a form inside a link is invalid
   markup and a browser repairs it by throwing one of the two away. The wrapper
   is what lets the heart sit over the picture anyway. Not the layer's `row`:
   stretch is what makes a whole row of cards one height. */
/* On the picture, in the corner the badges are not in — where every listing
   site has put it since there were listing sites. It was pinned 92px down,
   which was the picture's height written out as a number, and it slid onto the
   address the moment the picture grew. */
.k-cardw .k-heartf { position: absolute; top: 10px; right: 10px; }

/* A card has no edge (KODU-102): the picture and the words stand on the
   surface, and the pointer lifts the surface rather than lighting a frame —
   to white in the light scheme (2026-09-07: *"hoverdades list item läheb
   full white"*), a step up in the dark. */
.k-card2:hover { background: var(--k-card-hover); }

/* The listing the reader just came back from (KODU-30): the arrow's fragment
   lands on its own card and `:target` lights it — a little lift — so the eye
   finds where it left off without reading the page again. */
.k-cardw:target .k-card2,
.k-roww:target .k-lrow {
  box-shadow: var(--shadow);
}

/* The heart itself: a round button on a card, and a labelled pill on a
   listing page. Filled means saved — the shape is the same either way, which
   is what makes the two states one control rather than two icons. */
.k-heart { width: 30px; height: 30px; border-radius: 50%; }
.k-heart:hover { color: var(--danger); border-color: var(--danger); }
.k-heart svg { width: 15px; height: 15px; }
.k-heart.is-on { color: var(--danger); border-color: var(--danger); }
.k-heart.is-on svg,
.ui-pill.is-on svg { fill: currentColor; }
.ui-pill.is-on { border-color: var(--danger); color: var(--danger); }

/* A listing's own colour, until there are photographs (KODU-10). The hue comes
   from the markup; the two tokens above decide everything else about it, and
   nothing here names a shade. */
/* A ratio and not a height: the card is as wide as the grid gives it, and a
   picture that is 128px tall at every width is a letterbox on a wide screen
   and a stamp on a narrow one. Three by two is what a property photograph is
   shot in, so the slot is the right shape for the day there are photographs
   in it (KODU-10). */
.k-thumb {
  --hue: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 3 / 2;
  background: oklch(var(--k-thumb-bg) var(--hue));
  color: oklch(var(--k-thumb-fg) var(--hue));
}
.k-thumb svg { width: 34px; height: 34px; }
/* A photograph fills the same slot, and the tint stays behind it: a picture
   that has not arrived yet is the listing's own colour, not a white hole. */
.k-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The price is what a card is scanned by, so it leads and it is the largest
   thing on it. The unit is a unit: it belongs to the number and is not a
   second fact competing with it. The two sizes stay here because each surface
   overrides them below, and a number wants its own leading and not the
   ladder's. */
.k-pnum { font-size: var(--t8); letter-spacing: -0.01em; }
.k-punit { font-size: var(--t5); }

/* Not the layer's `row` either: two badges that wrap differently are still
   one height. */
.k-badges { position: absolute; top: 10px; left: 10px; display: flex; }
/* The size stays here: a badge's box is its padding and the line it inherits,
   and the ladder's leading at this step is not that line. */
.k-badge { font-size: var(--t3); }

/* One per line. The badges stop floating here — there is no picture for them
   to sit on top of, so they sit beside the address instead. The border is on
   the wrapper rather than the link, so the heart is inside it — and the
   right-hand padding, which the ladder has no single-side inline step for, is
   what keeps the heart off the edge. */
.k-roww { padding-right: var(--s5); }
.k-roww:hover { border-color: var(--accent-line); }
.k-lrow .k-thumb { flex: none; width: 56px; height: 56px; border-radius: var(--r4); }
.k-lrow .k-thumb svg { width: 22px; height: 22px; }
.k-lrow .k-badges { position: static; }
.k-lrow .k-badge { border-radius: var(--r3); }
.k-lrow .k-pnum { font-size: var(--t6); }

/* ── the pager ───────────────────────────────────────────────────────────── */

.k-step {
  width: 34px;
  height: 34px;
  /* The pager's arrows are the same family as the back button (KODU-51). */
  border-radius: 50%;
}
.k-step svg { width: 14px; height: 14px; }
.k-step:hover { border-color: var(--accent); color: var(--accent); }
.k-step.is-spent { color: var(--faint); border-color: var(--hair); }

/* ── My account (SPEC 6) ─────────────────────────────────────────────────── */

.k-atitle { margin-right: auto; }

/* The profile's own doors across the top of every profile screen (KODU-73):
   pills in a row, the one the reader is on filled with the accent. The rail's
   fold lists the same three; this is them again where the profile is open. */
.k-ptab {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 var(--s6);
  border: 1px solid var(--line2);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink2);
  font-size: var(--t5);
  font-weight: 600;
  white-space: nowrap;
}
.k-ptab:hover { border-color: var(--accent); color: var(--accent); }
.k-ptab.is-here { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* A month's lines on the invoices screen: what, what it cost, what a code
   took off, what is owed — the numbers in the mono face, right-aligned. */
.k-invline { padding: var(--s3) 0; border-top: 1px solid var(--line); }
.k-invline:first-of-type { border-top: 0; }
.k-invwhat { flex: 1 1 200px; }
.k-invlist { text-decoration: line-through; }
.k-invpay { min-width: 84px; text-align: right; }

/* A person's initials, in their own colour — `ui::face`'s hue, so somebody is
   the same colour here, in the console and in the mailbox (UI-19). */
.k-face {
  --h: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: var(--r5);
  background: oklch(var(--av-bg) var(--h));
  color: oklch(var(--av-fg) var(--h));
  font-size: var(--t5);
  font-weight: 600;
}
.k-face.is-big { width: 48px; height: 48px; border-radius: var(--r7); font-size: var(--t7); }
/* A bureau's logo (KODU-80): the picture wide, at its own proportions, on
   the portal's paper — a wordmark set in black draws on white in both
   schemes, which is what the paper token is for. Never a circle.

   **And no corner of ours** (2026-09-06): the rounding was this portal
   drawing a card behind somebody else's mark, and a wordmark with our radius
   cut into its own square corners is not their logo any more. The paper stays
   — a black wordmark needs white under it in the dark scheme — and it is the
   plain rectangle the mark was drawn on. */
.k-logo {
  display: inline-flex;
  align-items: center;
  flex: none;
  height: 34px;
  max-width: 180px;
  padding: var(--s2) var(--s3);
  background: var(--k-paper);
}
.k-logo.is-big { height: 48px; max-width: 260px; padding: var(--s3) var(--s4); }
.k-logopic { display: block; height: 100%; width: auto; max-width: 100%; object-fit: contain; }
/* A photograph fills the same slot and covers the initials: the face keeps
   its shape and its corner, whatever the picture's own. */
.k-face .k-facepic {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The settings screen's face is the control for changing it (SPEC 3.2): a
   proper portrait, the file input laid invisibly over the whole of it so a
   press anywhere on the face opens the picker with no script at all, a
   camera showing through on hover, and a ring while a file is being dragged
   over it. `is-busy` is the moment between choosing and the page coming
   back with the new picture. */
.k-facepick .k-face.is-big { width: 96px; height: 96px; font-size: var(--t9); }
.k-facefile {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.k-faceveil {
  position: absolute;
  inset: 0;
  border-radius: var(--r7);
  background: var(--k-light-veil);
  color: var(--k-light-fg);
  opacity: 0;
  transition: opacity .15s ease;
  pointer-events: none;
}
.k-faceveil svg { width: 28px; height: 28px; }
.k-facedrop:hover .k-faceveil,
.k-facedrop:focus-within .k-faceveil,
.k-photorow.is-over .k-faceveil,
.k-photorow.is-busy .k-faceveil { opacity: 1; }
.k-photorow.is-over .k-facedrop { outline: 2px solid var(--accent); outline-offset: 2px; }
.k-photorow.is-busy .k-facepill { visibility: hidden; }

/* Where something is in its own life: on the market, held back, or waiting for
   money. Three tones, and the third is the attention one because it is the
   only state its owner has to do something about (CTX-9). The shape is
   `looks::STATE_CHIP`. */
.k-chipst.is-live { background: var(--ok-bg); color: var(--ok-ink); }
.k-chipst.is-held { background: var(--chip); color: var(--ink3); }
.k-chipst.is-owed { background: var(--warn-bg); color: var(--warn-ink); }
.k-chipst.is-refused { background: var(--danger-soft); color: var(--danger); }

/* The operator's word (KODU-107, KODU-108): the refusal's reason opens under
   its press, over the row rather than pushing it down, and the press itself
   wears the danger the word carries. */
.k-deny > summary { list-style: none; cursor: pointer; color: var(--danger); border-color: var(--danger); }
.k-deny > summary:hover { background: var(--danger-soft); }
.k-deny > summary::-webkit-details-marker { display: none; }
/* The one surface on the queue: a popover over the row, not a box under it
   (KODU-110), so its ground and edge are the sheet's and not a utility's. */
.k-denyform {
  position: absolute; right: 0; z-index: 5;
  top: calc(100% + var(--s3));
  width: min(420px, calc(100vw - var(--s11)));
  padding: var(--s6);
  border: 1px solid var(--line2);
  border-radius: var(--r6);
  background: var(--surface);
  box-shadow: var(--shadow);
}
/* The word over a waiting listing's own page: a line across the top, under
   a rule, the way the editor's bar stands. */
.k-reviewbar { margin-bottom: var(--s8); padding-block: var(--s5); border-bottom: 1px solid var(--line2); }

/* ── the editor's manner, on every screen that manages something (KODU-110) ──
   Lisa kuulutus's column of numbered sections, without the pictures beside
   it: `.k-editor.is-one` is the same grid with one column, and what stands
   in it is `.k-edsec` from the editor. A head over the sections, a form that
   holds its sections without being a box round them, rows under hairlines,
   and figures in a strip — none of it a card. */
.k-editor.is-one { grid-template-columns: minmax(0, 1fr); }
.k-sheetform { display: contents; }
.k-sheethead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s6) var(--s8);
  padding-bottom: clamp(var(--s8), 2vw, var(--s10));
  border-bottom: 1px solid var(--line2);
}
.k-lines > * { padding-block: var(--s6); border-top: 1px solid var(--line); }
.k-lines > :first-child { padding-top: 0; border-top: 0; }
.k-lines > :last-child { padding-bottom: 0; }
.k-figures { display: flex; flex-wrap: wrap; gap: var(--s6) var(--s11); }
.k-figures > * + * { padding-left: var(--s11); border-left: 1px solid var(--line); }

/* The contexts this login can act as (CTX-15): rows, the one being acted in
   marked down its leading edge. CTX-12: the accent paints that edge, and an
   agency's is its own slot from the CTX-11 table. The person context has no
   slot and takes --accent, because being yourself is the default state. */
.k-ctx:hover { color: var(--accent); }
.k-ctx.is-on { padding-left: var(--s5); box-shadow: inset 3px 0 0 var(--accent); }
.k-ctx.is-on[data-slot="1"] { box-shadow: inset 3px 0 0 var(--k-accent-1); }
.k-ctx.is-on[data-slot="2"] { box-shadow: inset 3px 0 0 var(--k-accent-2); }
.k-ctx.is-on[data-slot="3"] { box-shadow: inset 3px 0 0 var(--k-accent-3); }
.k-ctx.is-on[data-slot="4"] { box-shadow: inset 3px 0 0 var(--k-accent-4); }
.k-ctx.is-on[data-slot="5"] { box-shadow: inset 3px 0 0 var(--k-accent-5); }
.k-ctx.is-on[data-slot="6"] { box-shadow: inset 3px 0 0 var(--k-accent-6); }

/* The listing rows, which are the point of the screen. */
.k-mthumb .k-thumb { width: 78px; height: 58px; border-radius: var(--r4); }
.k-mthumb .k-thumb svg { width: 22px; height: 22px; }
.k-mmid { flex: 1 1 200px; }
.k-mmid p { margin: 0; }

/* The edge, the ground and the ink stay here: Maksa and ✕ each repaint them,
   and a utility on the element would beat the rules that do the repainting. */
.k-mact {
  height: 32px;
  padding: 0 var(--s6);
  border: 1px solid var(--line2);
  background: var(--surface);
  color: var(--ink2);
  white-space: nowrap;
}
.k-mact:hover { border-color: var(--accent); color: var(--accent); }
.k-mact svg { width: 13px; height: 13px; }
.k-mact.is-owed { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }
.k-mact.is-danger { padding: 0 var(--s4); color: var(--mute); }
.k-mact.is-danger:hover { border-color: var(--danger); color: var(--danger); }

/* ── the listing page (SPEC 2.2) ─────────────────────────────────────────── */

/* The title takes the room, so the ♥ beside it sits against the right-hand
   edge rather than against the address (KODU-42): keeping a listing is done
   *to* the listing, and what a listing is is its address. */
.k-ltitle { flex: 1; }
/* The ♥ is the `<form>`, not the pill inside it — the rule named the pill and
   matched nothing, which is why nothing seemed to depend on it (KODU-46). */
.k-lhead2 .k-heartf { flex: none; }
.k-h1.is-page { font-size: clamp(var(--t8), 3vw, var(--t10)); }
/* An Estonian address can be forty unbreakable characters, and on a phone
   `min-width: 0` only lets the box shrink — the words then run out of it and
   under the ♥ beside them. Break inside the word rather than over the
   control. */
.k-lhead2 .k-h1.is-page { overflow-wrap: anywhere; }
.k-back {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  /* A circle, not a rounded square (KODU-51). It is a square box with a 10px
     radius everywhere else, which at 34px reads as neither one thing nor the
     other — and it sits beside the round ♥ and the round map controls, so the
     inconsistency is visible on the very screens it appears on. */
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink3);
}
.k-back:hover { border-color: var(--accent); color: var(--accent); }
.k-back svg { width: 16px; height: 16px; }

/* Two columns that become one on a narrow screen: the picture and the place
   on the left, everything a reader *reads* on the right (KODU-42) — the
   price, the numbers, the words and who is selling, in the order those
   questions get asked. The two widths are measures and not steps. */
.k-lmain { flex: 1 1 380px; }
.k-lside { flex: 1 1 360px; }
/* And air between them (2026-09-07): the pictures, the reading and the map
   stood sixteen apart — *"picture and details and map are really stuck
   together"* — which read as one block once the frames came off (KODU-102).
   The two columns are a gutter apart, and the pictures' own stack — gallery,
   plan, tour, map — the widest step of the scale. */
.k-lbody.row { gap: var(--s11) clamp(var(--s11), 3vw, var(--s13)); }
.k-lbody > .k-lmain { gap: var(--s11); }

/* And on a wide screen the reading column keeps pace with the pictures
   (2026-09-06). Scrolling through a gallery of twenty photographs used to
   leave the price, the facts and the way to reach the seller somewhere above
   the top of the window — the reader is looking at the flat and the answer to
   "how much, and who do I call" has scrolled off.

   `.start` on the row is what makes this possible at all: `align-items:
   flex-start` gives the column its own content height rather than the row's,
   and a sticky box the height of its row has nowhere to travel.

   Never a scroller of its own (2026-09-09). It was capped at the window and
   scrollable, with `overscroll-behavior: contain`, because the description
   lives in here and is as long as the seller made it — and a wheel over the
   description then moved the description and never the page: *"mouse scroll
   does not work for full page in kuulutus when i am on details description
   on the right side"*. The page is the one thing that scrolls, wherever the
   pointer is. A column taller than the window sticks by its *foot* instead
   of its head (`is-tall`, stamped by the column's own script, because a
   sheet cannot know a column's height): it travels with the page until the
   seller's card at the bottom is in view, and stays there — so nothing in it
   is unreachable, and nothing in it catches the wheel. */
@media (min-width: 861px) {
  .k-lside {
    position: sticky;
    top: var(--s6);
    align-self: flex-start;
  }
  .k-lside.is-tall { top: auto; bottom: var(--s6); }
}

/* A hand on a touch screen scrolls the page by dragging, and a column that
   catches the drag is a page that will not move. Nothing sticks there. */
@media (hover: none) {
  .k-lside { position: static; }
}

/* Where it stands, under the photographs (KODU-42). Tall enough to read the
   block it is in, and the component's own zoom is the only control on it. */
.k-onehouse {
  position: relative;
  height: clamp(240px, 30vw, 360px);
  overflow: hidden;
}
.k-onehouse .mp-page { position: absolute; }
.k-onehouse .kp-pin { cursor: default; }

.k-hero-shot .k-thumb { height: clamp(220px, 32vw, 340px); }
.k-hero-shot .k-thumb svg { width: 54px; height: 54px; }

/* The gallery (KODU-69) is the tier's now (UIC-20): the strip, the arrows,
   the count, the thumbnails and the full-screen view are `.ui-gal-*` and
   `.ui-light*` in the tier's own sheet, the plans on paper its `is-paper`.
   What is left here is the tint a listing with no photograph stands in. */

/* The 3D tour (KODU-77): the provider's own player under the gallery, at the
   gallery's proportions, fetched when the reader reaches it. The one frame of
   anybody else's on the page, because the walk through the flat is what a
   renter came for. */
.k-tour {
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 480px;
  background: var(--chip);
}
.k-tour iframe { display: block; width: 100%; height: 100%; border: 0; }

/* The reading column is one flow (KODU-102): the price, the facts, the words
   and who is selling stand on the page with a rule between them, not in
   four boxes. */
.k-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: var(--s7);
  padding: var(--s8) 0;
  border-top: 1px solid var(--line);
}

.k-prose {
  margin: 0;
  max-width: var(--k-prose);
  font-size: var(--t5);
  line-height: 1.65;
  color: var(--ink2);
  text-wrap: pretty;
  white-space: pre-line;
}

.k-bigprice .k-pnum { font-size: var(--t9); }
.k-bigprice .k-punit { font-size: var(--t5); }

.k-agent .k-face { border-radius: 50%; width: 42px; height: 42px; }
/* The partner's own front door under the source button (KODU-85): a link,
   and dressed as one rather than as a line of grey text. */
.k-agent a.k-cmeta { color: var(--ink3); text-decoration: underline; text-underline-offset: 3px; }
.k-agent a.k-cmeta:hover { color: var(--accent); }

/* The photograph rides over the initials (SPEC 3.2): a file that fails to
   load leaves the face this screen always had, never a broken image. */
.k-facepic {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* The one primary action that is not in a form's footer. */

/* ── Lisa kuulutus (SPEC 4.2) ────────────────────────────────────────────── */

/* The editor (KODU-81, KODU-82, 2026-09-04) wears the front page's manner
   (SITE-3): the ground is the paper, a light serif for the one heading and
   for each section's name, small tracked labels in the sans, thin rules
   where cards stood, pills for every press and every chip, and the fields
   as hairline boxes with the label floating inside. Two columns on a wide
   window — the form, and beside it the pictures — one under the other
   below 1 100px. The form itself is not a box: `display: contents` hands its
   two children to the grid, which is what lets the staging column stand
   inside the form on a first publish (nothing in it is a form) and outside
   it on an edit (everything in it is). */
.k-editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  gap: var(--s10) clamp(var(--s10), 4vw, 64px);
  align-items: start;
  width: 100%;
  max-width: var(--k-page);
  margin-inline: auto;
}
.k-publish { display: contents; }
/* Three items on the grid: the sections, the foot under them, and the
   pictures down the second column beside both — whichever side of the
   `</form>` they stand on. */
.k-edmain { display: flex; flex-direction: column; min-width: 0; grid-column: 1; order: 1; }
.k-pfoot { grid-column: 1; order: 3; }
.k-editor .k-pictures { grid-column: 2; grid-row: 1 / span 2; order: 2; }
.k-edmain > .k-problem { margin-bottom: var(--s9); }
/* A section: a rule over it, its number and name down the left in the
   serif, its controls to the right. Three fields share a line in the
   building's facts, so the row basis is a third of the body. */
.k-edsec {
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  gap: var(--s8) clamp(var(--s10), 3vw, var(--s13));
  padding-block: clamp(var(--s10), 2.6vw, var(--s12));
  border-top: 1px solid var(--line2);
}
.k-edmain > .k-edsec:first-of-type { padding-top: var(--s4); border-top: 0; }
.k-edsec .k-row > .ui-field { flex: 1 1 160px; }
.k-edno {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin: 0;
  font-family: var(--serif);
  font-size: var(--t8);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink);
  text-wrap: balance;
}
.k-edn {
  font-family: var(--mono);
  font-size: var(--t3);
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--mute);
}
/* The bar over the editor: the way back, what the screen is, the listing's
   own address as the heading, its state, its page and the one press — level
   across the editor's width, sticky at the top of the pane so the press is
   never a scroll away, and standing still on a phone, where the foot's
   press is one flick away. */
.k-edbar {
  position: sticky;
  top: calc(-1 * clamp(var(--s9), 2.6vw, var(--s12)));
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--s5) var(--s6);
  width: 100%;
  max-width: var(--k-page);
  margin-inline: auto;
  padding: var(--s6) 0 var(--s8);
  border-bottom: 1px solid var(--line2);
  background: var(--page);
}
.k-edtitle { display: flex; flex-direction: column; gap: var(--s2); flex: 1 1 320px; min-width: 0; }
.k-edback { display: inline-flex; align-items: center; gap: var(--s2); font-size: var(--t4); color: var(--mute); }
.k-edback:hover { color: var(--ink); }
.k-edback svg { width: 14px; height: 14px; }
.k-edkick { margin: var(--s3) 0 0; font-size: var(--t4); font-weight: 500; letter-spacing: 0.06em; color: var(--mute); }
.k-edh1 {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(var(--t9), 2.4vw, var(--t11));
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.1;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.k-edmeta:empty { display: none; }
.k-edbar .k-chipst {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  height: 40px;
  padding: 0 var(--s7);
  border-radius: var(--r-pill);
  font-size: var(--t4);
  font-weight: 500;
  letter-spacing: 0.04em;
}
/* The presses: a filled pill for the one, hairline pills for the rest, the
   way the front page's are. Ink on paper rather than the accent — the
   accent is what a press turns into under the pointer. */
.k-edbar .ui-submit, .k-editor .ui-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s4);
  height: 44px;
  margin: 0;
  padding: 0 var(--s10);
  border: 1px solid var(--solid);
  border-radius: var(--r-pill);
  background: var(--solid);
  color: var(--solid-ink);
  font-size: var(--t6);
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
  transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.k-edbar .ui-submit:hover, .k-editor .ui-submit:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  transform: translateY(-1px);
}
.k-editor .ui-submit.is-plain {
  height: 40px;
  padding: 0 var(--s8);
  border-color: var(--line2);
  background: transparent;
  color: var(--ink);
  font-size: var(--t5);
}
.k-editor .ui-submit.is-plain:hover { background: transparent; border-color: var(--ink); color: var(--ink); }
.k-edbar .ui-pill, .k-editor .ui-pill {
  height: 40px;
  padding: 0 var(--s8);
  border-color: var(--line2);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink);
  font-weight: 500;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.k-edbar .ui-pill:hover, .k-editor .ui-pill:hover { border-color: var(--ink); color: var(--ink); }
/* The form's own foot: the way back on the left, the press on the right. */
.k-pfoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
  flex-wrap: wrap;
  padding-top: clamp(var(--s10), 2.6vw, var(--s12));
  border-top: 1px solid var(--line2);
}
.k-pfoot .ui-text-btn { margin-right: auto; color: var(--mute); font-size: var(--t5); }
.k-pfoot .ui-text-btn:hover { color: var(--ink); text-decoration: none; }
.k-pfoot .ui-note { flex: 1 1 220px; font-size: var(--t5); }
/* The head of the forms still on cards — Seaded, Uus büroo, the inquiry
   (`looks::FORM_HEAD`): the heading and the way out on one line. */
.k-phead .k-h2 { margin-right: auto; }
/* The fields, the front page's way: a hairline box on the ground with the
   label floating inside it, small and tracked, and the value under it. The
   placeholder shows once the box has focus, so a label and an example are
   never read as two labels. */
.k-editor .ui-field { position: relative; display: block; margin: 0; }
.k-editor .ui-field > .ui-label {
  position: absolute;
  top: 11px;
  left: 17px;
  z-index: 1;
  max-width: calc(100% - 34px);
  margin: 0;
  overflow: hidden;
  font-family: var(--sans);
  font-size: var(--t3);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: none;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--mute);
  pointer-events: none;
}
.k-editor .ui-input {
  height: 54px;
  padding: var(--s10) var(--s8) var(--s4);
  border: 1px solid var(--line2);
  border-radius: var(--r6);
  background: transparent;
  color: var(--ink);
  font-size: var(--t6);
  line-height: 1.3;
  transition: border-color 0.15s ease;
}
.k-editor .ui-input:not(:focus)::placeholder { color: transparent; }
.k-editor .ui-input::placeholder { color: var(--faint); }
.k-editor textarea.ui-input { height: auto; min-height: 132px; padding-top: var(--s11); line-height: 1.5; resize: vertical; }
.k-editor select.ui-input { appearance: none; padding-right: var(--s12); }
.k-editor .ui-field:has(> select)::after {
  content: "";
  position: absolute;
  right: 18px;
  top: 21px;
  width: 7px;
  height: 7px;
  border: solid var(--mute);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
  pointer-events: none;
}
.k-editor .ui-input:focus-visible { outline: none; border-color: var(--ink); }
.k-editor .ui-field.is-invalid .ui-input { border-color: var(--danger); }
.k-editor .ui-field > .ui-hint { margin-top: var(--s3); }
.k-editor .k-hints { top: calc(100% + 4px); }
/* The chips are pills, and the one that is held is filled. */
.k-editor .k-group { gap: var(--s4); }
.k-editor .k-group > .ui-label,
.k-editor .k-regcard > .ui-label,
.k-editor .k-fold > summary {
  font-family: var(--sans);
  font-size: var(--t4);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--mute);
}
.k-editor .k-chips { gap: var(--s3); }
.k-editor .k-chip {
  height: 38px;
  padding: 0 var(--s8);
  border-color: var(--line2);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink2);
  font-weight: 500;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.k-editor .k-chip:hover { border-color: var(--ink); color: var(--ink); }
.k-editor .k-chk:checked + .k-chip { border-color: var(--solid); background: var(--solid); color: var(--solid-ink); }
.k-editor .ui-hint { font-size: var(--t4); line-height: 1.5; color: var(--mute); }
.k-editor .k-regcard { padding: var(--s6) 0 0; border-top: 1px solid var(--line); background: transparent; }
/* The translations fold: a rule, its name, and a chevron — a row that
   opens, not a box that looks like a select. */
.k-editor .k-fold { padding: 0; border-top: 1px solid var(--line); }
.k-editor .k-fold[open] { display: flex; flex-direction: column; gap: var(--s6); padding-bottom: var(--s6); }
.k-editor .k-fold > summary {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s7) 0;
  list-style: none;
  cursor: pointer;
  color: var(--ink);
}
.k-editor .k-fold > summary:hover { color: var(--accent); }
.k-editor .k-fold > summary::-webkit-details-marker { display: none; }
.k-editor .k-fold > summary::after {
  content: "";
  width: 6px;
  height: 6px;
  border: solid currentColor;
  border-width: 0 1.5px 1.5px 0;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.15s ease;
}
.k-editor .k-fold[open] > summary::after { transform: translateY(2px) rotate(225deg); }
/* What went wrong, said where it went wrong: amber, since nothing was lost. */
.k-problem {
  margin: 0;
  padding: var(--s5) var(--s7);
  border-radius: var(--r5);
  background: var(--warn-bg);
  color: var(--warn-ink);
  font-size: var(--t5);
  text-wrap: pretty;
}
.k-problem[hidden] { display: none; }
@media (max-width: 1100px) {
  .k-editor { grid-template-columns: minmax(0, 1fr); }
  .k-editor .k-pictures { grid-column: 1; grid-row: auto; padding-top: var(--s8); border-top: 1px solid var(--line2); }
}
/* The dock beside the page takes its 420px column from the editor's room,
   and a media query cannot ask about that (KODU-99): with the robot's dock
   open the pictures go under the sections up to 1700 — the 1100 the query
   alone stacks at, plus the dock and the rail — or the staging column
   stands on top of the fields it was drawn beside. */
@media (max-width: 1700px) {
  html[data-dock="half"] .k-editor { grid-template-columns: minmax(0, 1fr); }
  html[data-dock="half"] .k-editor .k-pictures { grid-column: 1; grid-row: auto; padding-top: var(--s8); border-top: 1px solid var(--line2); }
}
@media (max-width: 760px) {
  .k-edsec { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
  .k-edbar { position: static; align-items: center; }
  .k-edbar .ui-submit { height: 40px; flex: 1 1 auto; }
}

/* A field is a label over a control and nothing else.
   `flex: 1 1 220px` used to be here, and 220px is a *basis* — which in a
   column is a **height**. Every field that was not inside a row (the address,
   the place, the description, both boxes on the sign-in card) therefore asked
   for 220px of it, and the publish form came out with a hand's width of empty
   page under half its controls. The basis belongs to the fields that share a
   line, so it is written where that is true. */
/* What the address box found (KODU-40). It hangs under the very box it
   belongs to: the script puts the list inside the field, the field is the
   measure, and `top: 100%` is what actually hangs it — an absolutely
   positioned child of a flex column takes its static position at the
   column's *top*, so without the `top` the list stood over the label and
   the box it was suggesting for. */
.k-group .ui-field { position: relative; }
.k-hints {
  z-index: 40;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 260px;
}
/* The list carries `stack`, whose `display: flex` would otherwise beat the
   browser's own `[hidden]`. */
.k-hints[hidden] { display: none; }
.k-hint-row:last-child { border-bottom: 0; }
.k-hint-row:hover, .k-hint-row.is-hot { background: var(--chip); color: var(--ink); }

.k-row > .ui-field { flex: 1 1 220px; min-width: 0; }

.k-slot {
  width: 110px;
  height: 82px;
  border: 1.5px dashed var(--line2);
}
.k-slot svg { width: 18px; height: 18px; }

/* What the register knows about the picked building (KODU-67): the resolved
   ID-address as one quiet line under the box, and the mirror's facts as a
   card — read-only, because the fields above are the seller's. The card, the
   rows and the links carry `stack`/`flex`, whose `display` would otherwise
   beat the browser's own `[hidden]`. */
.k-regcard[hidden] { display: none; }
.k-regfacts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s3) var(--s7);
  margin: 0;
}
.k-regrow[hidden] { display: none; }
.k-regrow dt { color: var(--mute); }
.k-regrow dd { margin: 0; font-weight: 600; color: var(--ink); text-align: right; }
.k-reglinks .ui-pill[hidden] { display: none; }
@media (max-width: 560px) {
  .k-regfacts { grid-template-columns: 1fr; }
}

/* ── Seaded (SPEC 3.2) ───────────────────────────────────────────────────── */

/* The ground stays here: what turning the switch on changes is this, and a
   utility on the element would beat the rule that does the changing. */
.k-knob {
  width: 38px;
  height: 22px;
  background: var(--line2);
  transition: background 120ms ease;
}
.k-knob::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface);
  transition: transform 120ms ease;
}
.k-chk:checked + .k-switch .k-knob { background: var(--accent); }
.k-chk:checked + .k-switch .k-knob::after { transform: translateX(16px); }
.k-chk:focus-visible + .k-switch .k-knob { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── the payment card (BILL-4) ───────────────────────────────────────────── */

/* In the middle of the pane, like the publish form: a transfer card hugging
   the left edge of a wide screen read as misplaced. */
.k-pay { width: 100%; max-width: 640px; margin-inline: auto; }
.k-payrows { flex: 1 1 260px; }
.k-payrow .ui-label { width: 90px; flex: none; margin: 0; }
/* An IBAN is one word twenty characters long, and a column 260px wide is
   narrower than it. */
.k-payv { word-break: break-all; }

/* ── Turuülevaade (SPEC 7) ───────────────────────────────────────────────── */

/* In the editor's manner (KODU-112): the sheet's own measure, the counties
   as rows under hairlines (`.k-lines`) and no box round them — a table of
   fifteen counties stretched over a whole desk is a row somebody has to
   track with a finger. A row lights its name on hover, the way a context
   row does, rather than painting a ground under itself. */
.k-mkrow:hover .k-mkname { color: var(--accent); }
.k-mkname { flex: 1 1 140px; font-size: var(--t5); font-weight: 600; color: var(--ink2); }
.k-mkhead .k-mkname,
.k-mkhead .k-mkn,
.k-mkhead .k-mksqm,
.k-mkhead .k-mkd {
  font-family: var(--mono);
  font-size: var(--t3);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mute);
}
.k-mkn { width: 76px; flex: none; text-align: right; font-family: var(--mono); font-size: var(--t4); color: var(--ink3); }
.k-mkbar { flex: 2 1 120px; min-width: 40px; height: 6px; border-radius: var(--r1); background: var(--chip); overflow: hidden; }
.k-mkfill {
  /* How far along this county is, as a percentage of the dearest. The value
     comes from the element because it is a fact about the county; the default
     is here so a bar whose markup forgot is empty rather than absent. */
  --of: 0;
  display: block;
  width: calc(var(--of) * 1%);
  height: 100%;
  border-radius: var(--r1);
  background: var(--accent);
}
.k-mksqm { width: 110px; flex: none; text-align: right; font-family: var(--mono); font-size: var(--t4); color: var(--ink); }
.k-mkd { width: 56px; flex: none; text-align: right; font-family: var(--mono); font-size: var(--t4); color: var(--faint); }

/* ── the Terms ───────────────────────────────────────────────────────────── */

/* Nothing of their own any more (KODU-92): the Terms are `k-docmain` and
   `ui-doc-md`, like the policies and the manual. The card, the 720px measure
   and the three classes that dressed them are gone — a page that is only
   words should not have a second way of looking like a page that is only
   words. */

/* ── the manual (KDOC-1) ─────────────────────────────────────────────────── */

/* Wider than the terms and narrower than the pane: this is prose with tables
   and fenced blocks in it, and a reference row is a key beside a sentence
   rather than a sentence on its own. */
/* The manual is the mail app's manual, drawn the same way (KDOC-12): the head
   is a band across the whole screen — the title with the file's own path
   beside it and the two readings on the right — the books are underlined
   text rather than boxes, and the document stands on the page rather than in
   a card. Kodu's own rail is to the left of all of it, which is the only
   thing about this screen that is not Postbox's. */

/* Out of the pane by exactly the padding the pane put there, then the gutter
   back inside — the same trick the flush panel plays (KODU-27), for the same
   reason: the band reaches the glass and its words still line up with the
   prose under them. */
.k-dochead {
  margin: calc(clamp(var(--s9), 2.6vw, var(--s12)) * -1) calc(var(--k-gutter) * -1) 0;
  padding: var(--s9) var(--k-gutter) 0;
}
/* The path fills what is left of the title's line, and the two readings sit
   at the far end of it — which is the one thing about this head that is the
   portal's rather than the tier's. */
.k-docs .ui-doc-titled { flex: 1; min-width: 0; }
.k-docs .ui-doc-where { flex: 1; }
.k-docs .ui-doc-acts { margin-left: auto; }
/* The band reaches the glass and its words line up with the prose under them
   (KODU-92): the head's *contents* take the same measure the body does, so
   centring the manual does not leave the title behind in the corner. */
.k-docs .ui-doc-titlerow,
.k-docs .ui-doc-tabs { width: 100%; max-width: var(--k-page); margin-inline: auto; }

/* The manual's measure, which the policies, the partner pages and the
   campaigns borrow: prose *with tables in it* reads wider than the Terms.
   **The publish form's measure, and centred like it** (KODU-92): at 880 in
   the corner of a desk-width window the manual read as a column pushed into
   the left-hand edge, with the reference tables inside it wrapping while half
   the screen stood empty. The listings are the exception and stay whole —
   what is on the market is what this portal is for. */
.k-docmain {
  display: flex;
  flex-direction: column;
  gap: var(--s8);
  width: 100%;
  max-width: var(--k-page);
  margin-inline: auto;
  padding-top: clamp(var(--s9), 3vw, var(--s11));
}

/* The prose, the reference tables, the pages, the raw file and the Test panel
   are the tier's (`ui/assets/docs.css`, UIC-1) — this portal's manual is the
   mail app's manual drawn the same way (KDOC-12), and it says so by using it.
   What is left here is the band's own place in the portal's frame. */

/* ── the popular places ──────────────────────────────────────────────────── */

.k-pop {
  padding: var(--s3) var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r5);
  background: var(--surface);
  font-size: var(--t5);
  color: var(--ink2);
}
.k-pop:hover { border-color: var(--accent); color: var(--accent); }

/* ── signing in (AUTH-3) ─────────────────────────────────────────────────── */

/* `.hidden` is the utility layer's now — the same declaration stood in three
   sheets, so which one a screen got depended on who was serving it. */

/* The measure, which is not a step: a sign-in is the narrowest screen the
   portal has because there is one thing to do on it. How the column inside it
   is spaced is the markup's (UI-42). */
.k-signin {
  margin: auto;
  width: min(420px, 100%);
  padding: clamp(var(--s4), 3vh, var(--s12)) 0;
}

/* The two ways in, each said as itself (KODU-52). Both screens' arrangement is
   now steps in their own markup (UI-42); what is left here is the one thing a
   utility cannot draw — the rule either side of the word between them. */
.k-or::before, .k-or::after { content: ""; flex: 1; height: 1px; background: var(--hair); }

/* ── the map (KODU-17) ───────────────────────────────────────────────────── */

/* The map is the maps door's own component (MAPS-17): `.mp-page` and
   everything under it is `maps::STYLE`, served with this sheet, so the two
   maps are one drawing rather than two that happen to be of Estonia. What is
   here is only what stands *on* it — the pins, and what a pin opens.

   `.mp-page` is `position: fixed` on the door where the map is the whole
   page. Inside the portal's frame it is the pane instead, so the one rule
   that would take it out of the frame is the one rule overridden. */
.k-mapscreen {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  margin: calc(clamp(var(--s9), 2.6vw, var(--s12)) * -1) calc(var(--k-gutter) * -1) calc(clamp(var(--s10), 3vw, var(--s13)) * -1);
}
.k-mapscreen .mp-page { position: absolute; }
/* The developer lines — the level and corners, the ground and its frame
   rate — are not hidden here by the sheet any more: the server draws them
   only for a superadmin and for nobody else (MAPS-29), so what is on
   screen is what was sent. */
/* ⤢ means the whole screen on this door too (KODU-49). */
.k-mapscreen .mp-all { display: none; }
.k-mapscreen.is-full {
  position: fixed;
  inset: 0;
  z-index: 400;
  margin: 0;
  background: var(--page);
}

/* A mark on the map (KODU-57): a pill in the map's own material — the
   surface the floating controls wear, a hairline round it, the number in the
   house mono — so it reads as part of the drawing, the way the search box
   and the zoom do, rather than as a coin dropped on it. The accent arrives
   only when the pointer does. */
/* Except on the listing's own map, where the pin is a drawing (KODU-42). */
.k-onehouse .kp-pin { cursor: default; }
/* Bare numbers on the drawing (KODU-60): the rect is the pointer's target
   and nothing else — no surface, no hairline — and the number reads over
   the map through its halo, the way the place names do. The surface comes
   back only under the pointer, so hover still answers before a press. */
/* A count or a price is a pill (KODU-104): the surface with a quiet edge,
   so it reads over any land, water or road it stands on — bare figures on
   the ground were the thing nobody could see. Under the pointer the edge is
   the accent and the ground the chip, as a chosen chip's are. */
.kp-pill {
  fill: var(--surface);
  stroke: var(--line);
  stroke-width: 1;
  pointer-events: all;
  transition: fill .18s ease, stroke .18s ease;
}
.kp-pin:hover .kp-pill, .kp-pin.is-hot .kp-pill { stroke: var(--accent); stroke-width: 1.5; fill: var(--chip); }
.kp-n {
  fill: var(--ink);
  font-family: var(--mono);
  font-weight: 600;
  text-anchor: middle;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--mp-land);
  stroke-width: 2.4px;
  stroke-linejoin: round;
  transition: fill .18s ease;
}
/* How many is quieter than how much: a count pill's number steps back a
   shade, so a price among counts reads as the answer it is. */
.kp-pill.is-count + .kp-n { fill: var(--ink2); font-weight: 500; }
/* A district's name over its district (KODU-60): the basemap's own voice —
   information under everything, answering no pointer. */
.kp-district {
  font-family: var(--sans);
  font-size: var(--t3);
  letter-spacing: 0.04em;
  text-anchor: middle;
  fill: var(--mute);
  paint-order: stroke;
  stroke: var(--mp-land);
  stroke-width: 2px;
  stroke-linejoin: round;
  pointer-events: none;
  user-select: none;
}
.kp-pin:hover .kp-n { fill: var(--accent-down); }
/* The browser's own focus rectangle is a black box over the drawing; the
   focus is said on the mark itself instead, and only for a keyboard —
   a click's focus already has the pointer on it. */
.kp-pin:focus { outline: none; }
.kp-pin:focus-visible .kp-pill { stroke: var(--ink); stroke-width: 2; }
/* The label's whole box answers the pointer, not just the glyphs' own ink. */
.kp-hit { fill: none; pointer-events: all; }
/* A house that has something for sale (KODU-57): the footprint itself,
   tinted — the mark is part of the map because it *is* the map's own shape.
   The basemap draws the same outline underneath in `--mp-house`; this one
   sits over it in the soft accent with the accent's own edge. */
.kp-house {
  fill: var(--accent-soft);
  stroke: var(--accent);
  stroke-width: 1;
  stroke-linejoin: round;
  transition: fill .18s ease, stroke-width .18s ease;
}
.kp-pin.is-house:hover .kp-house, .kp-pin.is-house:focus-visible .kp-house {
  fill: color-mix(in oklab, var(--accent) 32%, var(--accent-soft));
  stroke-width: 2;
}
/* A named place (KODU-49): the name with the count under it, the site's own
   type at the site's own weight, haloed so it reads over whatever the map
   drew beneath. The basemap's label for the same shape bows out — one name,
   said once. Pointing at the name warms the land, and the other way round:
   they are one thing. */
.k-named .mp-name { display: none; }
.k-named .mp-land { cursor: pointer; }
.kp-place, .kp-count {
  text-anchor: middle;
  /* The halo is legibility, not weight: three pixels of it read as a bold
     the site does not use; one and a half vanished on the darker ground
     (KODU-104), so it is a little over two. */
  paint-order: stroke;
  stroke: var(--mp-land);
  stroke-width: 2.2px;
  stroke-linejoin: round;
  font-family: var(--sans);
  font-weight: 400;
  fill: var(--ink);
  transition: fill .18s ease, stroke .18s ease;
}
/* The warmth is the rail's own hover — the chip tone, eased in, because the
   pointer crossing a border toggles the shape off and on again and an
   instant fill read as a flicker. */
.mp-land { transition: fill .18s ease; }
.mp-land.is-hot { fill: var(--chip); }
.kp-pin.is-named.is-hot .kp-place,
.kp-pin.is-named.is-hot .kp-count { stroke: var(--chip); }
.kp-pin.is-named.is-hot .kp-place,
.kp-pin.is-named.is-hot .kp-count,
.kp-pin.is-named:focus-visible .kp-place { fill: var(--accent-down); }
/* The map keeps a map's own colours (MAPS-31, 2026-09-08: *"kodu map in
   light theme is really hard to see … use more standard map colors"*). It
   wore the site's palette — land as the page, water mixed onto it — and on
   the light page that was near-white land against a hard grey-blue sea with
   grey scratches for roads. The component's palette answers both schemes
   now, and the portal overrides nothing of it; only the furniture below
   is the portal's. */
/* In the dark the furniture was a ghost: surface on land measured nine
   steps of 255 apart and the hairlines vanished with it. The map's own scope
   lifts both a notch — the cards and pills ride the same tokens, so the whole
   deck rises together. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .k-mapview .mp-page,
  :root:not([data-theme="light"]) .k-mapscreen .mp-page,
  :root:not([data-theme="light"]) .k-pickmap .mp-page,
  :root:not([data-theme="light"]) .k-onehouse .mp-page {
    --surface: color-mix(in oklab, var(--ink) 8%, var(--page));
    --line: color-mix(in oklab, var(--ink) 24%, var(--page));
  }
}
/* A picked area on the picker's map (KODU-41). The map is a control there,
   and a control has to show what it is set to — the pill turns accent, the
   way a chosen chip does. */
.kp-pin.is-picked .kp-pill { fill: var(--accent); stroke: var(--accent-down); }
.kp-pin.is-picked .kp-n { fill: var(--accent-ink); }
/* And the land is pressable there, so it says so under the pointer. */
.k-pickmap .mp-land { cursor: pointer; }
/* The drawing wears the choice (KODU-63): a picked area's land keeps a
   standing wash, beside the fading is-on the search map plays. */
.k-pickmap .mp-land.is-picked { fill: var(--accent-soft); stroke: var(--accent); }
.k-pickmap .mp-map { cursor: default; }
.k-pickmap .mp-map.is-dragging { cursor: grabbing; }

/* The switch to the other view of the same search, floating under the
   standalone map's box (KODU-32). It rides in the box's own hits panel, and
   that panel is as wide as the box — so the two words stood at the left end
   of a bar of nothing, inside a second frame of their own (2026-09-07: *"the
   nimekiri kaart thingy is broken"*). The panel hugs the switch now, and the
   switch gives up its own edge: one small floating pill, two words. */
.mp-mine { display: flex; align-items: center; gap: var(--s4); width: max-content; padding: var(--s1); }
.mp-mine > nav { border-color: transparent; }

/* The door from the drawing to the rows (KODU-58): what the rectangle holds,
   as a list. Bottom left — the one quiet corner: the zoom keeps the right,
   and the bottom centre is the densest band of a short map, where the door
   was found sitting on the very pills it counted. */
.k-maplist {
  position: absolute;
  left: var(--s7);
  bottom: var(--s7);
  z-index: 4;
  padding: var(--s3) var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: var(--t4);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
.k-maplist:hover { border-color: var(--accent); color: var(--accent-down); }
.k-maplist[hidden] { display: none; }

/* The map's rectangle, worn as a chip on the list it narrows (KODU-58). */
.k-rectchip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  background: var(--chip);
  font-size: var(--t4);
  color: var(--ink2);
}
.k-rectchip:hover { border-color: var(--accent); color: var(--ink); }
.k-rectchip span { color: var(--mute); }

/* What a pin un-sums into. `display: flex` beats the `hidden` attribute, so
   the attribute is spelt out — three screens in this tree have been shipped
   with a panel that would not close for exactly this reason. */
/* `left` and `top` are the loader's (`kBeside`): the card stands beside the
   mark it is about, and where that is depends on where the pin is and how
   tall the card came out. The two here are only where it would sit if it were
   ever shown before it has been placed — and never `bottom`, because a box
   with a `top` and a `bottom` and no height is one stretched between them. */
.k-mapcard {
  position: absolute;
  left: 14px;
  top: 14px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  width: min(320px, calc(100% - 28px));
  max-height: 60%;
  border: 1px solid var(--line);
  border-radius: var(--r7);
  background: var(--surface);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.k-mapcard[hidden] { display: none; }
.k-maphead2 {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s5) var(--s4) var(--s5) var(--s7);
  border-bottom: 1px solid var(--hair);
}
.k-maphead2 strong { flex: 1; font-size: var(--t5); font-weight: 600; color: var(--ink); }
.k-mapclose {
  flex: none;
  width: 26px; height: 26px;
  /* And the map card's dismiss (KODU-51). */
  border-radius: 50%;
  font-size: var(--t6);
  color: var(--mute);
  cursor: pointer;
}
.k-mapclose:hover { background: var(--chip); color: var(--ink); }
.k-mapline {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) var(--s4);
  border-radius: var(--r4);
  font-size: var(--t5);
  color: var(--ink2);
}
.k-mapline:hover { background: var(--chip); color: var(--ink); }
/* The row wears the listing's own picture, small — the same thumb every
   card wears, so a reader recognises the flat before opening it (KODU-57). */
.k-mapline .k-thumb { flex: none; width: 56px; height: 40px; border-radius: var(--r4); }
.k-mapline .k-thumb svg { width: 18px; height: 18px; }
.k-mapline-what { flex: 1; display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.k-mapline-price { font-family: var(--mono); font-size: var(--t4); font-weight: 600; color: var(--ink); }

/* What a house says when it is pointed at (KODU-43). The same three things a
   card leads with — the price, the picture, the address — because a preview
   that showed something else would be a second way of describing a listing.
   A `<title>` said the address alone, in a tooltip nobody chooses by. */
/* A link where there is one listing to open and a button where there are
   several to un-sum, so the two are one control drawn one way. */
.k-mappeek {
  display: flex;
  align-items: center;
  gap: var(--s5);
  width: 100%;
  padding: var(--s4);
  border: 0;
  border-radius: var(--r6);
  background: none;
  color: var(--ink2);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.k-mappeek:hover { background: var(--chip); }
/* The thumbnail is three by two everywhere it is drawn; here it is small,
   because the card is a preview and not a result. */
.k-mappeek .k-thumb { flex: none; width: 78px; height: 52px; border-radius: var(--r5); }
.k-mappeek .k-thumb svg { width: 22px; height: 22px; }
.k-peekprice {
  font-family: var(--mono);
  font-size: var(--t6);
  font-weight: 600;
  color: var(--ink);
}
.k-peekwhere {
  font-size: var(--t4);
  color: var(--mute);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── the operator line ───────────────────────────────────────────────────── */

/* Inside the pane's scroll now that the rail owns the left column, and pushed
   to the bottom of it by `margin-top: auto` — so it is on the bottom edge of
   a short screen and at the end of a long one, rather than floating under
   three counties. */
/* A stack now that there is more than one line in it (KODU-50). The mono and
   the 11px are on the last line's own markup and never on the whole foot,
   which would set the links in a typewriter face. */
.k-foot {
  gap: clamp(var(--s8), 2vw, var(--s10));
  margin-top: auto;
  padding-top: clamp(var(--s10), 3.5vw, var(--s12));
  /* The **box** spans the pane's content width and the *reading* inside it is
     what stops at a measure. Two reasons, and the second is easy to lose:
     a rule drawn to the far edge of a 2 400px pane under a reading that
     stopped at 880 is a page divider rather than the top of a foot — and the
     foot standing at the full content width is a fact the landing's spec
     measures the gutter with (`kodu_portal`). Capping the whole element
     silently made the ruler shorter than the thing it measures. */
  width: 100%;
}
/* The bands inside it stop at the page's measure (KODU-50, amended
   2026-09-01 and again 2026-09-06): every band wears `measure`, and the
   **box** still spans — the rule under the content is a rule under the
   content, and the landing's spec measures the gutter with it. */

/* ── what to read, and what you agreed to (KODU-50) ──────────────────────── */

/* The foot is three bands, and each one is quieter than the one above it: the
   links a reader might want, a hairline, then the machine facts nobody reads
   until they need them.

   It is not a card. Nothing in this column floats (see the header, point 3),
   so the separation is a rule and space — the same way a listing is a surface
   with a hairline rather than a shadow.

   The column heads are the smallest tracked-out type on the page, which is
   what makes them read as labels rather than as more links — and `--ink3`
   rather than `--faint`, because at 11px uppercase a faint heading is one you
   have to hunt for. */
.k-footcols {
  /* `auto-fit` with a floor rather than a breakpoint: one column at 390px,
     two from about 560, three from about 780, and the foot needs no media
     query of its own. The columns are `1fr` and not `max-content` so the
     heads line up on their left edges instead of drifting apart with the
     longest link in each. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  /* The measure is the band rule above, with every other band (2026-09-06).
     It ran to the pane between 2026-09-01 and then, on the reasoning that a
     sitemap wears the pane — but the hairline under it did not, so the
     columns began outside the line that ended them. */
}

.k-footlist { list-style: none; }
.k-footlink {
  /* The size and its leading are one decision and the leading is not the
     ladder's: the links sit close together, so they are tighter than `--lh5`
     and stay in the sheet together. */
  font-size: var(--t5);
  line-height: 1.3;
  text-decoration: none;
  /* Underline on hover only, and drawn under the text rather than through the
     descenders — the links sit close together and a permanent rule under each
     would make the column a fence. */
  text-underline-offset: 3px;
}
.k-footlink:hover { color: var(--accent); text-decoration: underline; }
/* The assistant's row in the foot is the dock's own control (UIC-21), a
   button wearing a link's dress: it loses the button's box and keeps the
   link's word. */
.k-footlink.ui-dock-toggle { border: 0; background: none; padding: 0; width: auto; }

/* The company's three facts (KODU-53). They keep the register they had on the
   line they came off — mono, 11px, `--mute` — because that is what the whole
   design keeps machine facts in, and a registry code set in the text face
   beside the manual's links would read as another thing to press. The name
   itself is the page's own ink: it is a name, and the two numbers under it are
   what a name is checked against. */
.k-footfact { font-size: var(--t3); line-height: 1.35; }
.k-footfact:first-child { color: var(--ink3); }

/* The hairline, and the machine facts under it. `--hair` is what the manual
   and the cards separate with; a heavier `--line` here would read as the end
   of the page rather than the end of the reading. */
.k-footrule { width: 100%; height: 1px; background: var(--hair); }

/* Whose platform this stands on: quiet enough to be the last thing read, and
   never so quiet it cannot be. `--mute` clears 4.5:1 on `--surface` in both
   schemes, which is the difference between restrained and unreadable — the one
   thing a "barely visible" mark must not actually be. The name itself is a
   step darker than the words in front of it, because it is the part that is a
   link. */
.k-footmark-link { display: inline-flex; align-items: center; gap: .4em; text-decoration: none; text-underline-offset: 3px; }
.k-footmark-link:hover { color: var(--accent); text-decoration: underline; }
/* The company's mark beside its name (KODU-50). */
.k-footmark { display: inline-flex; width: 1.1em; height: 1.1em; }
.k-footmark svg { width: 100%; height: 100%; }

/* ── narrow ──────────────────────────────────────────────────────────────── */

/* A phone lays the rail down as a bottom bar, which is what the mail app does
   with the same column and for the same reason: a column down the left of a
   390px screen is navigation standing where the thing being navigated to has
   to go. Its arrangement is Postbox's, down to the reasons.

   `column-reverse` and not `order`: the rail is first in the document because
   it is first in the *reading* order, and this puts it at the bottom of the
   screen without moving it in the markup. */
@media (max-width: 860px) {
  .k { display: flex; flex-direction: column-reverse; }
  .k-rail {
    flex: none;
    flex-direction: row;
    align-items: center;
    gap: 0;
    border-right: none;
    border-top: 1px solid var(--line);
    /* Installed to the Home Screen the page runs full screen and the home
       indicator is inside it: the bar steps over, not under. */
    padding: var(--s2) var(--s2) calc(var(--s3) + env(safe-area-inset-bottom));
    position: relative;
    overflow: visible;
  }
  .k-pane { flex: 1; min-height: 0; }
  /* The mark, the rule and the spacer are the column's furniture and have
     nothing to say in a row. */
  .k-mark, .k-railrule, .k-railspace { display: none; }
  /* The six about the market share what is left after the reader's own two,
     and scroll sideways if they must — the account is never the thing that
     scrolls off, because a navigation whose account button is off the screen
     is a navigation with no way to the account. */
  .k-railgroup {
    flex-direction: row;
    flex: 1 1 auto;
    min-width: 0;
    gap: 0;
    justify-content: space-around;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .k-railgroup::-webkit-scrollbar { display: none; }
  /* Lying down, the column's own answer to a short window has nothing to do:
     it is the width that is short here, and the entries scroll sideways. */
  .k-rail > .k-railgroup:first-of-type { flex: 1 1 auto; overflow-y: visible; }
  .k-railfoot {
    flex: none;
    flex-direction: row;
    align-items: center;
    gap: var(--s1);
  }
  /* A finger is not a pointer: nothing in the bar is smaller than 44px. */
  .k-railitem, .k-menow { min-width: 44px; min-height: 44px; justify-content: center; }
  /* The box loses the height the column gave it: a bar is 44 points tall and
     the word still has to fit under the icon. */
  .k-railbox { width: 38px; height: 30px; }
  .k-melink { min-height: 40px; }

  /* Five in the bar, the rest behind Menüü (KODU-65): Üür and Müük are the
     two searches people come for, with Lemmikud and Lisa kuulutus beside
     them; Uusarendused, Äripinnad and the overview stand as labelled rows
     in the menu, where they are found by their names rather than
     remembered by their icons. The overview is the fifth tab since the map
     entry went (2026-09-03), and the language is a row along the menu's own
     foot since the rail's pill went (KODU-98). */
  .k-railitem[data-testid="kodu-tab"]:nth-of-type(3),
  .k-railitem[data-testid="kodu-tab"]:nth-of-type(4),
  .k-railitem[data-testid="kodu-tab"]:nth-of-type(5) { display: none; }

  /* A finger picks places too (KODU-63). */
  .k-arow { min-height: 44px; }
  .k-akids > summary.k-afold { width: 44px; height: 44px; }

  /* The menu (KODU-65): a rail entry shut, the whole screen open — the
     summary itself becomes the sheet's head, the way the picker's does. */
  .k-menu { display: block; }
  .k-menu > summary { list-style: none; }
  .k-menu > summary::-webkit-details-marker { display: none; }
  .k-menu[open] > .k-menubtn {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 70;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    min-height: 56px;
    padding: 0 var(--s7);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
  }
  .k-menu[open] > .k-menubtn .k-railbox { display: none; }
  .k-menu[open] > .k-menubtn .k-raillabel {
    font-size: var(--t7);
    font-weight: 600;
    color: var(--ink);
    text-align: left;
  }
  .k-menu[open] > .k-menubtn .k-menushut { display: block; font-size: var(--t8); color: var(--ink3); }
  .k-menupop {
    position: fixed;
    inset: 56px 0 0;
    z-index: 70;
    overflow-y: auto;
    padding-bottom: calc(var(--s7) + env(safe-area-inset-bottom));
  }
  .k-menurow { min-height: 48px; align-items: center; }
  .k-menurow.is-here { color: var(--accent); background: var(--accent-soft); }

  /* The filters behind one pill (KODU-64): the sheet is the very panel,
     stood up as a screen, opened and closed by the nameless gate. */
  .k-fbtn {
    display: inline-flex;
    align-items: center;
    gap: var(--s3);
    align-self: flex-start;
    margin: var(--s4) 0;
    padding: var(--s3) var(--s6);
    border: 1px solid var(--line2);
    border-radius: var(--r5);
    background: var(--surface);
    font-size: var(--t5);
    font-weight: 600;
    color: var(--ink2);
    cursor: pointer;
  }
  .k-fbadge {
    min-width: 20px;
    padding: 0 var(--s2);
    border-radius: var(--r4);
    background: var(--accent);
    color: var(--accent-ink);
    font-size: var(--t3);
    font-weight: 600;
    text-align: center;
  }
  form.k-panel { display: none; }
  .k-fgate:checked ~ form.k-panel {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0;
    position: fixed;
    inset: 0;
    z-index: 55;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: var(--surface);
    overflow-y: auto;
  }
  .k-fgate:checked ~ form.k-panel .k-fhead {
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: sticky;
    top: 0;
    z-index: 1;
    min-height: 56px;
    padding: 0 var(--s7);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
  }
  .k-fshut { font-size: var(--t8); color: var(--ink3); cursor: pointer; padding: var(--s3); }
  .k-fgate:checked ~ form.k-panel .k-fstack {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s6);
    padding: var(--s6) var(--s7);
  }
  .k-fgate:checked ~ form.k-panel .k-ffoot {
    display: flex;
    position: sticky;
    bottom: 0;
    padding: var(--s5) var(--s7) calc(var(--s5) + env(safe-area-inset-bottom));
    background: var(--surface);
    border-top: 1px solid var(--line);
  }
  .k-fgate:checked ~ form.k-panel .k-ffoot .ui-modal { width: 100%; text-align: center; justify-content: center; }

  /* The fold opens upward, clear of the bar and inside the screen — and it
     is placed against *the bar* rather than against the button that opened
     it. A 200px menu hung off a 44px target near the right edge of a 390px
     screen lands half a pixel inside it, and any change to the bar puts it
     outside. The bar is the full width, so `bottom: 100%` is its top edge
     and the offsets stop being numbers anybody has to keep in step. */
  .k-me { position: static; }
  .k-mepop {
    top: auto;
    left: auto;
    right: 8px;
    bottom: calc(100% + 8px);
    width: max-content;
    min-width: 200px;
    max-width: calc(100vw - 16px);
  }
}

/* Narrower still: the labels go and the icons stand for them, and every entry
   takes an equal share of the width so all ten reach the edge instead of the
   last three scrolling off it. A navigation whose account button is off the
   screen is a navigation with no way to the account. */
@media (max-width: 560px) {
  /* One thing to do on the sign-in, and a phone's worth of room to do it
     in (2026-09-01, iPhone mini): the intro tightens so the first field is
     on the first screenful instead of a hand's width of air and prose. */
  .k-signin { padding-top: var(--s4); gap: var(--s6); }
  .k-signin .k-lede { font-size: var(--t5); }
  .k-signin .ui-card { padding: var(--s6); gap: var(--s7); }
  .k-pane { padding-top: var(--s6); }

  .k-rail { gap: 0; padding: var(--s2) var(--s2) calc(var(--s2) + env(safe-area-inset-bottom)); overflow: visible; }
  .k-railgroup, .k-railfoot { flex: 1 1 auto; justify-content: space-around; gap: 0; }
  .k-railitem, .k-menow {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    padding: 0 var(--s1);
  }
  /* The word stays under the icon (KODU-65): five entries share the width
     now that the rest stand in the menu, and an icon a reader has to guess
     at was what "unclear" meant. It only shrinks. */
  .k-railitem .k-raillabel { font-size: var(--t2); line-height: 1.1; }
  /* But never the menu head's own word, which is the sheet's title. */
  .k-menu[open] > .k-menubtn .k-raillabel { font-size: var(--t7); }
  .k-tree { columns: auto; }

  /* The picker is the list alone — still the whole screen (KODU-25), just
     one column of it. The cartogram and the list are one control in two
     places (SPEC 1.2) and the second of them is fifteen counties in three
     hundred points, a target nobody hits with a finger. The list picks the
     same places, and the box above it finds them by name. */
  .k-apop { grid-template-columns: minmax(0, 1fr); }
  .k-amap { display: none; }
  .k-atree { border-right: 0; }

  /* The sort and the layout toggle are one row that scrolls, not three rows
     of stubs and a stack of two icons. */
  .k-rtools { width: 100%; margin-left: 0; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .k-rtools::-webkit-scrollbar { display: none; }
  .k-lays { flex: none; }
  .k-sopt { white-space: nowrap; }

  /* The map takes what is left of a phone, so the operator line does not eat
     a third of it — it is on every other screen, and this one is a map. */
  .k-mapscreen ~ .k-foot, .k-pane > .k-mapscreen + .k-foot { display: none; }
  /* Nearly the width of the map, which is what tells the loader there is no
     "beside" here and puts it along the bottom instead — so the position
     stays in one place and this rule is about the size only. */
  .k-mapcard { width: calc(100% - 20px); max-height: 45%; }
  .mp-zoom { bottom: 10px; right: 10px; }
}

/* Teavita probleemist (ISS-1): the operator's report box, opened from their
   own menu (`rail.rs`) and standing by the rail's foot where that menu is.
   The frame draws it for the superadmin alone. */
.k-issue {
  position: fixed;
  left: calc(var(--k-rail) + 14px);
  bottom: 14px;
  z-index: 320;
}
.k-issuebox {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  width: min(320px, calc(100vw - var(--k-rail) - 28px));
  padding: var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r7);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.k-issuebox[hidden] { display: none; }
.k-issuesaid {
  resize: vertical;
  min-height: 84px;
  border: 1px solid var(--line);
  border-radius: var(--r4);
  background: var(--page);
  color: var(--ink);
  padding: var(--s4);
  font: inherit;
  font-size: var(--t5);
}
.k-issuesaid:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.k-issuesend {
  border: 0;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--accent-ink);
  padding: var(--s3) var(--s7);
  font-size: var(--t4);
  font-weight: 600;
  cursor: pointer;
}
.k-issuesend:hover { background: var(--accent-down); }
.k-issuesend:disabled { opacity: .6; cursor: default; }
/* A phone's bottom edge is the rail's (2331 above), so the box stands above
   it and takes the width — the menu entry is reachable there too, and a tool
   that opened into nothing would be a menu that lies. */
@media (max-width: 860px) {
  .k-issue { left: 12px; right: 12px; bottom: 84px; }
  .k-issuebox { width: auto; }
}

/* ── the 360° pictures (KODU-78) ─────────────────────────────────────────── */

/* The portal's own viewer: a WebGL2 canvas in the gallery's box, turned by
   dragging, the plain picture underneath it for a browser with no script.
   The hint is a pill over the foot of the box until the first turn. */
.k-panobox {
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 480px;
  background: var(--chip);
  cursor: grab;
  touch-action: none;
  user-select: none;
}
.k-panobox:active { cursor: grabbing; }
.k-panobox:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.k-panoflat img { width: 100%; height: 100%; object-fit: cover; display: block; }
.k-pano.is-live .k-panoflat { display: none; }
.k-panohint {
  position: absolute;
  left: 50%;
  bottom: var(--s4);
  transform: translateX(-50%);
  margin: 0;
  padding: var(--s2) var(--s5);
  border-radius: var(--r7);
  background: var(--k-light-veil);
  color: var(--k-light-fg);
  font-size: var(--t4);
  white-space: nowrap;
  pointer-events: none;
  transition: opacity 0.4s;
}
.k-pano:not(.is-live) .k-panohint, .k-pano.is-moved .k-panohint { opacity: 0; }
.k-pano:not(.is-live) .k-panofull { display: none; }
.k-pano .k-panofull { opacity: 1; }
.k-panothumb img { object-fit: cover; }
/* Full screen is the same box in the dialog, the whole viewport of it. */
.k-panolight .k-panobox {
  width: 100vw;
  height: 100vh;
  aspect-ratio: auto;
  max-height: none;
  border-radius: 0;
}

/* ── the owner's pictures on the edit screen (KODU-78) ───────────────────── */

/* Beside the form (KODU-81, KODU-82): a column with a rule over each kind,
   three tiles to a row, every tile the card's own proportion, and the way
   to add one is a tile too — a dashed slot at the end of the row that is
   the file picker itself, and the drop zone. A standing picture carries its
   ✕ over the corner; a plan and a panorama are contained on paper the way
   the page draws them; a picture on its way up pulses until it is back. */
.k-pictures { display: flex; flex-direction: column; min-width: 0; scroll-margin-top: var(--s8); }
.k-picsec {
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  margin: 0;
  padding-block: var(--s9);
  border-top: 1px solid var(--line2);
  transition: background 0.15s ease;
}
.k-picsec.is-over { background: var(--chip); }
.k-piclabel { margin: 0; font-size: var(--t4); font-weight: 500; letter-spacing: 0.06em; color: var(--mute); }
.k-pictures > .k-problem { margin-bottom: var(--s8); }
.k-pic {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--chip);
}
.k-pic.is-pending { animation: k-pulse 1.2s ease-in-out infinite; }
@keyframes k-pulse { 50% { opacity: 0.45; } }
.k-picbadge {
  position: absolute;
  left: var(--s3);
  bottom: var(--s3);
  padding: var(--s1) var(--s4);
  border-radius: var(--r-pill);
  background: var(--k-light-veil);
  color: var(--k-light-fg);
  font-size: var(--t2);
  font-weight: 500;
  letter-spacing: 0.06em;
  pointer-events: none;
}
/* Every photograph carries the badge; the first alone shows it, so a tile
   taken off hands the word to the next without a redraw. */
.k-pic.is-photo ~ .k-pic.is-photo .k-picbadge { display: none; }
/* The standing pictures keep their own box for the suites to count in, and
   `contents` lets its tiles sit in the grid beside the add tile. */
.k-picstanding { display: contents; }
.k-pictile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  aspect-ratio: 3 / 2;
  border: 1px dashed var(--line2);
  border-radius: var(--r6);
  color: var(--ink3);
  font-size: var(--t4);
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.k-pictile svg { width: 18px; height: 18px; }
.k-pictile:hover, .k-picpick:focus-within, .k-picsec.is-over .k-pictile {
  border-color: var(--ink);
  background: var(--surface);
  color: var(--ink);
}
/* The tour's press stands under its box, at the right. */
.k-tourrow .ui-field { width: 100%; flex: none; }
.k-pic a, .k-pic img { display: block; width: 100%; height: 100%; }
.k-pic img { object-fit: cover; }
.k-pic.is-plan img, .k-pic.is-pano img { object-fit: contain; background: var(--k-paper); }
.k-pic form, .k-pic > .k-picdrop { position: absolute; top: var(--s2); right: var(--s2); margin: 0; }
.k-picdrop {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--k-light-line);
  background: var(--k-light-veil);
  color: var(--k-light-fg);
  cursor: pointer;
}
.k-picdrop svg { width: 13px; height: 13px; }
.k-picdrop:hover, .k-picdrop:focus-visible { border-color: var(--k-light-fg); background: var(--k-light-bg); }
/* Off the screen, not out of the document: a keyboard still reaches the
   picker, and the tile — the label itself — lights when it does. */
.k-picpick input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* With the script a choice is the press, so the button has nothing to do;
   without it a first publish has no way to stage, and says so instead —
   its empty kinds stand down with their pickers. */
.k-pictures.is-live .k-picsend { display: none; }
.k-pictures.is-live .k-picnojs { display: none; }
.k-pictures:not(.is-live) .k-picadd[data-stage] { display: none; }
.k-pictures[data-staging]:not(.is-live) .k-picsec:has(.k-picadd[data-stage]) { display: none; }
.k-picadd.is-busy .k-picpick { opacity: 0.6; pointer-events: none; }
.k-pictures .ui-submit { flex: 0 0 auto; }
.k-pictures .ui-pill { align-self: flex-start; }
@media (prefers-reduced-motion: reduce) {
  .k-pic.is-pending { animation: none; opacity: 0.6; }
  .k-edbar .ui-submit:hover, .k-editor .ui-submit:hover { transform: none; }
}

/* ── the seller's strip, and the bureau's page (KODU-83) ─────────────────── */

/* Over the similar ones: whose, the name as a link, and "show all" at the
   row's end when there is more than the strip holds. */
.k-sellerhead .ui-label { margin: 0; }
.k-sellerhead .k-link { font-weight: 600; color: var(--ink); }
.k-sellerhead .k-link:hover { color: var(--accent); text-decoration: none; }
.k-sellerhead .ui-pill { margin-left: auto; }
/* The bureau's page: its picture and its name over a rule, then a strip per
   deal, then the people who sell for it as a row of pills — a `stack` with
   no gap of its own, because each section brings its rule. */
/* The bureau's letterhead in the editor's manner (2026-09-06). The publish
   screen has read this way since KODU-81 and is the best-looking page here —
   paper for a ground, a light serif for the headings, thin rules where cards
   would stand, and each section's name down the left with its content to the
   right. This page borrows the shape rather than inventing a third one.

   No card, no border-bottom under the head: the sections below bring their
   own rule, and a rule under the brand *and* over the first section is two
   lines doing one job. */
.k-bhead {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(var(--s8), 2vw, var(--s10));
  padding-bottom: clamp(var(--s10), 2.6vw, var(--s12));
}
/* The brand is the page's one heading, and it is set like one. */
.k-bhead .k-edh1 { font-size: clamp(var(--t9), 2.6vw, var(--t11)); }
/* The contact reads as rows of a table, not as a paragraph: the label in the
   quiet tracked sans, the answer in the ink beside it, and the columns line
   up down the block. */
.k-reach .k-reachrow { display: grid; grid-template-columns: minmax(0, 150px) minmax(0, 1fr); align-items: baseline; }
@media (max-width: 560px) {
  .k-reach .k-reachrow { grid-template-columns: 1fr; gap: var(--s2); }
}
.k-btitle { display: flex; flex-direction: column; gap: var(--s2); flex: 1 1 260px; min-width: 0; }
/* How to reach them, on the brand's own line (2026-09-06). The header row
   already wraps, so this asks for no breakpoint of its own: beside the name
   where there is room, under it where there is not. No card and no rule
   around it — it is part of the letterhead, not a section standing after. */
.k-bhead .k-reach { flex: 0 1 auto; padding: 0; border: 0; background: none; }
.k-bhead .k-reach .k-reachrows { gap: var(--s3); }
.k-btitle .k-edh1 { white-space: normal; text-wrap: balance; }
.k-bhead .k-edmeta a { color: var(--ink2); }
.k-bhead .k-edmeta a:hover { color: var(--accent); }
.k-broker {
  display: inline-flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s3) var(--s8) var(--s3) var(--s3);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  transition: color 0.15s ease;
}
.k-broker:hover { color: var(--accent); }
/* A seller on the results, worn where the reader can take it off, the way
   the map's rectangle is (KODU-58). */
.k-sellerchip { margin-left: var(--s2); }
/* The bureau's own site under its name (KODU-85), and how to reach it: a
   rule, the label, and each way as a row — the telephone with its hours
   beside it. The picture on a listing's card leads where the name does. */
.k-bsite { align-self: flex-start; color: var(--accent); font-size: var(--t6); font-weight: 600; }
.k-bsite:hover { text-decoration: underline; }
/* The reader's own two settings in the foot (KODU-98): the language and the
   scheme, each a row of words rather than a fold. A footer has room for the
   name — the rail said `RU` because a ninety-six point column has room for
   two letters — and a row of four that are all visible is one press, where a
   fold is two. The one that is on wears the ink and the rule under it; the
   rest are quiet until pointed at. */
/* The greeting on a first visit (KODU-97): a band across the foot of the
   window, because it is about the reader's arrival and not about the market.
   It goes away for a year the moment they answer it, so it is never
   something anybody has to live with.

   It is the body's second row (see `body` at the head of this sheet) and not
   a sheet laid over the window: the shell gives up the height rather than
   lending it, so the rail's own foot — the reader's face, and on a phone the
   whole bar — stays where it is and stays pressable. Not inside the pane
   either: the pane scrolls, and a notice that scrolled away unanswered would
   be a question asked and then withdrawn. The safe-area inset is for a phone
   whose home bar would otherwise sit on the answers.

   Never more than a share of the window, though: taking its height from the
   shell is only fair while the share is small, and on a narrow screen the
   question, the four answers and the notice wrap into a column. Past that it
   scrolls inside itself and the portal keeps most of the glass. */
.k-greet {
  max-height: 45dvh;
  overflow-y: auto;
  padding: var(--s6) clamp(var(--s7), 2vw, var(--s10));
  padding-bottom: calc(var(--s6) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line2);
  background: var(--surface);
}
.k-greetin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5) clamp(var(--s8), 3vw, var(--s12));
  width: 100%;
  max-width: var(--k-page);
  margin-inline: auto;
}
.k-greetnote { flex: 1 1 320px; font-size: var(--t4); color: var(--ink3); }
.k-greetmore { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
/* The one that keeps what is showing is the primary press; the other three
   are quiet words beside it, because changing language is the rarer answer. */
.k-greetyes {
  padding: var(--s3) var(--s7);
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--page);
  font-size: var(--t5);
  font-weight: 600;
}
.k-greetyes:hover { background: var(--accent); color: var(--accent-ink); }
.k-greetlang {
  padding: var(--s3) var(--s5);
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  color: var(--ink3);
  font-size: var(--t5);
}
.k-greetlang:hover { border-color: var(--ink); color: var(--ink); }

/* `.k-footsets` is gone (2026-09-06): the language and the scheme are two
   columns of the foot's own grid now, so the row that held them apart — and
   the padding that held it off the six above — is a wrapper with nothing to
   wrap. What spaces them is the grid's own row gap, like every other pair. */
/* And `.k-footset` is gone too (2026-09-07): the languages and the schemes
   wore a tab's dress — padding, a rule under the chosen one — in a foot
   where everything else is a plain link, *"different looking than rest of
   the footer topic links"*. They are `.k-footlink` like the rows beside
   them, and the one in force is the bold one. */
.k-footlink.is-on { color: var(--ink); font-weight: 600; }

/* ── the blog (KODU-103) ─────────────────────────────────────────────────

   A reading page and nothing else on it: the title in the serif, who wrote
   it and when, the lead, the prose at the reading measure, and the writer
   again at the foot. The prose is a size up from the portal's own text and
   leaded like a book, because a post is read from top to bottom and a
   screen is skimmed. */
.k-blog {
  display: flex;
  flex-direction: column;
  gap: var(--s8);
  width: 100%;
  max-width: var(--prose);
  margin-inline: auto;
  padding-top: clamp(var(--s9), 3vw, var(--s11));
}
.k-blogrow + .k-blogrow { padding-top: var(--s10); border-top: 1px solid var(--hair); }
.k-posttitle {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(var(--t9), 3vw, var(--t11));
  font-weight: 600;
  line-height: var(--lh11);
  letter-spacing: -0.01em;
}
.k-posttitle.is-row { font-size: var(--t9); line-height: var(--lh9); }
.k-postlink { text-decoration: none; }
.k-postlink:hover { color: var(--accent); }
.k-postcrumb { margin: 0; font-size: var(--t4); }
.k-byline { display: flex; align-items: center; gap: var(--s5); margin: 0; font-size: var(--t5); color: var(--ink3); }
.k-portrait { flex: none; width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }
.k-portrait.is-big { width: 96px; height: 96px; }
.k-postbody { font-size: var(--t7); line-height: var(--lh6); padding-top: var(--s5); }
.k-postbody h2 {
  margin: var(--s11) 0 var(--s5);
  font-family: var(--serif);
  font-size: var(--t9);
  font-weight: 600;
  line-height: var(--lh9);
}
.k-postbody p { margin: 0 0 var(--s8); }
.k-postbody ul { margin: 0 0 var(--s8); padding-left: var(--s9); }
.k-postbody li { margin-bottom: var(--s4); }
.k-postbody a { color: var(--accent); text-underline-offset: 3px; }
.k-author {
  display: flex;
  align-items: center;
  gap: var(--s8);
  margin-top: var(--s10);
  padding-top: var(--s10);
  border-top: 1px solid var(--hair);
}

/* The dark phone keeps the house's deeper ground: the portal's own sheet
   restates `--page` in the dark (only the light one moved, 2026-09-07), and
   a restatement served after the tier's phone override would have undone
   it, so the phone's is restated here too. */
@media (prefers-color-scheme: dark) and (max-width: 640px) {
  :root:not([data-theme="light"]) { --page: oklch(0.1 0.004 85); }
}

.k-reachrow dt {
  flex: 0 0 120px;
  font-family: var(--mono);
  font-size: var(--t3);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mute);
}
.k-reachrow dd { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3) var(--s5); margin: 0; font-size: var(--t6); }
.k-reachrow dd a { color: var(--ink); font-weight: 600; }
.k-reachrow dd a:hover { color: var(--accent); }

  :root[data-theme="dark"] {
    /* The tint turns dark and its ink turns light; the hue does not move, so a
       listing keeps the colour a reader recognises it by. */
    --k-thumb-bg: 0.245 0.014;
    --k-thumb-fg: 0.46 0.03;

    --k-face-bg: 0.42 0.07;
    --k-face-fg: 0.93 0.035;

    /* Lighter than the ground here, which is the half of the rule that
       survives inverting everything else. */
    --k-rail-ink: oklch(0.62 0.01 85);

    /* The same four: the full-screen view is dark whichever scheme the page
       is in, because the picture is what is lit. */
    --k-light-bg: oklch(0.13 0.01 85);
    --k-light-fg: oklch(0.98 0.005 85);
    --k-light-veil: oklch(0.1 0.01 85 / 0.5);
    --k-light-line: oklch(0.98 0.005 85 / 0.28);

    /* Paper stays paper. */
    --k-paper: oklch(1 0 0);

    /* The pointer's step up, in the dark: the chip's lightness, since white
       under the pointer is a flash and not a lift. */
    --k-card-hover: oklch(0.25 0.006 85);
    /* The house's own dark ground, restated: only the light one moved. */
    --page: oklch(0.155 0.006 85);

    --k-accent-1: oklch(0.62 0.13 215);
    --k-accent-2: oklch(0.58 0.13 340);
    --k-accent-3: oklch(0.68 0.13 115);
    --k-accent-4: oklch(0.54 0.13 185);
    --k-accent-5: oklch(0.66 0.13 305);
    --k-accent-6: oklch(0.72 0.13 90);
  }
  :root[data-theme="dark"] .k-mapview .mp-page,
  :root[data-theme="dark"] .k-mapscreen .mp-page,
  :root[data-theme="dark"] .k-pickmap .mp-page,
  :root[data-theme="dark"] .k-onehouse .mp-page {
    --surface: color-mix(in oklab, var(--ink) 8%, var(--page));
    --line: color-mix(in oklab, var(--ink) 24%, var(--page));
  }
@media (max-width: 640px) {
  :root[data-theme="dark"] { --page: oklch(0.1 0.004 85); }
}


  /* The map is the page (MAPS-10): everything else floats over it, the way a
     map with a window's worth of world in it has to. */
  /* The map's own palette (MAPS-12). Its own values and not house tokens:
     a console's palette has a surface, a line and an accent, and a map needs
     water, land, and roads that contrast the ground. Drawn on the house
     tokens the whole thing was one near-white sheet with hairlines on it —
     every colour within a few percent of every other, which is a diagram
     and not a map. */
  .mp-page {
    /* The reading every map has taught (MAPS-31): land a warm grey a step
       darker than the page, so the panels float and a white road reads;
       water the soft blue a map's water is; a street white with a grey
       edge, and a trunk road the soft yellow with a firmer one. Not black
       in the dark and not white in the light — a map a step darker than
       the page it sits on, in both. */
    --mp-land: oklch(0.945 0.008 85);
    --mp-edge: oklch(0.860 0.010 85);
    --mp-water: oklch(0.860 0.045 235);
    /* A street is white with a grey edge — the casing is what draws it,
       and at far zooms, where the casing is put away, the road takes the
       casing's grey so the line does not vanish into the ground. */
    --mp-fill-3: oklch(0.990 0.002 85);
    --mp-case-3: oklch(0.850 0.008 85);
    --mp-fill-2: oklch(0.985 0.003 85);
    --mp-case-2: oklch(0.800 0.010 85);
    /* A trunk road: the soft yellow, edged. Quiet enough not to shout over
       the answer, coloured enough to be the road it is. */
    --mp-fill-1: oklch(0.930 0.070 92);
    --mp-case-1: oklch(0.780 0.070 85);
    /* A house is a shade *darker* than the ground, where a road is lighter —
       the two layers then separate without either taking a colour. */
    --mp-house: oklch(0.900 0.012 75);
    --mp-house-edge: oklch(0.820 0.014 75);
    position: fixed; inset: 0; background: var(--mp-water);
  }
  /* Three states (UIC-17): the system's dark unless light was chosen, and
     dark chosen. Lighter than the dark ground — the rule is *separate
     from*, not *darker than* — and the ground itself a dark grey rather
     than the page's near-black, so the sea, the roads and the marks have
     room to differ. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .mp-page {
      --mp-land: oklch(0.300 0.008 85);
      --mp-edge: oklch(0.380 0.010 85);
      --mp-water: oklch(0.340 0.050 235);
      --mp-fill-3: oklch(0.420 0.006 85);
      --mp-case-3: oklch(0.260 0.006 85);
      --mp-fill-2: oklch(0.480 0.008 85);
      --mp-case-2: oklch(0.270 0.007 85);
      --mp-fill-1: oklch(0.580 0.070 85);
      --mp-case-1: oklch(0.300 0.020 85);
      --mp-house: oklch(0.350 0.008 75);
      --mp-house-edge: oklch(0.430 0.010 75);
    }
  }
  :root[data-theme="dark"] .mp-page {
    --mp-land: oklch(0.300 0.008 85);
    --mp-edge: oklch(0.380 0.010 85);
    --mp-water: oklch(0.340 0.050 235);
    --mp-fill-3: oklch(0.420 0.006 85);
    --mp-case-3: oklch(0.260 0.006 85);
    --mp-fill-2: oklch(0.480 0.008 85);
    --mp-case-2: oklch(0.270 0.007 85);
    --mp-fill-1: oklch(0.580 0.070 85);
    --mp-case-1: oklch(0.300 0.020 85);
    --mp-house: oklch(0.350 0.008 75);
    --mp-house-edge: oklch(0.430 0.010 75);
  }
  .mp-map {
    position: absolute; inset: 0; width: 100%; height: 100%;
    display: block; touch-action: none; cursor: grab;
  }
  .mp-map.is-dragging { cursor: grabbing; }
  /* The GPU ground (MAPS-25): a canvas under the svg, never touched by a
     pointer — everything pressable lives in the svg above it. */
  .mp-gpu {
    position: absolute; inset: 0; width: 100%; height: 100%;
    display: block; pointer-events: none;
  }
  /* Until the layer has proved a frame it is not looked at (2026-09-09): an
     opaque context is black before its first clear, and under an svg whose
     sea is the page's ground, that black was the sea for the first beat.
     `visibility`, not `display` — it keeps its size, which is what the
     proving frame is drawn for. */
  .mp-gpu.is-proving { visibility: hidden; }
  /* With the ground painted below, the svg's own land turns to glass: still
     there to be pressed and to light up, no longer paint. Lit, warmed and
     picked shapes come back in colour — they are answers, not ground. */
  .mp-map.is-gpu .mp-land:not(.is-hot):not(.is-picked) { fill: transparent; stroke: transparent; }
  /* Hovering an area fades rather than flips. The shapes are replaced on
     every redraw, so the transition is what makes a pointer crossing a border
     read as a hand moving over a map instead of two colours flickering. */
  /* No hover. An area lighting up under the pointer is a map answering a
     question nobody asked — what is lit is what was searched for. */
  .mp-land {
    fill: var(--mp-land); stroke: var(--mp-edge); stroke-width: 1; stroke-linejoin: round;
  }
  /* An answer lights up and lets go (MAPS-27): the wash says "this is the
     place" and fades to the shape's own paint — a fill that stayed was a
     highlight nobody could put down. No `to` frame: the destination is the
     shape's computed paint, which is the land on the SVG ground and glass on
     the GPU one. */
  .mp-land.is-on { animation: mp-activate 1.6s ease-out; }
  @keyframes mp-activate {
    from { fill: var(--accent-soft); stroke: var(--accent); }
  }
  /* The streets (MAPS-9). Three widths, because three classes: the ones that
     say where the country is, where a town is, and where a house is. Drawn
     under the labels and never filled — a road is a line. */
  /* A road is a light line with a darker edge, which is what makes it read as
     something you could drive on rather than a pencil stroke. Two paths per
     class over the same `d`: every casing first, then every fill, so a road's
     middle is not overdrawn by the next road's edge. */
  /* Houses (MAPS-13): a filled footprint a shade off the ground with a line
     round it, so a terrace reads as houses rather than one blob. Under the
     roads, because a road runs over the ground and a house sits on it. */
  .mp-house {
    fill: var(--mp-house); stroke: var(--mp-house-edge); stroke-width: 0.5;
    stroke-linejoin: round; fill-rule: evenodd;
  }
  /* Width follows the zoom (`--mp-rw`, set per draw). A trunk road drawn five
     pixels wide across a whole country is a string of fat dots: the fragments
     it is made of are two points long at that width, and a round cap on each
     end of a two-point line is a circle. Thinner far out, wider close in. */
  .mp-road, .mp-case { fill: none; stroke-linecap: round; stroke-linejoin: round; }
  .mp-case.is-c1 { stroke: var(--mp-case-1); stroke-width: calc(var(--mp-rw, 1) * 3.4px); }
  .mp-case.is-c2 { stroke: var(--mp-case-2); stroke-width: calc(var(--mp-rw, 1) * 2.3px); }
  .mp-case.is-c3 { stroke: var(--mp-case-3); stroke-width: calc(var(--mp-rw, 1) * 1.4px); }
  .mp-road.is-c1 { stroke: var(--mp-fill-1); stroke-width: calc(var(--mp-rw, 1) * 2px); }
  .mp-road.is-c2 { stroke: var(--mp-fill-2); stroke-width: calc(var(--mp-rw, 1) * 1.2px); }
  .mp-road.is-c3 { stroke: var(--mp-fill-3); stroke-width: calc(var(--mp-rw, 1) * 0.6px); }
  /* Far out there is no middle to show, so the casing is the whole line twice
     — and two strokes a pixel apart is what made them look furry. */
  .mp-map.is-far .mp-case { display: none; }
  /* And with no casing to draw it, a road far out is its own edge's grey:
     a white line on the land was a road nobody could see (MAPS-31). */
  .mp-map.is-far .mp-road.is-c3 { stroke: var(--mp-case-3); }
  .mp-map.is-far .mp-road.is-c2 { stroke: var(--mp-case-2); }
  .mp-map.is-far .mp-road.is-c1 { stroke: var(--mp-case-1); }
  .mp-name { font-size: 11px; fill: var(--mute); text-anchor: middle; pointer-events: none; user-select: none; }
  /* A street's name lies along its street (MAPS-28), in the road's own quiet
     colour — information for a reader already looking there, never a shout.
     The ground-coloured halo keeps it legible across a casing. */
  .mp-road-name {
    font-size: 10.5px; fill: var(--mute); text-anchor: middle;
    paint-order: stroke; stroke: var(--mp-land); stroke-width: 2.6px;
    pointer-events: none; user-select: none;
  }
  .mp-road-name.is-c1 { font-size: 11.5px; }
  .mp-dot { fill: var(--accent); stroke: var(--surface); stroke-width: 1.5; }
  /* Search, floating over the map at the top left. */
  .mp-search {
    position: absolute; top: 12px; left: 12px; z-index: 2;
    width: min(24rem, calc(100vw - 24px));
    display: flex; flex-direction: column; gap: 8px;
  }
  /* `[hidden]` first: an explicit display beats the attribute's own, so
     without this the box stays on screen under the card that replaced it. */
  .mp-ask[hidden] { display: none; }
  .mp-ask {
    display: flex; align-items: center; gap: 4px;
    padding: 4px 4px 4px 14px; border-radius: 999px;
    background: var(--surface); box-shadow: 0 2px 10px rgb(0 0 0 / 0.14);
  }
  .mp-ask input {
    flex: 1; min-width: 0; border: 0; background: none; outline: none;
    font: inherit; font-size: 14px; color: var(--ink2); padding: 8px 0;
  }
  .mp-ask button {
    display: grid; place-items: center; width: 34px; height: 34px;
    border: 0; border-radius: 999px; background: none; color: var(--mute); cursor: pointer;
  }
  .mp-ask button:hover { background: var(--surface2); color: var(--accent); }
  .mp-clear { font-size: 20px; line-height: 1; }
  .mp-clear[hidden] { display: none; }
  /* Suggestions while typing (MAPS-11). `[hidden]` first: an explicit display
     beats the attribute's own, and the list would be open before it is asked. */
  .mp-suggest[hidden] { display: none; }
  .mp-suggest {
    list-style: none; margin: 0; padding: 6px; max-height: 20rem; overflow-y: auto;
    border-radius: 12px; background: var(--surface); box-shadow: 0 2px 10px rgb(0 0 0 / 0.14);
  }
  .mp-suggest li {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px;
    padding: 8px 10px; border-radius: 8px; font-size: 13px; cursor: pointer;
  }
  .mp-suggest li.is-on, .mp-suggest li:hover { background: var(--surface2); }

  /* What has been chosen, and the trip it is one end of (MAPS-16). A chosen
     place looks chosen: a card with its name and where it is, not text left
     in a field. */
  .mp-trip[hidden] { display: none; }
  .mp-trip {
    display: flex; flex-direction: column; gap: 8px;
    padding: 8px; border-radius: 12px;
    background: var(--surface); box-shadow: 0 2px 10px rgb(0 0 0 / 0.14);
  }
  .mp-card {
    position: relative; padding: 8px 32px 8px 10px; border-radius: 8px;
    background: var(--surface2);
  }
  .mp-card-name { margin: 0; font-size: 13px; font-weight: 600; }
  .mp-card-at { margin: 2px 0 0; font-family: var(--mono); font-size: 11px; color: var(--faint); }
  .mp-card-drop {
    position: absolute; top: 6px; right: 6px; width: 22px; height: 22px;
    display: grid; place-items: center; border: 0; border-radius: 999px;
    background: none; color: var(--mute); font-size: 16px; line-height: 1; cursor: pointer;
  }
  .mp-card-drop:hover { background: var(--surface); color: var(--accent); }
  .mp-trip-link {
    align-self: flex-start; padding: 4px 6px; border: 0; border-radius: 6px;
    background: none; color: var(--accent); font: inherit; font-size: 12.5px;
    cursor: pointer; text-decoration: underline; text-underline-offset: .2em;
  }
  .mp-trip-link:hover { background: var(--surface2); }
  .mp-trip-said { margin: 0 6px 2px; font-size: 12.5px; color: var(--mute); }
  .mp-ask-to { box-shadow: none; background: var(--surface2); }

  /* The route: the accent, which nothing on the map itself uses. */
  .mp-trip-case {
    fill: none; stroke: var(--surface); stroke-width: 7;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .mp-trip-line {
    fill: none; stroke: var(--accent); stroke-width: 4;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .mp-hits[hidden] { display: none; }
  .mp-hits {
    max-height: min(28rem, calc(100vh - 8rem)); overflow-y: auto;
    border-radius: 12px; background: var(--surface); box-shadow: 0 2px 10px rgb(0 0 0 / 0.14);
  }
  .mp-hits ul { list-style: none; margin: 0; padding: 6px; }
  .mp-hits li {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    gap: 0 10px; padding: 8px 10px; border-radius: 8px; font-size: 13px;
  }
  .mp-hits li { cursor: pointer; }
  .mp-hits li:hover, .mp-hits li:focus-visible { background: var(--surface2); outline: none; }
  .mp-hits li.is-on { background: var(--accent-soft); }
  .mp-hit-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mp-hit-kind { color: var(--faint); font-size: 11.5px; text-align: right; }
  .mp-hit-at { grid-column: 1 / -1; font-family: var(--mono); font-size: 11px; color: var(--faint); }
  .mp-nothing { margin: 0; padding: 12px 14px; color: var(--mute); font-size: 13px; }

  /* The controls, bottom right, where a hand on a trackpad already is. */
  .mp-zoom {
    position: absolute; right: 12px; bottom: 12px; z-index: 2;
    display: flex; flex-direction: column;
    border-radius: 10px; overflow: hidden; background: var(--surface);
    box-shadow: 0 2px 10px rgb(0 0 0 / 0.14);
  }
  .mp-zoom button {
    width: 38px; height: 38px; border: 0; border-bottom: 1px solid var(--line);
    background: none; color: var(--ink2); font-size: 18px; line-height: 1; cursor: pointer;
  }
  .mp-zoom button:last-child { border-bottom: 0; font-size: 14px; }
  .mp-zoom button:hover { background: var(--surface2); color: var(--accent); }
  .mp-where {
    position: absolute; left: 12px; bottom: 12px; z-index: 2; margin: 0;
    padding: 4px 8px; border-radius: 6px; background: var(--surface);
    font-family: var(--mono); font-size: 10.5px; color: var(--mute);
    box-shadow: 0 1px 4px rgb(0 0 0 / 0.10);
  }
  /* The debug line (MAPS-27): which rung the ground is on and how the frames
     come. Above `.mp-where`, because the door shows both. */
  .mp-debug {
    position: absolute; left: 12px; bottom: 40px; z-index: 2; margin: 0;
    padding: 4px 8px; border-radius: 6px; background: var(--surface);
    font-family: var(--mono); font-size: 10.5px; color: var(--mute);
    box-shadow: 0 1px 4px rgb(0 0 0 / 0.10);
  }
  .mp-debug:empty { display: none; }
  .mp-about {
    position: absolute; right: 12px; top: 12px; z-index: 2;
    max-width: min(26rem, calc(100vw - 24px));
    padding: 8px 12px; border-radius: 10px; background: var(--surface);
    font-size: 12px; color: var(--mute); box-shadow: 0 1px 4px rgb(0 0 0 / 0.10);
  }
  .mp-about summary { cursor: pointer; color: var(--ink2); }
  .mp-about p { margin: 8px 0 0; }
/* 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); }
}
