/* Reset, typography and the components every screen shares: materials (glass, frosted card), buttons, fields, skeleton, sheets, toasts, banners, splash and gates.
   Layout of the screens themselves is in app.css, the flight-monitor components in components.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. They are restyled around, never rewritten. */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
html { background-color: var(--bg); }                         /* the colour under the page: Telegram's header, background and bottom bar are painted with it */
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%; text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
[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; }   /* the outline follows each control's own radius */
[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 sky: ONE fixed layer behind everything, as tall as the screen without a keyboard (--full-h, so it never jumps when the window shrinks).
   Static orbs (radial gradients, no filter: blur, no background-attachment) and, on top of them, a fade to --bg at the top and the bottom edge: the screen edges,
   Telegram's header and its bottom bar stay one solid colour (smoke test C3). It stops 6 px short of the top and bottom edges, where Safari probes for a "bar" (4 px in):
   html and body, solid --bg like the sky's own edges, fill those strips, so nothing changes to the eye. */
.sky { position: fixed; left: 0; right: 0; top: 6px; height: calc(var(--full-h, 100%) - 12px); pointer-events: none;      /* no z-index: painted before #app by DOM order */
  background:
    linear-gradient(180deg, var(--bg) 0, transparent 96px, transparent calc(100% - 132px), var(--bg) 100%),
    radial-gradient(78% 40% at 96% 7%, var(--sky-a) 0, transparent 100%),
    radial-gradient(74% 34% at 0% 46%, var(--sky-b) 0, transparent 100%),
    radial-gradient(86% 42% at 100% 92%, var(--sky-c) 0, transparent 100%),
    var(--bg); }

/* ---- materials. Glass is for controls and navigation that float over the content; content itself (cards) is frosted: almost opaque, no blur.
   The specular border is box-shadow only (an inset hairline, a top highlight, a bottom shade): no mask-composite. Reduced effects (tokens.css) swap the fills for solid colours
   and the filter for none. */
.glass { background: var(--glass-sheen), var(--glass-fill); -webkit-backdrop-filter: var(--glass-filter); backdrop-filter: var(--glass-filter);
  box-shadow: inset 0 0 0 .5px var(--glass-edge), inset 0 1px 0 var(--glass-rim), inset 0 -1px 0 var(--glass-shade), var(--glass-shadow); }
.frost { background: var(--card-fill); box-shadow: inset 0 0 0 .5px var(--card-edge), inset 0 1px 0 var(--card-edge), var(--card-shadow); }

/* ---- buttons. Capsules, a press that springs back (the only places with the spring: presses and chips) */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--s2); min-height: 50px; padding: 0 var(--s5); border-radius: var(--r-pill);
  font-weight: 600; font-size: var(--t-body); line-height: 1.2; text-align: center; user-select: none; -webkit-user-select: none;
  transition: transform var(--d3) var(--spring), background-color var(--d1), opacity var(--d1), box-shadow var(--d1); }
.btn:active { transform: scale(.96); transition-duration: 90ms; transition-timing-function: var(--ease); }
.btn[disabled] { opacity: .45; pointer-events: none; }
.btn.block { width: 100%; }
.btn.sm { min-height: var(--hit); padding: 0 var(--s4); font-size: var(--t-sm); }
.btn.primary { background: var(--accent); color: var(--accent-contrast); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), 0 6px 18px -8px var(--accent); }
.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); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18); }
.btn.danger-tonal { background: var(--danger-soft); color: var(--danger); }
.btn.ok { background: var(--ok); color: var(--ok-contrast); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 6px 18px -8px var(--ok); }
.btn.glass { color: var(--text); }
.btn.large { min-height: 58px; font-size: var(--t-headline); }
@media (hover: hover) { .btn.primary:hover, .btn.danger:hover, .btn.ok:hover { filter: brightness(1.06); } .btn.quiet:hover, .btn.tonal:hover { filter: brightness(.97); } }
.icon-btn { position: relative; width: var(--hit); height: var(--hit); display: grid; place-items: center; flex: none; border-radius: var(--r-pill); color: var(--muted);
  transition: background-color var(--d1), transform var(--d3) var(--spring); }
.icon-btn:active { transform: scale(.9); transition-duration: 90ms; transition-timing-function: var(--ease); }
.icon-btn > svg { position: relative; }
.icon-btn.sm::before { content: ''; position: absolute; inset: 6px; border-radius: 50%; background: var(--surface-2); }      /* a 32 px circle in a 44 px target */
.icon-btn.sm { color: var(--text); }
@media (hover: hover) { .icon-btn:not(.glass):not(.sm):hover { background: var(--surface-2); color: var(--text); } }
/* the round glass button of the top bar (bell, gear, back) */
.icon-btn.glass { color: var(--text); background: var(--glass-sheen), var(--glass-thin); -webkit-backdrop-filter: var(--glass-filter); backdrop-filter: var(--glass-filter);
  box-shadow: inset 0 0 0 .5px var(--glass-edge), inset 0 1px 0 var(--glass-rim), inset 0 -1px 0 var(--glass-shade), 0 6px 16px -8px rgba(0, 0, 0, .25); }
.icon-btn .dot { position: absolute; top: 9px; right: 9px; width: 9px; height: 9px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 2px var(--surface); }
.icon-btn .dot.warn { background: var(--warn); }
.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, .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: 14px; padding: 13px var(--s4);
  line-height: 1.4; transition: border-color var(--d1), box-shadow var(--d1); }
.input { min-height: 52px; }
.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), 0 0 0 5px var(--accent-soft); }
.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 */
.input.flightno { font-weight: 600; letter-spacing: .03em; text-transform: uppercase; }      /* the flight number: shown in capitals (the value is normalised by the server) */
.input.flightno::placeholder { font-weight: 500; text-transform: none; }
.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; }

/* ---- 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 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); background: var(--surface); border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--shadow); 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: 8px 0 0; display: grid; place-items: center; cursor: grab; touch-action: none; }
.sheet-grip i { width: 36px; height: 5px; border-radius: 3px; background: var(--border); opacity: .45; }
/* the title centred between the close button and an empty slot of the same size (iOS sheet) */
.sheet-head { display: grid; grid-template-columns: var(--hit) minmax(0, 1fr) var(--hit); align-items: center; gap: var(--s1);
  padding: 2px calc(var(--sar) + var(--s2)) 2px calc(var(--sal) + var(--s2)); }
.sheet-head::before { content: ''; }
.sheet-head h2 { font-size: var(--t-headline); font-weight: 650; line-height: 1.25; text-align: center; min-width: 0; overflow-wrap: anywhere; }
.sheet-body { overflow: auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); 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)); }
/* A sheet WITHOUT a text field (confirmation, calendar, settings without fields) is glass, and on a phone it floats: 8 px from the sides and the bottom, round on every corner.
   A sheet WITH a field stays opaque (--surface), full width and attached to the bottom: the keyboard rules above paint --surface under it. */
.sheet:not(.has-field) { background: var(--glass-sheen), var(--glass-sheet); -webkit-backdrop-filter: var(--glass-filter-strong); backdrop-filter: var(--glass-filter-strong);
  box-shadow: inset 0 0 0 .5px var(--glass-edge), inset 0 1px 0 var(--glass-rim), var(--shadow); }
@media (max-width: 719.98px) {
  .sheet:not(.has-field) { left: calc(var(--sal) + 8px); right: calc(var(--sar) + 8px); bottom: calc(var(--kb, 0px) + 8px); border-radius: var(--r-sheet);
    padding-bottom: max(calc(var(--sab) - 8px), 0px); max-height: min(calc(var(--app-h, 100vh) - var(--sat) - var(--s4) - 8px), 720px); transform: translateY(calc(100% + 24px)); }
  .sheet:not(.has-field).on { transform: none; }
  .sheet:not(.has-field) .sheet-grip { padding-top: 7px; }
}
@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); 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: glass capsules. 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: 10px; max-width: min(87vw, 420px); min-height: var(--hit); padding: 10px 18px 10px 14px; border-radius: var(--r-pill);
  background: var(--glass-sheen), var(--glass-thick); -webkit-backdrop-filter: var(--glass-filter); backdrop-filter: var(--glass-filter); color: var(--text);
  box-shadow: inset 0 0 0 .5px var(--glass-edge), inset 0 1px 0 var(--glass-rim), var(--glass-shadow);
  font-size: var(--t-sm); font-weight: 600; line-height: 1.3; animation: toast-in var(--d4) var(--spring) backwards; }
.toast svg { color: var(--ok); }
.toast span { min-width: 0; overflow-wrap: anywhere; }
.toast.error svg { color: var(--danger); }
.toast.out { opacity: 0; transform: translateY(8px) scale(.96); transition: opacity var(--d2), transform var(--d2); }
@keyframes toast-in { from { transform: translateY(16px) scale(.88); } }      /* transform only: the capsule is glass, an opacity animation would flatten it */

/* ---- banner: a notice above the content (offline, stale, notifications off) */
.banner { display: flex; align-items: center; gap: var(--s3); padding: 12px var(--s4) 12px 12px; border-radius: var(--r-l); font-size: var(--t-sm); line-height: 1.4;
  background: var(--card-fill); box-shadow: inset 0 0 0 .5px var(--card-edge), var(--card-shadow); }
.banner > svg:first-child { box-sizing: content-box; padding: 7px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); }
.banner.error > svg:first-child { background: var(--danger-soft); color: var(--danger); }
.banner.warn > svg:first-child { background: var(--warn-soft); color: var(--warn); }
.banner > span, .banner-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.banner-text b { display: block; font-weight: 650; }
.banner.stack { flex-wrap: wrap; }                                   /* a long sentence with a button: the button goes under it, where the text starts */
.banner.stack > .banner-text { flex: 1 1 calc(100% - 32px - var(--s3)); }
.banner.stack > .btn { margin-left: calc(32px + var(--s3)); }

/* ---- splash while the app signs in, and gates for the states where the app cannot open (not in Telegram, no access, expired, error).
   Both are transparent over the sky. The mark is a glass-looking orb painted with gradients (no backdrop-filter: the splash fades with opacity). */
.mark { --size: 64px; width: var(--size); height: var(--size); border-radius: 50%; display: grid; place-items: center; flex: none; color: var(--accent);
  background: radial-gradient(100% 100% at 30% 18%, var(--glass-rim) 0, transparent 46%), radial-gradient(90% 90% at 70% 100%, var(--hero-glow) 0, transparent 70%), var(--accent-soft);
  box-shadow: inset 0 0 0 .5px var(--glass-edge), inset 0 -2px 6px var(--glass-shade), var(--glass-shadow); }
.splash { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center; background: transparent; transition: opacity var(--d3) var(--ease); }
.splash.out { opacity: 0; pointer-events: none; }
.splash .mark { --size: 76px; animation: pulse 1.6s var(--ease) infinite; }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(.93); } }
.gate { position: fixed; inset: 0; z-index: 75; overflow-y: auto; display: grid; place-items: center; text-align: center; background: transparent;
  padding: calc(var(--sat) + var(--s6)) calc(var(--sar) + var(--s5)) calc(var(--sab) + var(--s6)) calc(var(--sal) + var(--s5)); }
.gate-card { display: grid; gap: var(--s3); justify-items: center; width: 100%; max-width: 380px; padding: var(--s7) var(--s5) var(--s6); border-radius: 30px;
  background: var(--card-fill); box-shadow: inset 0 0 0 .5px var(--card-edge), inset 0 1px 0 var(--card-edge), var(--card-shadow); }
.gate-card .mark { --size: 72px; margin-bottom: var(--s2); }
.gate h1 { font: 700 var(--t-title2)/1.2 var(--font-display); letter-spacing: -.01em; }
.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(--s1) var(--s1) var(--s1) var(--s4);
  border: 1px solid var(--border); border-radius: 14px; 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; }
}
