/* K5 — the shell's one stylesheet (SHELL-PLAN v2.1 §1.1, §1.8, §1.11; A-4, A-9, A-10).
 *
 * Tokens first; light and dark from the system; forced colours honoured; no motion under reduced
 * motion; reflow down to 320 CSS px at text scale 2.0 with no horizontal page scroll; every control
 * at least 44 x 44 CSS px. No external resource of any kind. */

:root {
  /* The approved MAYA palette, direction `aurora` — the one the phone-proven app runs.
   * Recovered from the preserved native tree, not re-invented: warm bone paper, near-black ink,
   * hairline borders as alpha rather than a solid grey, and one saturated colour in the whole
   * system. `--accent` is reserved for the Telegram row, the microphone and send; content never
   * wears it. */
  --bg: #f4f0eb;
  --surface: #fffdf9;
  --surface-muted: #efeae1;
  --text: #18160f;
  --text-muted: rgba(24, 22, 15, 0.55);
  --border: rgba(24, 22, 15, 0.1);
  --accent: #0a84ff;
  --accent-text: #ffffff;
  --focus: #0a84ff;
  /* The approved bubble is its own value, not a tint of the ink: #ECEAE5 light, #2A2A2C dark. */
  --user-bubble: #eceae5;
  --notice: #efeae1;
  --danger-line: #8a3b12;
  --radius: 0.75rem;
  --gap: 0.75rem;
  --target: 44px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* The platform's own reading face, for MAYA's prose only. No file is fetched for it. */
  --font-reading: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Segoe UI", Roboto, sans-serif;
  /* The device's own unsafe edges — notch or status bar, home indicator, landscape cheeks — read once
   * here so every fixed and full-height surface pads from the same four numbers. `viewport-fit=cover`
   * in entry/index.html is what makes them non-zero in a standalone window (§1.11, A-10). */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* True black, not charcoal: the approved dark direction. */
    --bg: #000000;
    --surface: #0f0f11;
    --surface-muted: #0b0b0c;
    --text: #f4f0eb;
    --text-muted: rgba(244, 240, 235, 0.55);
    --border: rgba(244, 240, 235, 0.12);
    --accent: #0a84ff;
    --accent-text: #ffffff;
    --focus: #0a84ff;
    --user-bubble: #2a2a2c;
    --notice: #0b0b0c;
    --danger-line: #f0b48c;
  }
}

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

[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
  /* A drag that runs out of scrollable content must stop there. Chained up to the page it becomes
   * pull-to-refresh, and a reload ends the memory-only session (A6): the owner is signed out and the
   * conversation is gone. Every scroll container below repeats this with `contain`. */
  overscroll-behavior: none;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 100%;
  line-height: 1.45;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  min-height: 100vh;
  min-height: 100dvh;
}

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

p,
h2,
h3,
h4 {
  margin: 0;
}

button,
input,
textarea {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* ── the app ─────────────────────────────────────────────────────────────────────────────── */

/* The log takes the room that is left; chrome around it never shrinks under its content. */
.identity,
.composer,
.nav,
.timeline-status {
  flex-shrink: 0;
}

.app {
  display: block;
  min-width: 0;
  max-width: 100%;
}

.app-shell {
  /* The height the owner can actually SEE: the window less the virtual keyboard that entry/main.ts
   * measured from visualViewport. On iOS the LAYOUT viewport keeps its full height when the keyboard
   * opens, so `vh`, `dvh` and `@media (max-height: …)` all still report the whole window and this inset
   * is what closes the gap. Where the layout viewport DOES shrink for the keyboard — Chrome, honouring
   * the `interactive-widget=resizes-content` this page asks for — the measured inset is 0 and `100dvh`
   * has already shrunk. So the token is the visible height on both carriers, with no branch. */
  --maya-visible-height: calc(100dvh - var(--maya-keyboard-inset, 0px));
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  max-width: 48rem;
  margin: 0 auto;
  padding-top: var(--safe-top);
  padding-right: var(--safe-right);
  padding-bottom: var(--maya-keyboard-inset, 0px);
  padding-left: var(--safe-left);
  background: var(--bg);
}

.identity {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--border);
  min-width: 0;
}

.identity-brand {
  font-weight: 700;
  letter-spacing: 0.04em;
}

.identity-text {
  color: var(--text-muted);
  overflow-wrap: anywhere;
  min-width: 0;
}

.panel {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
}

/* The conversation column lives INSIDE a fixed-height shell, so it may never be taller than the room
 * that shell gives it: whatever does not fit has to scroll here rather than spill out of the window.
 * Without this the column's own minimum (the cold-start hint at a 2x text scale plus a composer that
 * does not shrink) pushed the composer and «Отправить» below the window and made the page scrollable.
 * The hint's shrink below is what keeps that from happening at the sizes this shell is built for; this
 * is the floor under it. With room to spare nothing here scrolls and the composer does not move. */
.panel--conversation {
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
}

/* The cold-start hint is the one part of the column with no upper bound: it is prose, it wraps, and at
 * a 2x text scale on a 320 px screen its own minimum is most of the screen. So it gives its room back
 * (`min-height: 0`, which a flex item does not do by itself) and carries its remainder itself — the
 * composer below it cannot shrink and «Отправить» has to stay inside the window. */
.timeline-hint {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1rem 1rem 0;
  color: var(--text-muted);
}

.timeline {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  padding: 0.75rem 1rem;
  display: flex;
  flex-direction: column;
  /* The approved rhythm between messages is 14px, not the 12px --gap the rest of the shell uses. */
  gap: 0.875rem;
}

.timeline-status {
  padding: 0 1rem;
  color: var(--text-muted);
  min-height: 0;
}

.turn,
.notice {
  max-width: 100%;
  min-width: 0;
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
}

.turn-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* 🔴 The approved voice split, recovered from the native build.
 *
 * MAYA's own words are set in the PLATFORM face at reading size — on an iPhone that is San
 * Francisco — so long assistant prose reads like native text rather than like a web page. The
 * user's words stay in the shell's own face, a size smaller and tighter, so the two voices are
 * told apart by type rather than by colour. It is the cheapest strong signal a chat product has,
 * and it costs no font file. */
/* The approved chat speaks in ONE bubble, not two. What the person said is held in a rounded
 * container at the right; what MAYA said is not held in anything at all — the preserved source is
 * explicit, her branch sets width 100% and padding 0 with no background, border or radius. That
 * asymmetry is most of the screen's character: her words ARE the page, and the person's words are
 * the thing placed on it. 20px radius and 10px/15px padding are the approved numbers. */
.turn--user {
  align-self: flex-end;
  background: var(--user-bubble);
  max-width: min(78%, 36rem);
  border-radius: 1.25rem;
  padding: 0.625rem 0.9375rem;
}

.turn--user .turn-text {
  font-size: 0.9375rem;
  line-height: 1.375rem;
}

/* No container. Full lane, on the page's own background. */
.turn--maya {
  align-self: stretch;
  width: 100%;
  max-width: 100%;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}

.turn--maya .turn-text {
  font-family: var(--font-reading);
  font-size: 1.0625rem;
  line-height: 1.5625rem;
  letter-spacing: -0.01em;
}

.turn--sending {
  opacity: 0.85;
}

.turn-voice {
  color: var(--text-muted);
}

.turn-failure {
  margin-top: 0.35rem;
  padding-left: 0.5rem;
  border-left: 3px solid var(--danger-line);
  overflow-wrap: anywhere;
}

.turn-retry,
.notice-action {
  min-height: var(--target);
  min-width: var(--target);
  margin-left: 0.25rem;
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  background: var(--surface);
  cursor: pointer;
}

.notice {
  align-self: stretch;
  background: var(--notice);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.notice-text {
  overflow-wrap: anywhere;
  min-width: 0;
}

.reply-link {
  color: var(--accent);
  text-decoration: underline;
  overflow-wrap: anywhere;
}

/* ── the composer ────────────────────────────────────────────────────────────────────────── */

.composer {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.5rem 1rem 0.75rem;
  border-top: 1px solid var(--border);
  background: var(--bg);
}

.composer-input {
  display: block;
  width: 100%;
  min-height: var(--target);
  /* Two fifths of the room, where "the room" is what the keyboard left. `40vh` alone lets the owner
   * drag the textarea taller than the visible area, and «Отправить» goes under the keyboard. */
  max-height: min(40vh, calc(0.4 * var(--maya-visible-height, 100dvh)));
  resize: vertical;
  /* The approved composer is one pill at radius 26 and 52px tall, not a rectangle at 12. The element
   * stays a textarea: the keyboard-inset and text-scale contracts are written against it, and the
   * approved single-line input cannot satisfy them. */
  padding: 0.75rem 1.0625rem;
  border: 1px solid var(--border);
  border-radius: 1.625rem;
  min-height: 3.25rem;
  background: var(--surface);
}

.composer-input[aria-disabled="true"] {
  background: var(--surface-muted);
  color: var(--text-muted);
}

.composer-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.25rem 0.75rem;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.composer-counter--over,
.composer-note {
  color: var(--text);
}

.composer-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem;
  min-width: 0;
}

.composer-send,
.signin-button,
.route-action,
.fullscreen-close,
.nav-button,
.widget-action,
.widget-choice,
.voice-mic,
.voice-send,
.voice-cancel {
  min-height: var(--target);
  min-width: var(--target);
  max-width: 100%;
  padding: 0.5rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  cursor: pointer;
  overflow-wrap: break-word;
  hyphens: auto;
  text-align: center;
}

.composer-send,
.signin-button--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

/* The approved send and microphone are both solid accent rounds. The accent belongs to exactly
 * three controls — the Telegram row, the microphone and send — and the microphone is the one this
 * stylesheet's own token comment already reserved it for while painting it a plain surface.
 * They are stadiums rather than the approved 36px circles because every visible control here is
 * MEASURED at a 44px minimum, at a 2x text scale, on a 320px phone. The words they carry are what
 * makes them reachable by name, and those names are pinned by the verifier, so they stay. */
.composer-send,
.voice-mic,
.voice-send,
.voice-cancel {
  border-radius: 999px;
}

.voice-mic {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

[aria-disabled="true"].composer-send,
[aria-disabled="true"].signin-button,
[aria-disabled="true"].widget-action {
  opacity: 0.6;
  cursor: not-allowed;
}

/* ── the voice control (dom/voice-control.ts) ───────────────────────────────────────────── */

.voice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.voice-mic {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.voice-indicator {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.voice-meter {
  display: inline-flex;
  gap: 2px;
}

.voice-meter-step {
  display: inline-block;
  width: 0.4rem;
  height: 0.9rem;
  border: 1px solid var(--text-muted);
  border-radius: 2px;
}

.voice-meter-step.is-on {
  background: var(--text);
}

.voice-status {
  flex-basis: 100%;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.voice-status:empty {
  display: none;
}

.voice-unavailable {
  color: var(--text);
}

/* ── navigation and route chrome ─────────────────────────────────────────────────────────── */

.nav {
  /* The nav is the surface that ends the column, so the home indicator is its job at EVERY viewport.
   * The row's own padding is a token and the bottom inset is its own longhand, declared after the
   * shorthand: a media block below changes only the token, so it cannot drop the inset by restating
   * `padding` or `padding-block` — which is exactly how the short-viewport block used to lose it. */
  --nav-pad-block: 0.35rem;
  border-top: 1px solid var(--border);
  padding: var(--nav-pad-block) 0.5rem;
  padding-bottom: calc(var(--nav-pad-block) + var(--safe-bottom));
  background: var(--bg);
  /* The five routes scroll sideways on a short viewport (below); that drag stays in the nav. */
  overscroll-behavior: contain;
}

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.nav-list > li {
  flex: 1 1 auto;
  display: flex;
}

.nav-button {
  flex: 1 1 auto;
  padding: 0.35rem 0.5rem;
  font-size: 0.875rem;
  background: transparent;
  border-color: transparent;
  overflow-wrap: break-word;
  hyphens: auto;
}

.nav-button[aria-current="page"] {
  background: var(--surface);
  border-color: var(--border);
  font-weight: 600;
}

.panel--route {
  overflow-y: auto;
  overscroll-behavior: contain;
}

.route {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap);
  padding: 1rem;
}

.route-note,
.route-identity {
  overflow-wrap: anywhere;
}

/* ── sign-in (the signed-out root state) ─────────────────────────────────────────────────── */

.signin {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  max-width: 30rem;
  margin: 0 auto;
  /* Sign-in is the root screen when signed out (SH-04) and has no .app-shell above it, so it carries
   * its own insets. */
  padding: calc(1.5rem + var(--safe-top)) calc(1rem + var(--safe-right)) calc(2rem + var(--safe-bottom)) calc(1rem + var(--safe-left));
}

.signin-title {
  font-size: 1.5rem;
}

.signin-reason {
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  background: var(--notice);
}

.signin-status:empty,
.signin-countdown:empty {
  display: none;
}

.signin-status,
.signin-countdown {
  overflow-wrap: anywhere;
  font-weight: 600;
}

.signin-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.signin-group-title {
  font-size: 1rem;
}

.signin-step,
.signin-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}

.signin-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.signin-input {
  width: 100%;
  min-height: var(--target);
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  background: var(--surface);
}

.signin-input[aria-invalid="true"] {
  border-color: var(--danger-line);
  border-width: 2px;
}

.signin-hint,
.signin-note {
  color: var(--text-muted);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

.signin-error {
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The approved ribbon-v1 mark. It is a CSS background because <img> and <svg> are outside the
 * closed tag set the build enforces — the element that carries it is an ordinary div, marked
 * aria-hidden, so the mark is decoration and the heading is what is read. */
.signin-mark {
  width: 6.5rem;
  aspect-ratio: 820 / 474;
  margin: 0 auto;
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%22220 408 820 474%22 fill=%22none%22 data-maya-identity=%22ribbon-v1%22%3E%3Cdefs%3E%3ClinearGradient id=%22maya-ribbon-a%22 x1=%22400%22 y1=%22485%22 x2=%22784%22 y2=%22851%22 gradientUnits=%22userSpaceOnUse%22%3E%3Cstop stop-color=%22%230055ff%22/%3E%3Cstop offset=%22.5%22 stop-color=%22%2308a9fc%22/%3E%3Cstop offset=%221%22 stop-color=%22%2332c5f4%22/%3E%3C/linearGradient%3E%3ClinearGradient id=%22maya-ribbon-b%22 x1=%220%22 y1=%22580%22 x2=%220%22 y2=%22875%22 gradientUnits=%22userSpaceOnUse%22%3E%3Cstop stop-color=%22%230532ed%22/%3E%3Cstop offset=%22.58%22 stop-color=%22%23007dff%22/%3E%3Cstop offset=%221%22 stop-color=%22%2322baf5%22/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath data-maya-ribbon=%22base%22 d=%22M240 790C238 769 249 747 262 724C302 652 363 534 406 475C445 422 503 415 541 443C586 472 600 556 631 674C691 576 751 484 821 475C900 463 945 520 941 606C939 694 946 721 984 743C1026 765 1028 799 997 827C960 865 904 870 864 846C809 814 804 747 807 651C783 682 767 715 748 746C709 812 665 863 610 853C545 844 521 783 499 716C489 684 480 653 473 636C444 689 419 747 393 801C369 854 325 873 284 850C252 834 239 814 240 790Z%22 fill=%22url(%23maya-ribbon-a)%22/%3E%3Cpath data-maya-ribbon=%22fold%22 d=%22M240 790C286 700 425 574 501 582C565 571 604 638 639 709C668 770 716 773 750 740C793 654 866 567 941 598C939 693 946 721 984 743C1026 765 1028 799 997 827C960 865 904 870 864 846C809 814 804 747 807 651C783 682 767 715 748 746C709 812 665 863 610 853C545 844 521 783 499 716C489 684 480 653 473 636C444 689 419 747 393 801C369 854 325 873 284 850C252 834 239 814 240 790Z%22 fill=%22url(%23maya-ribbon-b)%22/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

.signin-matches {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* A match is a name and, under it, the street address that tells two of the same name apart. The
 * row is a plain surface: the accent belongs to Telegram, the microphone and send, never to content. */
.signin-match {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  align-items: flex-start;
  text-align: left;
}

.signin-match-name {
  font-weight: 600;
}

.signin-match-address {
  color: var(--text-muted);
  font-size: 0.875rem;
}

.signin-chosen {
  font-weight: 600;
}

/* The submits are ordinary surfaces. The accent is spoken for — the Telegram row, the microphone
 * and send — and a second blue button on the same screen would dilute the one that matters. */
.signin-button--submit {
  font-weight: 600;
}

/* The one accent control on this screen. */
.signin-button--telegram {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

/* «Моего бизнеса ещё нет» and «Другой способ входа»: present, but never competing with the way in. */
.signin-button--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
  font-size: 0.9375rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.signin-retry {
  min-height: var(--target);
  min-width: var(--target);
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  background: var(--surface);
  cursor: pointer;
}

.noscript {
  padding: calc(1rem + var(--safe-top)) calc(1rem + var(--safe-right)) calc(1rem + var(--safe-bottom)) calc(1rem + var(--safe-left));
}

/* ── widgets (dom/host.ts) ───────────────────────────────────────────────────────────────── */

.widget {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 100%;
  min-width: 0;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow-wrap: anywhere;
}

.widget-heading {
  font-size: 1.05rem;
}

.widget-block--group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.widget-choice {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  text-align: start;
}

.widget-choice--static,
.widget-field {
  min-height: var(--target);
  padding: 0.5rem;
  border: 1px dashed var(--border);
  border-radius: 0.5rem;
}

.widget-field--refused {
  border: 2px solid var(--danger-line);
}

.leaf--not-known {
  font-style: italic;
}

.leaf-detail,
.widget-explanation {
  color: var(--text-muted);
  font-size: 0.875rem;
}

.widget-limitation {
  padding-left: 0.5rem;
  border-left: 3px solid var(--border);
}

.widget-limitation--blocking {
  border-left-color: var(--danger-line);
  font-weight: 600;
}

.widget-table-scroll {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.widget-table {
  border-collapse: collapse;
  min-width: 100%;
}

.widget-table caption {
  text-align: start;
  font-weight: 600;
  padding-bottom: 0.25rem;
}

.widget-table th,
.widget-table td {
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--border);
  text-align: start;
  vertical-align: top;
}

.widget-table .cell--end {
  text-align: end;
}

.widget--collapsed,
.widget--terminal {
  background: var(--surface-muted);
}

.widget-sentence,
.widget-stale {
  color: var(--text-muted);
}

/* ── the fullscreen host ─────────────────────────────────────────────────────────────────── */

.fullscreen {
  width: min(100vw, 48rem);
  max-width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0 auto;
  /* The dialog fills the window, so it clears the notch and the cheeks itself; the home-indicator inset
   * is on .fullscreen-body, which is the surface that ends there. The bottom padding is the keyboard
   * inset — the same term .app-shell uses, for the same reason: `100dvh` is the whole window on iOS
   * even with the keyboard up, so without it the body's last line and the close control sit under the
   * keyboard. The box still reaches the window's edge; only its CONTENT stops above the keyboard. */
  padding: var(--safe-top) var(--safe-right) var(--maya-keyboard-inset, 0px) var(--safe-left);
  border: 0;
  /* The UA gives <dialog> `overflow: auto`, which made the DIALOG the scroll container: the bar
   * scrolled away and the drag chained out. The body scrolls instead. */
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
}

/* Scoped to [open]: an author `display` on a closed <dialog> would beat the UA's
 * `dialog:not([open]) { display: none }` by origin and show the empty chrome. */
.fullscreen[open] {
  display: flex;
  flex-direction: column;
}

.fullscreen::backdrop {
  background: rgba(0, 0, 0, 0.45);
}

.fullscreen-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--border);
}

.fullscreen-title {
  font-size: 1.1rem;
  overflow-wrap: anywhere;
  min-width: 0;
}

.fullscreen-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: 1rem 1rem calc(1rem + var(--safe-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ── short LAYOUT viewports (landscape phones, large text): the conversation keeps its room — one
 *    identity line, and the five routes in one row that scrolls inside the nav.
 *
 *    On iOS an open keyboard does NOT reach this query: the layout viewport keeps its full height and
 *    only the visual viewport shrinks, so `max-height` never sees the keyboard. What has to fit above
 *    the keyboard is bounded by --maya-visible-height instead, which is correct on both carriers. ── */

@media (max-height: 32rem) {
  .identity {
    flex-wrap: nowrap;
    padding-block: 0.25rem;
  }

  .identity-text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* `min-height: 0` lets a flex item give up its CONTENT box, never its padding — a border-box item
   * cannot shrink below padding + border. At a 2x text scale those two paddings are 80 px of floor
   * under the log and the hint, which is room the composer needs to stay inside the column. So the
   * short viewport tightens them, exactly as it tightens the identity's and the composer's own. */
  .timeline-hint,
  .timeline {
    padding-block: 0.25rem;
  }

  .composer {
    padding-block: 0.35rem;
  }

  .composer-input {
    height: max(var(--target), calc(1lh + 1rem + 2px));
    max-height: min(30vh, calc(0.3 * var(--maya-visible-height, 100dvh)));
    resize: none;
  }

  .composer {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
  }

  .composer-input {
    flex: 1 1 10rem;
    width: auto;
    min-width: 0;
  }

  .composer-meta {
    order: 3;
    flex-basis: 100%;
  }

  .composer-meta:has(> :not([hidden])) {
    display: flex;
  }

  .composer-meta:not(:has(> :not([hidden]))) {
    display: none;
  }

  .composer-actions {
    flex: 1 1 auto;
    flex-wrap: nowrap;
    justify-content: flex-end;
  }

  .composer-send {
    padding-inline: 0.6rem;
  }

  .voice-mic > span:last-child {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .nav {
    overflow-x: auto;
    overflow-y: hidden;
    /* Only the token: the bottom inset stays whatever the base rule computed it to be. */
    --nav-pad-block: 0.25rem;
  }

  .nav-list {
    flex-wrap: nowrap;
  }

  .nav-list > li {
    flex: 0 0 auto;
  }

  .nav-button {
    white-space: nowrap;
  }
}

/* ── motion: a CSS-only reveal; the text is complete on the first frame ──────────────────── */

@media (prefers-reduced-motion: no-preference) {
  .turn,
  .notice,
  .widget {
    animation: maya-reveal 160ms ease-out both;
  }

  @keyframes maya-reveal {
    from {
      opacity: 0.001;
      transform: translateY(4px);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

/* ── coarse pointers ─────────────────────────────────────────────────────────────────────── */

@media (pointer: coarse) {
  .reply-link {
    display: inline-block;
    min-height: var(--target);
    padding-block: 0.6rem;
  }
}

/* ── forced colours: system colours, visible focus, state never by colour alone ──────────── */

@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid CanvasText;
    outline-offset: 2px;
  }

  .composer-input,
  .signin-input {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
  }

  .nav-button[aria-current="page"] {
    border: 2px solid CanvasText;
  }

  .signin-input[aria-invalid="true"],
  .widget-field--refused {
    border: 3px double CanvasText;
  }

  .voice-meter-step.is-on {
    background: CanvasText;
  }
}
