/* Reset, typography and the components every screen shares: the glass material, buttons, fields, segmented control, chips, sheets, toasts, banners, splash, gates.
   NOTHING HERE HAS A COLOUR, A SIZE OR AN EFFECT OF ITS OWN: everything is a token from the active design system (web/ds/<name>/tokens.css, contract in web/ds/contract.json).
   Layout of the screens is in app.css, the map in map.css. The keyboard rules (sheets at bottom: var(--kb), no animation that moves fixed children)
   come from js/viewport.js: see the comments marked KEYBOARD. */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { background: var(--bg); color: var(--text); font: 400 var(--t-body)/1.5 var(--font); overflow: hidden; overscroll-behavior: none;
  -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-synthesis: none; }
[hidden] { display: none !important; }
button, input, textarea, select { font: inherit; color: inherit; margin: 0; }
button { cursor: pointer; background: none; border: 0; padding: 0; touch-action: manipulation; }
h1, h2, h3, p { margin: 0; }
svg { display: block; flex: none; }
a { color: var(--accent); text-underline-offset: 2px; }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-s); }
[tabindex="-1"]:focus-visible { outline: none; }               /* headings and sheets that take focus from script are not controls: no ring */
::selection { background: var(--accent-soft); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.muted { color: var(--muted); }
.wrap-any { overflow-wrap: anywhere; }                      /* long words and URLs must never widen the page */
.num { font-variant-numeric: tabular-nums; }

/* The level of whatever carries data-level (a region, a chip, a pill): its colour, and the colour of what is written on it. */
[data-level="A1"] { --lv: var(--lv-a1); --lv-ink: var(--lv-a1-ink); }
[data-level="A2"] { --lv: var(--lv-a2); --lv-ink: var(--lv-a2-ink); }
[data-level="B1"] { --lv: var(--lv-b1); --lv-ink: var(--lv-b1-ink); }
[data-level="B2"] { --lv: var(--lv-b2); --lv-ink: var(--lv-b2-ink); }
[data-level="M"] { --lv: var(--lv-m); --lv-ink: var(--lv-m-ink); }

/* ---- THE MATERIAL. A pane of frosted glass: it blurs what is behind it, is lit along its top edge and shaded along its bottom edge, and has a sheen of light
   across it. Three thicknesses (thin, regular, thick: --g-bg and --g-filter pick one). Every value is a token, so a design system without glass (an opaque colour,
   `none` for the filter and the sheen) gets plain panels from the very same markup. The sheen is a second background layer and the edge lights are inset
   shadows: no pseudo-element, no z-index, nothing that could get in the way of the content. */
.glass, .topbar, .card, .ctl, .chip, .seg, .toast, .hint, .sheet {
  background: var(--glass-spec), var(--g-bg, var(--glass-reg-bg));
  -webkit-backdrop-filter: var(--g-filter, var(--glass-reg-filter)); backdrop-filter: var(--g-filter, var(--glass-reg-filter));
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-edge-hi), inset 0 -1px 0 var(--glass-edge-lo), var(--glass-shadow);
}
.chip, .seg, .glass.thin { --g-bg: var(--glass-thin-bg); --g-filter: var(--glass-thin-filter); }
.card, .toast, .sheet, .hint, .glass.thick { --g-bg: var(--glass-thick-bg); --g-filter: var(--glass-thick-filter); }
/* Where the browser cannot blur, where the user asked for less transparency, or where the page chose the plain look (Settings): an opaque pane. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .glass, .topbar, .card, .ctl, .chip, .seg, .toast, .hint, .sheet { background: var(--glass-solid-bg); }
}
@media (prefers-reduced-transparency: reduce) {
  .glass, .topbar, .card, .ctl, .chip, .seg, .toast, .hint, .sheet { background: var(--glass-solid-bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
html[data-glass="solid"] :is(.glass, .topbar, .card, .ctl, .chip, .seg, .toast, .hint, .sheet) { background: var(--glass-solid-bg); -webkit-backdrop-filter: none; backdrop-filter: none; }

/* ---- buttons: capsules */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--s2); min-height: 50px; padding: 0 var(--s6); border-radius: var(--r-pill);
  font-weight: 600; line-height: 1.2; user-select: none; transition: transform var(--d1) var(--ease), background var(--d1), opacity var(--d1), box-shadow var(--d2) var(--ease); }
.btn:active { transform: scale(.97); }
.btn[disabled] { opacity: .5; pointer-events: none; }
.btn.block { width: 100%; }
.btn.sm { min-height: 40px; padding: 0 var(--s4); font-size: var(--t-sm); }
.btn.primary { background: var(--accent); color: var(--accent-contrast); box-shadow: inset 0 1px 0 var(--glass-edge-hi), 0 8px 22px var(--accent-glow); }
.btn.tonal { background: var(--accent-soft); color: var(--accent); }
.btn.quiet { background: var(--surface-2); color: var(--text); }
.btn.ghost { color: var(--accent); }
.btn.danger { background: var(--danger); color: var(--danger-contrast); }
.btn.danger-tonal { background: var(--danger-soft); color: var(--danger); }
.btn.glass { color: var(--text); }
@media (hover: hover) { .btn.primary:hover, .btn.danger:hover { filter: brightness(1.08); } .btn.quiet:hover { filter: brightness(.96); } }
.icon-btn { width: var(--hit); height: var(--hit); display: grid; place-items: center; flex: none; border-radius: var(--r-pill); color: var(--muted);
  transition: background var(--d1), transform var(--d2) var(--ease); }
.icon-btn:active { transform: scale(.92); }
.icon-btn.sm { width: 36px; height: 36px; }
.icon-btn.glass { color: var(--text); }
@media (hover: hover) { .icon-btn:hover { background: var(--surface-2); color: var(--text); } .icon-btn.glass:hover { background: var(--glass-spec), var(--glass-thick-bg); } }
svg.ic { stroke-width: var(--icon-stroke); }        /* the weight of every icon is a token of the design system */
.spinner { width: 18px; height: 18px; border-radius: 50%; border: 2px solid currentColor; border-top-color: transparent; opacity: .8; animation: spin .8s linear infinite; flex: none; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- fields */
.field { display: grid; gap: var(--s2); }
.field > label { font-size: var(--t-meta); font-weight: 600; color: var(--muted); }
.input, .textarea { width: 100%; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-m); padding: 13px var(--s4);
  line-height: 1.4; transition: border-color var(--d1), box-shadow var(--d1); }
.input::placeholder, .textarea::placeholder { color: var(--muted); }
.input:focus-visible, .textarea:focus-visible { outline: none; }
.input:focus, .textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger); }
.input.secret { -webkit-text-security: disc; }              /* a pasted secret shown as dots; type=password makes iOS slow to raise the keyboard */
.textarea { resize: none; min-height: 140px; }
.field-row { display: flex; justify-content: space-between; gap: var(--s3); min-height: 20px; font-size: var(--t-meta); }
.field-error { color: var(--danger); }
.field-count { margin-left: auto; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- a segmented control (two or three choices; the lit thumb slides to the chosen one: --seg-n choices, --seg-i the chosen index, set by ui.js segmented()) */
.seg { --seg-n: 2; --seg-i: 0; position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; padding: 3px; border-radius: var(--r-pill); }
.seg::before { content: ''; position: absolute; top: 3px; bottom: 3px; left: 3px; width: calc((100% - 6px) / var(--seg-n)); border-radius: var(--r-pill);
  background: var(--glass-spec), var(--glass-thick-bg); box-shadow: inset 0 1px 0 var(--glass-edge-hi), var(--shadow-sm);
  transform: translateX(calc(var(--seg-i) * 100%)); transition: transform var(--d3) var(--ease-spring); }
.seg > button { position: relative; min-height: 36px; padding: 0 var(--s3); border-radius: var(--r-pill); font-size: var(--t-sm); font-weight: 600; color: var(--muted); white-space: nowrap;
  transition: color var(--d2) var(--ease); }
.seg > button[aria-pressed="true"] { color: var(--text); }

/* ---- a chip: a small capsule that can be on (aria-pressed). The level ring is an inset shadow, so it also shows in the plain look. */
.chip { display: inline-flex; align-items: center; justify-content: center; gap: var(--s1); min-height: 40px; padding: 0 var(--s3); border-radius: var(--r-pill); color: var(--text);
  font-size: var(--t-meta); font-weight: 700; user-select: none; transition: transform var(--d2) var(--ease), box-shadow var(--d2) var(--ease); }
.chip:active { transform: scale(.95); }
.chip[aria-pressed="true"] { --g-bg: var(--glass-thick-bg); --g-filter: var(--glass-thick-filter);
  box-shadow: inset 0 0 0 2px var(--lv, var(--accent)), inset 0 1px 0 var(--glass-edge-hi), var(--glass-shadow); }
.pill { display: inline-flex; align-items: center; gap: var(--s1); height: 22px; padding: 0 var(--s2); border-radius: var(--r-pill); font-size: var(--t-cap); font-weight: 700;
  background: var(--lv, var(--surface-2)); color: var(--lv-ink, var(--text)); }

/* ---- loading placeholder */
.skeleton { background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface) 37%, var(--surface-2) 63%); background-size: 400% 100%; border-radius: var(--r-m);
  animation: shimmer 1.4s ease infinite; }
@keyframes shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* ---- sheets: a bottom sheet on phones, a centred dialog on wide screens.
   KEYBOARD: bottom is var(--kb): on iOS the keyboard covers the bottom of the layout viewport, so the sheet is lifted by what the keyboard covers.
   Where the window itself shrinks (Telegram on Android, simulated shrink) --kb is 0 and the sheet is already above the keyboard. */
.scrim { position: fixed; inset: 0; z-index: 40; background: var(--scrim); opacity: 0; transition: opacity var(--d2) var(--ease); }
.scrim.on { opacity: 1; }
/* The page behind a sheet is blurred (native look, and it hides the page resizing behind the sheet when the keyboard comes). The scrim's opacity fades the blur in and out with it. Where the
   browser cannot blur, the scrim alone stays. If it stutters on a weak phone, lower --scrim-blur in the design system's tokens.css. */
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .scrim { -webkit-backdrop-filter: blur(var(--scrim-blur)); backdrop-filter: blur(var(--scrim-blur)); }
}
.scrim:not(.on), .sheet:not(.on) { pointer-events: none; }        /* while it slides out (or before it slides in) a tap goes through to the page */
.sheet { position: fixed; z-index: 41; left: 0; right: 0; bottom: var(--kb, 0px); display: flex; flex-direction: column;
  max-height: min(calc(var(--app-h, 100vh) - var(--sat) - var(--s4)), 720px); border-radius: var(--r-xl) var(--r-xl) 0 0; border-bottom: 0;
  padding-bottom: var(--sab); touch-action: pan-y; transform: translateY(100%);
  transition: transform var(--d3) var(--ease), bottom var(--d2) var(--ease); }
.sheet.on { transform: none; }
/* ---- sheets with a text field (class has-field, set by ui.js). js/sheetlift.js decides WHEN; this is how they move (references/ui-pitfalls.md K14 to K19).
   While a field has the focus it sets html.kb-lift (+ --lift-full, the window's height with no keyboard), and when the keyboard starts to rise --lift-kb (the height it covers).
   The sheet is then placed from the TOP of the window: #layer becomes a box at top: 0 with a px height, the sheet sits at the bottom of it (position: absolute; bottom: <kb px>).
   Nothing depends on where the bottom of the window is, so the window shrinking under the sheet changes nothing (iOS lays position: fixed out against a layout viewport that follows
   the new height one frame late: a handover to `bottom: 0` flashed the sheet 308 px low). Nothing animated contains a percentage. The sheet keeps its height (its bottom padding is
   frozen at the value it had on focus) and so rises as one piece.
   WHY `bottom` AND NOT `transform`: iOS moves its native text caret when the page LAYS OUT. A transform runs on the compositor and lays nothing out, so the caret stayed behind and
   then jumped. `bottom` is a layout animation of one small box: cheap, and the caret follows the sheet to within 2 px.
   UP: the lower envelope of the keyboard's motion, measured on an iPhone in three openings: never ahead of it, at most 5 px behind (its 11 px shadow strip hides that).
   DOWN (the keyboard hides, the sheet stays open): the keyboard needs ~165 ms, nearly linear; the sheet takes 120 ms so that it is always ahead of it.
   BELOW the sheet: the keyboard has rounded top corners and the page shows through them. Before the window shrinks that is the page under the sheet's shadow: the sheet's own colour is
   painted 24 px below it (the first shadow). After it shrinks it is what Telegram paints under the page: tg.js paintBelow() gives that the sheet's colour for as long as the lift lasts. */
@media (max-width: 719.98px) {
  html.kb-lift #layer { position: fixed; top: 0; left: 0; right: 0; height: var(--lift-full, 100vh); }
  html.kb-lift .sheet.has-field { position: absolute; top: auto; bottom: var(--lift-kb, 0px); padding-bottom: var(--lift-pad, var(--sab));
    max-height: min(calc(var(--lift-full, 100vh) - var(--lift-want, 0px) - var(--sat, 0px) - var(--s3)), 720px);
    box-shadow: 0 24px 0 0 var(--surface), var(--shadow);
    transition: transform var(--d3) var(--ease), bottom 420ms cubic-bezier(.26, .8, .16, .96); }
  html.kb-lift-down .sheet.has-field { transition: transform var(--d3) var(--ease), bottom 120ms linear; }
  html.kb-lift-snap .sheet.has-field, html.kb-lift .sheet.has-field.instant { transition: none; }     /* the change of placement itself must not animate */
}
/* startViewport({ sheetMode: 'top' }): the alternative that needs no keyboard arithmetic at all. A sheet with a field hangs from the top of the screen, where the field is visible with
   or without a keyboard, so WebKit has nothing to scroll and nothing moves when the keyboard comes. Fine for short forms; a tall sheet should scroll inside. */
html.kb-top .sheet.has-field { top: calc(var(--sat, 0px) + var(--s3)); bottom: auto; border-radius: var(--r-xl); transform: translateY(-115%); }
html.kb-top .sheet.has-field.on { transform: none; }
html.kb-top .sheet.has-field .sheet-grip { display: none; }
.sheet.instant { transition: none; transform: none; opacity: 0; }       /* leaving while the keyboard is up: gone at once, no slide over a screen that is changing size */
.sheet-grip { padding: 10px 0 2px; display: grid; place-items: center; cursor: grab; touch-action: none; }
.sheet-grip i { width: 40px; height: 5px; border-radius: 3px; background: var(--border); opacity: .55; }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); padding: var(--s2) var(--s5) var(--s2) calc(var(--sal) + var(--s5)); }
.sheet-head h2 { font-size: var(--t-title); font-weight: 700; line-height: 1.25; min-width: 0; }
.sheet-body { overflow: auto; min-height: 0; display: grid; gap: var(--s4); align-content: start; padding: var(--s2) calc(var(--sar) + var(--s5)) var(--s5) calc(var(--sal) + var(--s5));
  overscroll-behavior: contain; scroll-padding: var(--s6) 0; }
.sheet-foot { display: grid; gap: var(--s2); padding: var(--s2) calc(var(--sar) + var(--s5)) var(--s4) calc(var(--sal) + var(--s5)); }
@media (min-width: 720px) {
  .sheet { left: 50%; right: auto; bottom: auto; top: calc(var(--app-top, 0px) + var(--app-h, 100vh) / 2); width: min(480px, 92vw); border-radius: var(--r-xl); border-bottom: 1px solid var(--glass-edge); padding-bottom: 0; opacity: 0;   /* centred in the VISIBLE area, keyboard or not */
    transform: translate(-50%, -46%) scale(.97); transition: transform var(--d3) var(--ease), opacity var(--d2); }
  .sheet.on { transform: translate(-50%, -50%); opacity: 1; }
  .sheet-grip { display: none; }
}

/* ---- toasts. Above gates and splash (z-index), lifted above the keyboard, and moved to the top while a sheet is open so they never cover its buttons. */
.toasts { position: fixed; z-index: 90; left: 0; right: 0; bottom: calc(var(--kb, 0px) + var(--sab) + var(--fab) + var(--s6)); display: grid; justify-items: center; gap: var(--s2);
  padding: 0 var(--s4); pointer-events: none; }
#layer.sheet-open .toasts { bottom: auto; top: calc(var(--app-top, 0px) + var(--sat) + var(--s3)); }
.toast { pointer-events: auto; display: inline-flex; align-items: center; gap: var(--s2); max-width: min(92vw, 420px); padding: 11px var(--s4); border-radius: var(--r-pill);
  color: var(--text); font-size: var(--t-sm); font-weight: 500; animation: toast-in var(--d3) var(--ease-spring) backwards; }
.toast span { min-width: 0; overflow-wrap: anywhere; }
.toast svg { color: var(--ok); }
.toast.error { box-shadow: inset 0 0 0 1.5px var(--danger), var(--glass-shadow); }
.toast.error svg { color: var(--danger); }
.toast.out { opacity: 0; transform: translateY(8px); transition: opacity var(--d2), transform var(--d2); }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px) scale(.94); } }

/* ---- banner: a notice above the content (offline, stale) */
.banner { display: flex; align-items: flex-start; gap: var(--s3); padding: 12px var(--s4); border-radius: var(--r-m); background: var(--surface-2); font-size: var(--t-sm); line-height: 1.4; }
.banner svg { margin-top: 1px; color: var(--muted); }
.banner.error { background: var(--danger-soft); } .banner.error svg { color: var(--danger); }
.banner.ok { background: var(--ok-soft); } .banner.ok svg { color: var(--ok); }
.banner > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }

/* ---- splash while the app signs in, and gates for the states where the app cannot open (not in Telegram, no access, expired, error) */
.mark { --size: 64px; width: var(--size); height: var(--size); border-radius: 22px; display: grid; place-items: center; flex: none; background: var(--accent-soft); color: var(--accent); }
.splash { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center; background: linear-gradient(180deg, var(--map-sea-1), var(--map-sea-2)); transition: opacity var(--d3) var(--ease); }
.splash.out { opacity: 0; pointer-events: none; }
.splash .mark { animation: pulse 1.6s var(--ease) infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .55; transform: scale(.94); } }
.gate { position: fixed; inset: 0; z-index: 75; overflow-y: auto; display: grid; place-items: center; text-align: center; background: linear-gradient(180deg, var(--map-sea-1), var(--map-sea-2));
  padding: calc(var(--sat) + var(--s6)) calc(var(--sar) + var(--s6)) calc(var(--sab) + var(--s6)) calc(var(--sal) + var(--s6)); }
.gate-card { display: grid; gap: var(--s3); justify-items: center; width: 100%; max-width: 360px; padding: var(--s6); border-radius: var(--r-xl);
  background: var(--glass-spec), var(--glass-thick-bg); -webkit-backdrop-filter: var(--glass-thick-filter); backdrop-filter: var(--glass-thick-filter);
  border: 1px solid var(--glass-edge); box-shadow: inset 0 1px 0 var(--glass-edge-hi), var(--glass-shadow); }
.gate h1 { font-size: var(--t-title); line-height: 1.25; }
.gate h1:focus { outline: none; }
.gate p { color: var(--muted); overflow-wrap: anywhere; }
.gate .actions { display: grid; gap: var(--s2); width: 100%; margin-top: var(--s2); }
.id-box { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); width: 100%; padding: var(--s2) var(--s2) var(--s2) var(--s4);
  border: 1px solid var(--border); border-radius: var(--r-m); background: var(--surface); }
.id-box code { font: 600 var(--t-body) var(--mono); user-select: all; -webkit-user-select: all; overflow-wrap: anywhere; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
}
