/* The Telegram shell: bottom navigation, viewport and safe area, pull to refresh,
   sheet behaviour.
   ============================================================================
   Loaded after css/app.css and only ever overrides it — the palette, the cards,
   the wheels and the room panel all stay where they are. No colour is invented
   here: every value is a var() from app.css, so both schemes come for free, and
   the spacing, radius, type and weight tokens app.css defines are used in place of
   fresh numbers wherever one of them is the number this file wants.

   AND IT DOES NOT TOUCH css/components.css.
   The Mini App consumes the shared design system directly from the markup —
   js/app.js writes .ds-badge for every status on screen, .ds-btn for every
   button it builds at runtime, and .ds-skeleton / .ds-empty-state /
   .ds-error-state for the three states of every list. Not one ds-* class is
   re-opened below, and none may be: components.css is the single definition
   those three shells share, and a local override of it here is the same
   drift as a second colour table. What this file adds around them is
   placement (.tg-state, .tg-actions) and one modifier of its own on a
   shell-prefixed class (.tg-danger) — never a second opinion about what a
   badge or a button looks like. tests/test_responsive_qa.py pins the exact
   set of app.css classes this layer is allowed to re-open, and components.css
   is not in it.

   Two rules this file keeps to, because breaking either has already cost the
   project a day: a transition always names the properties it animates, and no
   colour is ever one of them (a colour that changes because a custom property
   changed can stick on its old value indefinitely). Guarded by
   tests/test_web_theme.py and tests/test_telegram_shell.py.
   ============================================================================ */

/* Shell measurements. Deliberately not written into a `:root{` block: the palette
   in app.css owns that selector, and its token list is compared against the light
   scheme by test_web_theme.py — nothing here belongs in that comparison. */
html{
  --tabbar-h:58px;
  /* The section you are in. Dark CAU navy on a light page, the crest's gold on
     a dark one — the two colours that read as "chosen" in their own theme.
     --accent flips to a pale navy in the dark palette, which is right for a
     filled button on white and wrong for a 10px label on navy; gold is what
     the dark theme has always used to mark the current thing. Declared here as
     a shell measurement rather than in a :root palette block: css/app.css owns
     :root, and its token list is compared against the light scheme by
     tests/test_web_theme.py. No colour is invented — both sides are tokens. */
  --tabbar-on:var(--accent);
  /* Filled in by syncViewport() in js/app.js from tg.viewportStableHeight and
     tg.viewportHeight. The fallbacks are what a plain browser tab gets. */
  --tg-vh:100%;
  --tg-stable-vh:100%;
  --kb-inset:0px;
  --safe-b:env(safe-area-inset-bottom, 0px);
  --safe-t:env(safe-area-inset-top, 0px);
}
/* Both halves of the dark scheme, the same pair app.css keeps: the default for
   a dark client that has never chosen, and the explicit choice that has to be
   able to beat the media query. Written on `html` rather than `:root` for the
   reason above — same element, a selector app.css's palette tests do not read. */
@media (prefers-color-scheme: dark){
  html:not([data-theme="light"]){ --tabbar-on:var(--gold); }
}
html[data-theme="dark"]{ --tabbar-on:var(--gold); }
html[data-theme="light"]{ --tabbar-on:var(--accent); }

/* ============================================================
   Viewport and safe area
   ============================================================
   tg.expand() makes the window tall; it does not stop the client's own chrome or
   the keyboard from covering the bottom of it. The app is sized to the height
   Telegram says is stable, and every edge that can be covered gets its inset. */
.app{min-height:var(--tg-stable-vh, 100%)}
.hdr{padding-top:calc(var(--space-14) + var(--safe-t))}
/* The panel is `inset:0` on a window Telegram has already made taller than the
   part a person can see. Left at the window's height its bottom bar — now a
   real flex item, see .tg-actionbar — is pinned to an edge below the client's
   own chrome. Clamped to the height Telegram calls stable, it is pinned to the
   edge of the screen instead. */
.panel{max-height:var(--tg-stable-vh, 100%)}
/* Room for the bar under the last card — without it the final room sits behind it. */
main{padding-bottom:calc(var(--tabbar-h) + var(--safe-b) + var(--space-18))}

/* ============================================================
   Bottom navigation
   ============================================================
   Four sections where the thumb already is — the same four the browser product
   lists in its sidebar. The header's segmented control stays in the markup —
   anything still reaching for `.tabs` or `#mine-badge` keeps working — but it
   is not drawn twice.

   The bar is fixed and it stays on screen for all four sections, which is the
   whole point of moving Account out of a `.panel` and into a section of its
   own: a panel is z-index 40 and the bar is 30, so an overlay for one of the
   four made the navigation vanish exactly where a person needed it. The room
   detail is still a panel and still covers the bar — that one is a detail view
   with a Back button and a bottom bar of its own, not a fifth section. */
.hdr .tabs{display:none}

.tabbar{
  position:fixed;left:0;right:0;bottom:0;z-index:30;
  max-width:480px;margin:0 auto;
  /* As many equal columns as there are items, rather than a number written
     here. Two of the sections are drawn only for some readers — Instructions
     when an administrator has switched it on, «Мои комнаты» for whoever is on
     the coffee rota — so a fixed count would leave a gap where a section is not
     drawn and overflow where one is. */
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:var(--space-2);
  padding:var(--space-6) var(--space-8) calc(var(--space-6) + var(--safe-b));
  background:color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  border-top:1px solid var(--border);
  transform:translateY(0);
  transition:transform .22s cubic-bezier(.32,.72,0,1);
}
/* Typing: the keyboard is up, the bar has nothing to offer, and Telegram's own
   lists get out of the way in exactly this manner. */
:root.kb-open .tabbar{transform:translateY(110%)}

.tabbar-item{
  -webkit-appearance:none;appearance:none;border:0;background:transparent;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--space-3);
  padding:var(--space-7) var(--space-2) var(--space-5);
  border-radius:var(--radius-14);cursor:pointer;
  color:var(--text-faint);
  font:var(--weight-640) var(--fs-10-5)/1 var(--font);letter-spacing:.2px;
  touch-action:manipulation;
  transition:transform .12s ease, opacity .16s linear;
}
.tabbar-item:active{transform:scale(.94)}
/* The section you are in: navy on a light page, gold on a dark one. See
   --tabbar-on at the top of this file for why it is not one colour. */
.tabbar-item.on{color:var(--tabbar-on)}
.tabbar-item.on .tabbar-lbl{font-weight:var(--weight-700)}

/* 24px and 22px are not on app.css's size scale, and this file may not add to it:
   an icon box that matches the label's cap height is what the bar needs. */
.tabbar-ico{position:relative;width:24px;height:24px;display:grid;place-items:center}
.tabbar-ico svg{width:22px;height:22px;display:block}
.tabbar-lbl{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}

/* Bookings waiting for an administrator, on the icon rather than beside the label:
   at this size a number after the word pushes the word out of the item. */
.tabbar-badge{
  position:absolute;top:-3px;right:-7px;
  min-width:15px;height:15px;padding:0 var(--space-4);
  border-radius:var(--radius-pill);background:var(--wait);color:var(--on-accent);
  font:var(--weight-700) var(--fs-9-5)/15px var(--font);text-align:center;
  border:1px solid var(--bg);
}
.tabbar-badge[hidden]{display:none}

/* ============================================================
   Pull to refresh
   ============================================================
   One dial under the top edge, pulled down by the finger and spun while the
   requests are in flight. `--pull` and `--pull-turn` are written by drawPull() on
   every touchmove, which is why the transition is switched off while the gesture
   is live: a chase animation at 60fps reads as lag. Letting go removes `.on`, the
   transition comes back, and the dial retracts by itself. */
.ptr{
  position:fixed;left:50%;top:calc(var(--safe-t) + var(--space-8));z-index:30;
  width:var(--size-34);height:var(--size-34);margin-left:calc(var(--size-34) / -2);
  border-radius:50%;display:grid;place-items:center;pointer-events:none;
  background:var(--surface-solid);border:1px solid var(--border);box-shadow:var(--shadow);
  opacity:0;visibility:hidden;
  /* 46px parks it just above the top edge, out of sight until it is pulled in. */
  transform:translateY(calc(var(--pull, 0px) - 46px));
  transition:transform .24s cubic-bezier(.32,.72,0,1), opacity .2s linear, visibility .2s linear;
}
.ptr.on{opacity:var(--pull-fade, 1);visibility:visible;transition:none}
.ptr-ring{
  display:grid;place-items:center;
  width:var(--size-18);height:var(--size-18);color:var(--text-dim);
  transform:rotate(var(--pull-turn, 0turn));
}
.ptr-ring svg{width:var(--size-18);height:var(--size-18);display:block}
/* Far enough to count, and then actually working. */
.ptr.ready .ptr-ring{color:var(--gold)}
.ptr.busy .ptr-ring{color:var(--gold);animation:ptr-spin .8s linear infinite}
@keyframes ptr-spin{to{transform:rotate(1turn)}}

/* ============================================================
   Sheets
   ============================================================
   The project already had sheets; what they lacked was a way out other than the
   scrim, a ceiling of their own, and any idea what to do when the keyboard opens. */
.sheet{
  max-height:calc(var(--tg-stable-vh, 100vh) - var(--space-26));
  overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;touch-action:pan-y;
}
/* Lifted by exactly the height the keyboard has taken, so the field being typed
   into and the button under it are both still on screen. */
.sheet.on{transform:translateY(calc(var(--kb-inset, 0px) * -1))}
/* Under the finger the sheet must not also be easing somewhere. */
.sheet.dragging{transition:none}

/* The grab area is the width of a thumb, not the width of the drawn bar. The
   pseudo-element takes the hits and reports them as the handle itself. */
.sheet .handle{position:relative;cursor:grab}
.sheet .handle::before{
  content:"";position:absolute;
  left:calc(var(--space-18) * -1);right:calc(var(--space-18) * -1);
  top:calc(var(--space-14) * -1);bottom:calc(var(--space-16) * -1);
}
.sheet .handle:active{cursor:grabbing}

/* Nothing behind an open sheet scrolls. overflow is enough — touch-action would
   also stop the sheet's own content from scrolling. */
:root.no-scroll,:root.no-scroll body{overflow:hidden}
.scrim{touch-action:none}

/* Clear of the navigation bar, which it used to sit behind. */
.toast{bottom:calc(var(--tabbar-h) + var(--safe-b) + var(--space-20))}

/* ============================================================
   Admin entry
   ============================================================
   The way into the Admin Panel: a pill in the header next to the avatar,
   injected by js/account.js only for administrators. Gold on gold-soft — the
   crest's accent already marks "elevated" everywhere else in the app. */
.tg-admin-entry{
  -webkit-appearance:none;appearance:none;cursor:pointer;flex:none;
  display:inline-flex;align-items:center;gap:var(--space-5);
  padding:var(--space-7) var(--space-10);
  border:1px solid var(--gold-line);border-radius:var(--radius-pill);
  background:var(--gold-soft);color:var(--gold);
  font:var(--weight-640) var(--fs-10-5)/1 var(--font);letter-spacing:.4px;
  touch-action:manipulation;
  transition:transform .12s ease, opacity .16s linear;
}
.tg-admin-entry:active{transform:scale(.94)}
/* 12px matches the label's cap height; the icon scale in app.css has no token
   this small, and this file may not add one. */
.tg-admin-entry svg{width:12px;height:12px;display:block}

/* ============================================================
   The day strip
   ============================================================
   js/app.js draws the fourteen days as <button>s rather than <div>s, so the
   header's main control can be reached with a keyboard and can say which day
   is chosen. app.css styles .day for a div; these four declarations are the
   whole difference between the two elements, and without them the pills lose
   the page's font and line-height (a button's UA defaults are neither) and
   the numbers come out in the UA's button text colour, which in the dark
   theme is black on navy. `font:inherit` and not a fresh font stack: the pill
   must keep whatever app.css's body says, including its 1.45 line-height —
   that number is what sets the strip's height. */
.day{
  -webkit-appearance:none;appearance:none;
  font:inherit;color:inherit;
}

/* ============================================================
   Buildings
   ============================================================
   The room list groups under campus buildings; with more than one building a
   chip filter sits above it. The chips themselves are app.css's .chip-btn —
   only the shell-specific layout lives here. */
.tg-bldg-filter{
  display:flex;gap:var(--space-6);
  margin:0 calc(var(--space-16) * -1);
  padding:var(--space-2) var(--space-16);
  /* contain, because a sideways flick that runs off the end of the chips is
     otherwise handed to the Telegram client, which reads it as "go back" and
     closes the Mini App mid-filter. */
  overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none;
}
.tg-bldg-filter::-webkit-scrollbar{display:none}
.tg-bldg-chip{flex:none;white-space:nowrap}
/* The same column-and-gap the view itself uses, so grouping does not change
   the rhythm of the cards. */
.tg-bldg-group{display:flex;flex-direction:column;gap:var(--space-12)}
.tg-bldg-head{
  margin:var(--space-6) 0 0;
  font-size:var(--fs-11);font-weight:var(--weight-640);
  letter-spacing:.8px;text-transform:uppercase;color:var(--text-faint);
}

/* ============================================================
   The unread pill in the header
   ============================================================
   Shares the admin pill's shape — the same corner of the same row should not
   have two ideas about what a header control looks like — and drops the gold,
   which marks "elevated" and belongs to the way into the Admin Panel alone. */
.tg-inbox{background:var(--accent-soft);border-color:var(--border);color:var(--text)}
.tg-inbox svg{width:var(--size-14);height:var(--size-14)}
.tg-inbox-n{font-variant-numeric:tabular-nums}
.tg-inbox[hidden]{display:none}

/* ============================================================
   Where a badge sits
   ============================================================
   This file used to draw the seven display statuses itself (.tg-st) and the
   five states of a release request (.tg-rel), in colour families of its own
   invention. Both are deleted: js/app.js writes components.css's .ds-badge
   for every one of them, so "Отклонена" cannot be red on this screen and grey
   in the panel the administrator refused it from.

   All that is left is the slot the badge sits in — a flex row with a room name
   or a colleague's name beside it, either of which would otherwise squeeze the
   pill. Placement only; what a badge looks like is not this file's business
   and may not become it. */
.tg-state{flex:none;min-width:0}

/* ============================================================
   Blocks inside a booking card
   ============================================================ */
/* A row of buttons that wraps rather than squeezing three labels into one line:
   the Uzbek word for "release" is half again the length of the Russian one, and
   a button that has to be read twice is not a button.

   The buttons in these rows are the design system's .ds-btn now, which brings
   its own 40px floor and its own intents; the element selector below is
   deliberately not a .ds-btn one — this file may not re-open a components.css
   class — and only says how they share the row. */
.tg-actions{
  display:flex;flex-wrap:wrap;gap:var(--space-8);
  margin-top:var(--space-12);
}
.tg-actions>button{flex:1 1 auto;min-height:var(--size-40)}

/* Destroying something you own — releasing a room, refusing a colleague,
   withdrawing an ask — is a bordered button with red writing, not a filled red
   one. .ds-btn--danger fills, and a filled red button is the last step of a
   confirmation; these three sit three-to-a-row on a card that is otherwise
   reporting, and three red blocks there read as an emergency. Modifier on top
   of .ds-btn--secondary, so the shape, the height and the press stay the
   shared ones and only the ink changes. */
.tg-danger{color:var(--danger);border-color:var(--danger-soft)}
.tg-bk-actions{border-top:1px solid var(--border);padding-top:var(--space-12)}
.tg-bk-st{margin:var(--space-7) 0 0}
.tg-bk-note{
  margin:var(--space-10) 0 0;padding:var(--space-9) var(--space-11);
  border-radius:var(--radius-12);background:var(--surface-2);
  font-size:var(--fs-12-5);line-height:1.4;color:var(--text-dim);
}

/* Why it was refused. Red down the left edge rather than a red box: this is an
   explanation, not an alarm, and it has to stay readable at length. */
.tg-reason{
  margin-top:var(--space-12);padding:var(--space-10) var(--space-12);
  border-left:2px solid var(--busy);border-radius:0 var(--radius-12) var(--radius-12) 0;
  background:var(--busy-soft);
}
.tg-reason-head{
  margin:0;font-size:var(--fs-11);font-weight:var(--weight-700);
  letter-spacing:.7px;text-transform:uppercase;color:var(--busy);
}
.tg-reason-code{margin:var(--space-5) 0 0;font-size:var(--fs-13);font-weight:var(--weight-620)}
.tg-reason-note{
  margin:var(--space-5) 0 0;font-size:var(--fs-12-5);line-height:1.45;color:var(--text-dim);
  overflow-wrap:anywhere;
}

/* An offer of another room. Gold-edged: it is the one block on the screen that
   is asking for a decision rather than reporting one. */
.tg-offer{
  margin-top:var(--space-12);padding:var(--space-12);
  border:1px solid var(--gold-line);border-radius:var(--radius-14);background:var(--gold-soft);
}
.tg-offer-head{
  margin:0;font-size:var(--fs-11);font-weight:var(--weight-700);
  letter-spacing:.7px;text-transform:uppercase;color:var(--gold);
}
.tg-offer-lead{margin:var(--space-5) 0 var(--space-10);font-size:var(--fs-12-5);color:var(--text-dim)}
.tg-offer-note{
  margin:var(--space-10) 0 0;font-size:var(--fs-12-5);line-height:1.45;color:var(--text-dim);
  overflow-wrap:anywhere;
}

/* ============================================================
   Release requests
   ============================================================
   A colleague asking for a room is a fact about that booking, so it is drawn on
   the booking rather than in a section of its own — the bottom bar has three
   places and a request nobody scrolls to might as well not have been sent. */
.tg-rr-list{display:flex;flex-direction:column;gap:var(--space-10)}
.tg-rr{
  margin-top:var(--space-12);padding:var(--space-12);
  border:1px solid var(--border);border-radius:var(--radius-14);background:var(--surface-2);
}
.tg-rr-list .tg-rr{margin-top:0}
/* What this block is, before who it is from: a request arriving on somebody's
   own booking has to announce itself, or it reads as part of the booking. */
.tg-rr-title{
  margin:0 0 var(--space-8);font-size:var(--fs-11);font-weight:var(--weight-700);
  letter-spacing:.7px;text-transform:uppercase;color:var(--wait);
}
.tg-rr-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8)}
/* Name, and a department when the payload carries one. Nothing else: a phone
   number or a telegram handle on this row would be on screen all day for two
   people who only need to know who they are talking to. */
.tg-rr-who{
  min-width:0;font-size:var(--fs-13-5);font-weight:var(--weight-640);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.tg-rr-who small{
  display:block;font-size:var(--fs-10-5);font-weight:var(--weight-600);letter-spacing:.6px;
  color:var(--text-faint);margin-top:var(--space-2);
}
.tg-rr-where{margin:var(--space-7) 0 0;font-size:var(--fs-12);color:var(--text-faint)}
/* What they wrote, in quotation marks, because it is theirs and not ours. */
.tg-rr-msg{
  margin:var(--space-8) 0 0;font-size:var(--fs-13);line-height:1.45;color:var(--text);
  overflow-wrap:anywhere;
}
.tg-rr-msg:not(:empty)::before{content:"«"}
.tg-rr-msg:not(:empty)::after{content:"»"}
.tg-rr-answer{
  margin:var(--space-8) 0 0;padding-left:var(--space-10);
  border-left:2px solid var(--border-strong);
  font-size:var(--fs-12-5);line-height:1.45;color:var(--text-dim);overflow-wrap:anywhere;
}
.tg-rr-answer span{
  display:block;font-size:var(--fs-10-5);font-weight:var(--weight-700);letter-spacing:.6px;
  text-transform:uppercase;color:var(--text-faint);
}
/* One sentence saying what the three buttons under it mean — that the room is
   still the owner's and the answer is theirs. Drawn only while the request is
   open, so it never survives as an instruction for a decision already made. */
.tg-rr-lead{
  margin:var(--space-10) 0 0;font-size:var(--fs-12-5);line-height:1.45;color:var(--text-dim);
}
/* The mini-chat, folded away until it is asked for and scoped to this request
   alone — two people arguing about one room must not find themselves in the
   conversation about another. */
.tg-rr-chat{margin-top:var(--space-12);padding-top:var(--space-12);border-top:1px solid var(--border)}
.tg-rr-chat[hidden]{display:none}

/* ============================================================
   Sheets built at runtime
   ============================================================ */
/* The summary block a runtime sheet opens with. app.css owns .kv itself; only
   the space under it is this file's business, and it takes a class of its own
   rather than re-opening that component. */
.tg-facts{margin-bottom:var(--space-14)}
.tg-ta{
  width:100%;min-height:var(--size-66);resize:vertical;
  padding:var(--space-11) var(--space-12);
  border:1px solid var(--border-strong);border-radius:var(--radius-12);
  background:var(--surface-2);color:var(--text);
  font:var(--weight-500) var(--fs-13-5)/1.45 var(--font);
}
/* The same focus ring the text inputs above it get. A ring and not a colour
   transition: a colour that animates off a custom property is the bug this
   file's header is about. */
.tg-ta:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.tg-hint{margin:var(--space-6) 0 0;font-size:var(--fs-11);color:var(--text-faint)}
.tg-chat{display:flex;flex-direction:column;gap:var(--space-10);min-height:var(--size-96)}
.tg-flush{margin:0}

/* ============================================================
   The room's own day, in the panel
   ============================================================ */
.tg-slot{cursor:default;flex-wrap:wrap}
.tg-slot-to{font-size:var(--fs-11);font-weight:var(--weight-500);color:var(--text-faint)}

/* ============================================================
   The booking panel's bottom bar
   ============================================================
   Reported as "the panel overlaps the content": «с», «до», the duration and
   Забронировать sat in a translucent strip absolutely positioned over the
   scroll, so the last block read through it and the button could land on top
   of the time wheels on a short screen.

   Three separate faults, one per rule below.

   1. It floated. `position:absolute` takes the bar out of flow, which is why
      css/app.css has to reserve 150px of padding under the content and why the
      reserve is a guess: it is right at one type size and wrong at another.
      Sticky puts it back in the flex column — `.panel` is a column, the body
      above it is the only thing that scrolls — so the bar is pinned to the
      bottom by the layout rather than by a number, and the content ends where
      the bar begins.
   2. It was see-through. `color-mix(in srgb, var(--bg) 92%, transparent)` plus
      a blur is a frosted strip; over a scrolling list it is text over text.
      --surface-solid is the one token that is opaque in both schemes — white
      on a light page, navy-900 on a dark one — and the blur goes with it,
      because a backdrop-filter creates a containing block and was part of why
      the wheels above clipped strangely.
   3. It sat under the home indicator. The safe-area inset is re-stated here
      rather than inherited, because the padding shorthand this rule sets would
      otherwise drop app.css's own.

   z-index above the panel body's own stacking contexts (the wheel list is 2,
   its fades 3) so nothing in the time picker can scroll over the button.

   These are `.tg-` names on nodes js/app.js tags at runtime, not overrides of
   .actionbar / .panel-body: tests/test_responsive_qa.py pins the exact set of
   app.css classes this file may re-open and those two are not in it. */
.tg-actionbar{
  position:sticky;bottom:0;z-index:5;flex:none;
  background:var(--surface-solid);
  backdrop-filter:none;-webkit-backdrop-filter:none;
  padding-bottom:calc(var(--space-14) + var(--safe-b));
}
/* The 150px reserve app.css keeps for a floating bar is a hole under the last
   block once the bar is in flow. One ordinary gap instead. */
.tg-panel-body{padding-bottom:var(--space-20)}

/* The time picker.
   `overflow-x:clip` and not `hidden`: hidden on an axis turns the box into a
   scroll container on the other one too, which is what let the wheels drag the
   whole panel sideways at 375px. min-width:0 on the columns is what stops two
   180px-tall lists from refusing to shrink inside a 343px-wide card. */
.tg-wheels{min-width:0;overflow-x:clip}
.tg-wheel-col{flex:1 1 0;min-width:0}
.tg-wheel{max-width:100%}
/* A flick that runs off the end of a wheel must stay in the wheel: handed on,
   Telegram reads it as "close the Mini App" and the panel goes with it. */
.tg-wheel-list{overscroll-behavior:contain;touch-action:pan-y}

/* ============================================================
   Активные | Архив
   ============================================================
   The two halves of «Мои брони». The control itself is the design system's
   .ds-segmented — this class is only the row it sits in and the count on each
   half, because css/components.css owns what a segmented control looks like
   and this file may not hold a second opinion about it. */
.tg-mine-tabs{align-self:flex-start;max-width:100%;margin-bottom:var(--space-2)}
.tg-mine-n{font-variant-numeric:tabular-nums;color:var(--text-faint)}
/* Who ended it and when, under the reason. Same ink as the comment above it —
   this is a fact about the decision, not a second alarm. */
.tg-reason-who{
  margin:var(--space-7) 0 0;font-size:var(--fs-11-5);line-height:var(--lh-1-45);
  color:var(--text-dim);overflow-wrap:anywhere;
}

/* ============================================================
   Working
   ============================================================
   Every button that talks to the server is disabled for the length of its own
   request, which is what stops a second tap sending a second one. Disabled alone
   reads as broken, so it also breathes — the animation says the app is waiting
   on the network rather than on the person. */
.tg-busy{animation:tg-working 1.1s ease-in-out infinite}
@keyframes tg-working{0%,100%{opacity:.55}50%{opacity:.85}}

/* ============================================================
   Touch
   ============================================================
   `manipulation` drops the wait for a possible double-tap — that wait is the
   ~300ms that made every tap in this app feel like a slow connection. Panning and
   pinch-zoom are untouched. */
button,.card,.opt,.find,.day,.wi,.tabbar-item{touch-action:manipulation}
/* A press should show up immediately; the release can take its time. */
.card:active,.opt:active,.find:active{transition-duration:.06s}

/* ============================================================
   Narrow phones
   ============================================================
   375px is the narrowest width in the canon (see app.css) and it is where the
   header runs out of room: three language pills, the unread control and the
   crest all want the same row. The pills give up their padding first — two
   letters stay two letters — and the action rows stop trying to share a line. */
/* One row of buttons per thumb, on every phone and not only the narrowest.
   430px is the canon's widest phone; below it the three answers to a release
   request are 40px targets a thumb aims at one-handed, and three of them
   sharing a 350px line are each about the width of a thumb print. On a desktop
   client the viewport is far wider than the 480px column, this query never
   fires, and the row stays a row. Above the stacking rule rather than inside
   the 375 block, because .ds-btn does not wrap its label: a squeezed button
   loses the end of its word rather than folding it. */
@media (max-width: 430px){
  .tg-actions>button{flex:1 1 100%}
}

/* Four labels instead of three on the narrowest phone in the canon. 375/4 is
   93px a side, and «Сообщения» is the longest of the four: the item gives up
   its horizontal padding first and drops to the smallest sanctioned micro-label
   size, which keeps the whole word rather than ellipsing it. Checked at 375,
   390 and 430; above 430 the original size is what fires. */
@media (max-width: 430px){
  .tabbar{padding-left:var(--space-4);padding-right:var(--space-4)}
  .tabbar-item{padding-left:var(--space-1);padding-right:var(--space-1)}
}

@media (max-width: 375px){
  .tg-inbox{padding:var(--space-6) var(--space-8)}
  .tg-actions{gap:var(--space-6)}
  .tg-rr,.tg-offer{padding:var(--space-10)}
  .tabbar-item{font-size:var(--fs-10);letter-spacing:0}
  .tg-mine-tabs{width:100%;justify-content:center}
}

/* On a desktop client the window is far wider than the 480px the app is drawn
   at, and a bar pinned to the bottom of it with square top corners reads as a
   strip of another page. Rounded, it reads as part of this one. */
@media (min-width: 768px){
  .tabbar{
    border-radius:var(--radius-18) var(--radius-18) 0 0;
    border-left:1px solid var(--border);border-right:1px solid var(--border);
  }
}

@media (prefers-reduced-motion: reduce){
  .ptr,.tabbar,.sheet,.panel{transition-duration:.01ms}
  .ptr.busy .ptr-ring{animation-duration:1.6s}
  .tg-busy{animation:none;opacity:.6}
  .view.on{animation:none}
}

/* «Приоритет ректора» on a room card. Its own chip rather than one of the seven
   status badges: those mean seven things about a booking, and this means none
   of them. */
.tg-cond-chip{
  display:inline-flex;align-items:center;margin:var(--space-6) 0 0;
  padding:0 var(--space-8);border:1px solid var(--gold-line);border-radius:var(--radius-12);
  background:var(--gold-soft);color:var(--gold);
  font:var(--weight-620) var(--fs-12)/1.7 var(--font)
}

/* An administrator's demand to free a room, across the top of the list. Loud on
   purpose, and never used for a colleague's request — see releaseAlertHTML in
   js/app.js for why the two are not the same banner. */
.tg-alert{
  margin:0 0 var(--space-14);padding:var(--space-14);
  border:1px solid var(--danger);border-radius:var(--radius-13);
  background:var(--danger-soft);color:var(--text)
}
.tg-alert b{display:block;margin:0 0 var(--space-8);color:var(--danger);font-size:var(--fs-14)}
.tg-alert p{margin:0 0 var(--space-6);font-size:var(--fs-13);line-height:var(--lh-1-35)}
.tg-alert p span{color:var(--text-dim)}
.tg-alert .tg-alert-ok{margin:var(--space-8) 0 0;width:100%}

/* Same line in the Mini App, under the status and above the note. */
.tg-bk-prep{margin:var(--space-6) 0 0;font-size:var(--fs-12);color:var(--text-dim)}

/* The duty board: her rooms, in three headed groups. Nothing here is a new
   component — a card is the card every other list draws, and the only control
   is the one button at the bottom of each. */
.duty-lead{margin:0 0 var(--space-12)}
.duty-h{
  margin:var(--space-16) 0 var(--space-8);
  font:var(--weight-620) var(--fs-12)/1.4 var(--font);
  letter-spacing:.5px;text-transform:uppercase;color:var(--text-faint)
}
.duty-h:first-child{margin-top:0}
.duty-card{margin:0 0 var(--space-10)}
.duty-top{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-10)}
.duty-when{font-size:var(--fs-12);color:var(--text-dim);white-space:nowrap}
.duty-note{margin:var(--space-8) 0 0;font-size:var(--fs-13);line-height:var(--lh-1-35)}
.duty-who{margin:var(--space-6) 0 var(--space-10);font-size:var(--fs-12);color:var(--text-faint)}

/* The call sheet: four options stacked, and the state line under a booking. */
.tg-call-opts{display:grid;gap:var(--space-8);margin:var(--space-12) 0}
.tg-call-opt{justify-content:flex-start;text-align:left}
.tg-call-opt.on{border-color:var(--accent);color:var(--accent)}
.tg-call-state{margin:var(--space-6) 0 0;font-size:var(--fs-12);color:var(--text-faint)}
.tg-call-state--done{color:var(--free)}

/* The two steps before a room goes, in the Mini App's own sheet shapes. */
.tg-duty-why{display:grid;gap:var(--space-8);margin:var(--space-12) 0}
.tg-duty-why__opt{justify-content:flex-start;text-align:left}
