/*
 * The site's stylesheet, and the *only* one — the static pages here and the server-rendered pages in
 * `src/web/` and `src/admin/` all link to this file. That is deliberate: `/privacy` is produced by Node
 * because it has to touch the database, `/download` is a flat file served by Caddy, and a visitor must not
 * be able to tell which is which.
 *
 * Everything below is transcribed from the app's own design language (ui/theme/Theme.kt and ui/UiCommon.kt),
 * dp mapped 1:1 to px. The rules that are easy to "improve" and must not be:
 *
 *   - The page background is GREY (#F2F2F7) and the cells are WHITE. In dark mode the page is BLACK and
 *     the cells are #1C1C1E. That inversion is asymmetric and it is what makes this look like iOS grouped
 *     content rather than Material.
 *   - No shadows anywhere except popovers. Separation is by surface colour and 1px lines.
 *   - Two greys with fixed jobs: --separator for control BORDERS, --separator-2 for divider LINES. Swapping
 *     them makes inputs shout and dividers look dirty.
 *   - An input is a 4px-radius square, a selector is a 999px pill, both exactly 40px tall with the same
 *     14px/500 type. Same size, different shape — that is how "type here" is told from "choose here".
 *   - Text colour is --text, never --accent. Accent colouring reads as "tappable".
 */

:root {
  --bg: #f2f2f7;
  --surface: #ffffff;
  --surface-2: #f2f2f7;
  --surface-3: #ffffff;
  --text: #000000;
  --text-2: #6e6e73;
  --accent: #007aff;
  --on-accent: #ffffff;
  --accent-soft: #d9e8ff;
  --on-accent-soft: #00305c;
  --separator: #c6c6c8;
  --separator-2: #e3e3e8;
  --danger: #ff3b30;
  --on-danger: #ffffff;
  --danger-soft: #ffdad5;
  --on-danger-soft: #410002;
  --warn: #ff9800;
  --good: #4caf50;
  --motif-opacity: 0.14;
  color-scheme: light dark;
}

/*
 * The system preference is the default signal; the [data-theme] attribute set by the header toggle has to
 * win in BOTH directions, which is why the explicit selectors repeat the values instead of relying on the
 * media query alone.
 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000000;
    --surface: #1c1c1e;
    --surface-2: #2c2c2e;
    --surface-3: #2c2c2e;
    --text: #ffffff;
    --text-2: #98989e;
    --accent: #0a84ff;
    --accent-soft: #003e7a;
    --on-accent-soft: #d9e8ff;
    --separator: #48484a;
    --separator-2: #38383a;
    --danger: #ff453a;
    --danger-soft: #93000a;
    --on-danger-soft: #ffdad5;
  }
}

:root[data-theme='dark'] {
  --bg: #000000;
  --surface: #1c1c1e;
  --surface-2: #2c2c2e;
  --surface-3: #2c2c2e;
  --text: #ffffff;
  --text-2: #98989e;
  --accent: #0a84ff;
  --accent-soft: #003e7a;
  --on-accent-soft: #d9e8ff;
  --separator: #48484a;
  --separator-2: #38383a;
  --danger: #ff453a;
  --danger-soft: #93000a;
  --on-danger-soft: #ffdad5;
  color-scheme: dark;
}

:root[data-theme='light'] {
  --bg: #f2f2f7;
  --surface: #ffffff;
  --surface-2: #f2f2f7;
  --surface-3: #ffffff;
  --text: #000000;
  --text-2: #6e6e73;
  --accent: #007aff;
  --accent-soft: #d9e8ff;
  --on-accent-soft: #00305c;
  --separator: #c6c6c8;
  --separator-2: #e3e3e8;
  --danger: #ff3b30;
  --danger-soft: #ffdad5;
  --on-danger-soft: #410002;
  color-scheme: light;
}

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

/* `hidden` has to win over a class that sets `display`, and by default it does not.
   The attribute's rule lives in the user-agent stylesheet, so any `display` in this file beats it on
   specificity. That is not theoretical: the calculator's warning box is a `.note`, which sets its own
   `display` (`flex` when this was found, `block` since), and `element.hidden = true` left an empty
   warning triangle sitting on the page —
   an "attention" frame with nothing in it, on a page full of numbers, reads as a failed calculation.
   One rule here rather than a `[hidden]` override next to every component that might ever be toggled. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  /* System stack, no web font: closer to the iOS feel the app is after, and no external request. */
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text', 'Segoe UI', Roboto,
    system-ui, 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  background: var(--bg);
  color: var(--text);
  /* Room for the fixed tab bar. */
  padding-bottom: 96px;
  -webkit-font-smoothing: antialiased;
}

/*
 * The app's background motif: the illustration pinned to the bottom edge, full width, behind everything,
 * at exactly the same 0.14 opacity. Brighter than that and it stops being a background.
 */
/* The drawing behind the page, one of the app's own backgrounds.

   It is portrait art made for a phone, and `width: 100%` used to be the whole rule — which is right on a
   phone and wrong everywhere else: at 1600 px of viewport a 1:1 drawing became 1600 px tall, and since it
   is pinned to the bottom of the window, a desktop reader saw a strip of the bottom of it and nothing
   else. So the width is capped at a phone's worth of pixels and the picture is centred; `contain` plus a
   viewport-relative ceiling is the belt to that braces, so a tall drawing on a short window shrinks to fit
   instead of being cropped again. Under 420 px wide nothing changes — `min()` yields 100% and the rule is
   the old one. */
.motif {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  width: min(100%, 420px);
  max-height: 68vh;
  height: auto;
  object-fit: contain;
  object-position: bottom center;
  opacity: var(--motif-opacity);
  z-index: 0;
  pointer-events: none;
  user-select: none;
}

.wrap {
  position: relative;
  z-index: 1;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 14px 14px;
}

.wrap.wide {
  max-width: 1120px;
}

/* The user list, and only it: the whole window, with no cap at all.

   Ten columns of a person's account do not fit in 1120 px without every cell wrapping, and the panel is
   read on a desktop by one person, so it gets the monitor it is actually on. The 2400 px cap that used to
   be here was a backstop against an unfollowable row — and on a 3440 px monitor it left a third of the
   screen empty while the table it was protecting scrolled sideways *inside* the card. Between an
   over-wide row and a table you cannot see at once, the table wins: this screen exists to be read across.

   The padding is narrower than `.wrap`'s too. Every pixel here is a pixel the address column does not
   have to wrap into. */
.wrap.full {
  max-width: none;
  padding: 0 8px 14px;
}

/* ------------------------------------------------------------------ top bar (BrandedTitle) */

.topbar {
  position: relative;
  z-index: 1;
  min-height: 64px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 8px 0;
  background: transparent;
  max-width: 1120px;
  margin: 0 auto;
}

/* The header of a full-width page has to be full width too.

   This is what "плывёт разметка" on the user list actually was, and it was not the table: `.wrap.full` has
   no cap, so on a 2560 px monitor the content spanned the whole window while the bar above it stayed
   1120 px centred. The screen title sat over the middle of the fifth column, the theme button floated in
   the middle of nowhere, and every horizontal alignment on the page disagreed with every other. The journal
   looked fine at the same moment for exactly one reason: it renders `wide`, so its bar and its content are
   the same 1120 px. */
.topbar.full {
  max-width: none;
}

.topbar .head {
  flex: 1;
  min-width: 0;
  text-align: center;
}

/* 11px accent line above the screen name, exactly as in the app's header. */
.brand {
  display: block;
  font-size: 11px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--accent);
  text-decoration: none;
}

.screen-title {
  display: block;
  font-size: 16px;
  line-height: 24px;
  font-weight: 500;
  letter-spacing: 0.15px;
  color: var(--text);
  margin: 0;
}

/* Icon button: 48px touch target around a ~24px glyph. */
.icon-btn {
  width: 48px;
  height: 48px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
}

.icon-btn:hover {
  background: color-mix(in srgb, var(--text) 5%, transparent);
}

/* ------------------------------------------------------------------ space switch
 *
 * Two products live on this domain — «Панацея» (лекарства) at `/` and «Панацея — подписки» at
 * `/subscriptions/` — and this is the only control that says so. It stands on the two landing pages
 * and nowhere else: an inner page already belongs to a space, and a switch there would offer to leave
 * a page the reader just chose.
 *
 * A segmented control rather than a link, because the two are PEERS. A link in the footer or a card at
 * the bottom would make one of them the site and the other an annex, which is not the shape of this
 * family. The app that ships the subscriptions product draws its own switches the same way (its
 * `SegmentedControl` is written from scratch for exactly this reason), so the idiom is the products'
 * own.
 *
 * No colour change comes with the space: `--accent` here is #007AFF, which is also the accent of the
 * subscriptions app (`ui/theme/Colors.kt`). Two products of one family, one accent — and nothing to
 * keep in step between a stylesheet and a Kotlin palette.
 *
 * The track is `--surface`, not `--surface-2`: in light mode `--surface-2` IS the page background, so a
 * track painted with it would be invisible on the very pages this control stands on.
 */
.space-switch {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 2px;
  width: fit-content;
  max-width: calc(100% - 28px);
  margin: 0 auto;
  padding: 3px;
  border-radius: 999px;
  background: var(--surface);
}

.space-switch a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 18px;
  border-radius: 999px;
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.1px;
  color: var(--text-2);
  text-decoration: none;
  white-space: nowrap;
}

/* The current space is stated by `aria-current`, the same attribute the tab bar uses, so the fill and
   the announcement to a screen reader cannot disagree. */
.space-switch a[aria-current='page'] {
  background: var(--accent);
  color: var(--on-accent);
}

/* ------------------------------------------------------------------ hero */

.hero {
  position: relative;
  z-index: 1;
  max-width: 720px;
  margin: 0 auto;
  padding: 8px 14px 4px;
  text-align: center;
}

.hero .mark {
  width: 72px;
  height: 72px;
  margin: 8px auto 12px;
  display: block;
}

.hero h1 {
  font-size: 34px;
  line-height: 41px;
  font-weight: 700;
  letter-spacing: -0.4px;
  margin: 0 0 8px;
}

.hero p {
  font-size: 16px;
  line-height: 24px;
  color: var(--text-2);
  margin: 0 auto;
  max-width: 34rem;
}

.hero .actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 18px;
}

/* ------------------------------------------------------------------ grouped list */

/* Uppercase group header. It sits on the grey page, not inside the white card. */
.sec-title {
  text-transform: uppercase;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-2);
  padding-left: 8px;
  margin: 22px 0 6px;
}

/*
 * Larger section headings, and page-scoped rather than global.
 *
 * `.sec-title` is the iOS-style section label used on every page of this site **and** in the admin panel, so
 * raising it in place would resize six screens nobody asked about. `/web` is a page people are sent to when
 * they are trying to install something on a device they own, and there the headings are the thing being
 * scanned for — «Android», «Компьютер» — rather than a quiet label above a card.
 *
 * Exactly ×1.7 of the 12/16 default, as asked; the line height has to scale with it or two-line headings
 * («Чем браузерная версия отличается» wraps below ~400 px) would collide.
 */
.wrap.titles-lg .sec-title {
  font-size: 20.4px;
  line-height: 27.2px;
}

/*
 * The device icon in front of a section heading.
 *
 * Own letter-spacing, because the heading's 0.06em tracking is there to open up small uppercase text and on
 * an emoji it just pushes the glyph away from its own word. `font-weight: 400` for the same reason the
 * tabbar's icons are unweighted: a bold emoji is a heavier emoji on some platforms and identical on others.
 */
.sec-title .sec-ico {
  letter-spacing: 0;
  margin-right: 8px;
  font-weight: 400;
}

/*
 * The two brand marks are inline SVG, not emoji, and there was no choice about it.
 *
 * The Apple logo is U+F8FF — a **Private Use Area** codepoint. It renders on Apple's own platforms and
 * nowhere else, so on the Android and Windows browsers that read this page most it would be an empty box on
 * the one heading whose job is to say «this section is for your iPhone». The Android robot has no codepoint
 * at all (🤖 is a generic robot face, not the mark).
 *
 * `1em` so they track the heading's own size — this page raises it ×1.7 and the icons must follow. Sized in
 * `em` rather than px for that reason alone; a fixed 20 px would be right on exactly one page.
 *
 * `--text`, not the heading's muted `--text-2`: a logo at 60 % contrast reads as a disabled control. The
 * baseline nudge is because both paths are drawn to the full 24-unit box while the text sits above the
 * descender line.
 */
svg.sec-ico {
  width: 1em;
  height: 1em;
  vertical-align: -0.12em;
  color: var(--text);
}

/*
 * The robot keeps its brand green in both themes — it is legible on white and on black, which is what the
 * colour was chosen for. The apple stays monochrome, which is Apple's own usage and the only way it works
 * against two opposite backgrounds.
 *
 * **Its own size, because the two marks are not the same shape.** Measured from the path data: the apple
 * fills its whole 24-unit box top to bottom, the robot head fills 13.5 of 24 — so at an equal `1em` the robot
 * would render at 56 % of the apple's height and read as a mistake. Its viewBox is cropped to its own content
 * and the size given as height × the mark's real 1.775 aspect, so the two icons carry comparable weight
 * without the robot being stretched.
 */
.sec-ico-android {
  color: #3ddc84;
  height: 0.8em;
  width: 1.42em;
  vertical-align: -0.05em;
}

.card {
  background: var(--surface);
  border-radius: 16px;
  box-shadow: none;
  border: 0;
  padding: 14px;
  margin: 0 0 8px;
}

.card > :first-child {
  margin-top: 0;
}

.card > :last-child {
  margin-bottom: 0;
}

/* A reference page's own name, and the only `<h1>` inside a card on this site.

   The landing page's `<h1>` lives in `.hero` and is 30 px; a `/ref/*` page is a document about one
   substance, so its name is a heading rather than a card title — but it still sits in the grouped-card
   layout, so it is sized between the two rather than shouting. */
.card h1 {
  font-size: 22px;
  line-height: 28px;
  font-weight: 600;
  letter-spacing: -0.2px;
  margin: 0 0 4px;
  color: var(--text);
}

.card h2 {
  font-size: 16px;
  line-height: 24px;
  font-weight: 500;
  letter-spacing: 0.15px;
  margin: 0 0 2px;
  color: var(--text);
}

.card h3 {
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.1px;
  color: var(--accent);
  margin: 14px 0 4px;
}

.card .sub {
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.4px;
  color: var(--text-2);
  margin: 0 0 10px;
}

.card p {
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.25px;
  margin: 0 0 10px;
}

/* A real iOS group: one rounded container, hairlines between the rows, inset from the left. */
.group {
  background: var(--surface);
  border-radius: 16px;
  overflow: hidden;
  margin: 0 0 8px;
}

.cell {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 12px 14px;
  color: var(--text);
  text-decoration: none;
}

.cell + .cell {
  border-top: 1px solid var(--separator-2);
}

a.cell:hover {
  background: color-mix(in srgb, var(--text) 5%, var(--surface));
}

.cell .body {
  flex: 1;
  min-width: 0;
}

.cell .t {
  font-size: 16px;
  line-height: 24px;
  letter-spacing: 0.5px;
}

.cell .h {
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.4px;
  color: var(--text-2);
}

.cell .chev {
  flex: none;
  color: var(--text-2);
  font-size: 18px;
}

.cell .lead {
  flex: none;
  width: 24px;
  text-align: center;
  color: var(--accent);
  font-size: 18px;
}

/* Feature bullets: the severity-dot row from the app, reused as a list marker. */
ul.dots {
  list-style: none;
  margin: 0;
  padding: 0;
}

ul.dots li {
  position: relative;
  padding-left: 20px;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.25px;
  margin: 0 0 8px;
}

ul.dots li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

ul.dots li:last-child {
  margin-bottom: 0;
}

/*
 * The tinted note card the app uses for disclaimers. In light mode --surface-2 equals the page
 * background, so this block only reads correctly INSIDE a white card — on the page itself it disappears.
 */
/* Рамка-примечание: значок слева, текст с висячим отступом под ним.
 *
 * БЫЛА `display: flex`, и это молча ломало каждое примечание, внутри которого есть хоть один тег.
 * Flex заворачивает в анонимный элемент **каждый непрерывный кусок текста**, поэтому
 * «Приложение есть <strong><a>в RuStore</a></strong> — оттуда оно ставится…» раскладывалось в три
 * колонки: «Приложение есть», «в RuStore», «— оттуда оно ставится…». На живом сайте это стояло на
 * /download в двух местах (ссылка на магазин и фраза про `.bin`), и увидеть это можно было только
 * глазами: разметка верна, стиль верен, ошибка возникает при их встрече.
 *
 * Сейчас это блок, а висячий отступ даёт пара `padding-left` + отрицательный `text-indent`. 25px —
 * это 15px значка и 10px промежутка, то есть ровно прежние `gap` и размер, поэтому примечания без
 * тегов выглядят как раньше. Значку `text-indent` возвращается в ноль, иначе он уезжает влево
 * вместе с первой строкой. */
.note {
  background: var(--surface-2);
  border-radius: 14px;
  padding: 12px 12px 12px 37px;
  text-indent: -25px;
  font-size: 12px;
  line-height: 18px;
  letter-spacing: 0.4px;
  color: var(--text-2);
  margin: 10px 0;
}

.note::before {
  content: 'ⓘ';
  display: inline-block;
  width: 15px;
  margin-right: 10px;
  text-indent: 0;
  font-size: 15px;
  line-height: 18px;
}

.note.warn::before {
  content: '⚠';
  color: var(--warn);
}

.note.danger {
  color: var(--text);
}

.note.danger::before {
  content: '⚠';
  color: var(--danger);
}

/* Result banners on the server-rendered forms. */
.ok,
.err {
  border-radius: 14px;
  padding: 12px 14px;
  font-size: 14px;
  line-height: 20px;
  margin: 0 0 10px;
}

.ok {
  background: color-mix(in srgb, var(--good) 14%, var(--surface));
  border-left: 3px solid var(--good);
}

.err {
  background: var(--danger-soft);
  color: var(--on-danger-soft);
  border-left: 3px solid var(--danger);
}

/* ------------------------------------------------------------------ controls */

.btn {
  /* 4px of margin reproduces the app's 48px touch target around a 40px control — without it a button
     and an input standing in the same row miss each other's baseline by exactly 4px. */
  margin-block: 4px;
  height: 40px;
  border-radius: 999px;
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 24px;
  cursor: pointer;
  text-decoration: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--accent);
  font-family: inherit;
  white-space: nowrap;
}

.btn-fill {
  background: var(--accent);
  color: var(--on-accent);
  border-color: transparent;
}

.btn-outline {
  border-color: var(--separator);
  color: var(--accent);
}

.btn-text {
  padding: 8px 12px;
}

.btn-danger {
  color: var(--danger);
}

.btn-fill.btn-danger {
  background: var(--danger);
  color: var(--on-danger);
}

.btn:disabled,
.btn[aria-disabled='true'] {
  color: color-mix(in srgb, var(--text) 38%, transparent);
  background: color-mix(in srgb, var(--text) 12%, transparent);
  border-color: transparent;
  cursor: default;
  pointer-events: none;
}

.btn-lg {
  height: 48px;
  padding: 8px 28px;
  font-size: 16px;
}

label.lbl {
  display: block;
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.1px;
  color: var(--text);
  margin: 10px 0 4px;
}

/* The square 4px input. Not a pill — the shape is what distinguishes it from a selector. */
input[type='text'],
input[type='email'],
input[type='password'],
input[type='date'],
input[type='search'],
textarea {
  margin-block: 4px;
  min-height: 40px;
  width: 100%;
  border: 1px solid var(--separator);
  border-radius: 4px;
  background: transparent;
  padding: 8px 16px;
  font-family: inherit;
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.1px;
  /* --text, never --accent: a value the user typed must not look tappable. */
  color: var(--text);
  caret-color: var(--accent);
}

input::placeholder,
textarea::placeholder {
  color: var(--text-2);
  font-weight: 400;
}

input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  /* Colour only. Thickening the border to 2px makes the box jump by a pixel on focus. */
  border-color: var(--accent);
  outline: none;
}

/* The selector: same 40px height, pill shape, accent text. */
select {
  margin-block: 4px;
  height: 40px;
  width: 100%;
  border: 1px solid var(--separator);
  border-radius: 999px;
  background: transparent;
  color: var(--accent);
  padding: 8px 16px 8px 20px;
  font-family: inherit;
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: 0.1px;
  cursor: pointer;
}

.field-row {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.field-row > * {
  min-width: 0;
}

hr {
  height: 1px;
  border: 0;
  background: var(--separator-2);
  margin: 14px 0;
}

/* ------------------------------------------------------------------ badges, tables */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.5px;
  background: var(--surface-2);
  color: var(--text-2);
  white-space: nowrap;
}

.badge-accent {
  background: var(--accent-soft);
  color: var(--on-accent-soft);
}

.badge-danger {
  background: var(--danger-soft);
  color: var(--on-danger-soft);
}

.badge-good {
  background: color-mix(in srgb, var(--good) 18%, var(--surface));
  color: var(--text);
}

.scroll-x {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* A table that fits instead of scrolling: the user list, the journal, the model matrix.

   Everything else in the panel keeps `.scroll-x` — a plan row is six numbers and a form, and it is
   allowed to push a narrow window sideways. These three are not, because their job is comparison across a
   row, and a horizontal scrollbar means the address and the buttons that act on it are never on screen
   together.

   **`table-layout: fixed` is the whole mechanism, and `width: 100%` alone was not enough.** With automatic
   layout the browser sizes every column to its widest cell, so a `<select>` as wide as its longest option
   and an untruncatable date input decided the geometry: the sum came out wider than the window, the columns
   that had something to say got squeezed to nothing, and the cells that could wrap wrapped into three-line
   rows. Fixed layout inverts that — the `<colgroup>` in each table says what share of the width every
   column gets, and the contents adapt to it. That is what makes the whole thing scale with the screen
   instead of fighting it.

   The percentages live in the HTML next to the headers they belong to (`src/admin/pages.ts`), because a
   column's share and its heading are one decision and splitting them across two files is how they drift. */
.table-fit {
  width: 100%;
  max-width: 100%;
  table-layout: fixed;
}

/* Fixed layout hands each cell a width; this is what makes the contents accept it.

   `min-width: 0` is the load-bearing line, twice over: a flex item defaults to `min-width: auto`, i.e. it
   refuses to shrink below its content, and that default is exactly what pushed a row of controls out of its
   cell and onto a second line. With it, `nowrap` below is safe — the controls scale down together instead
   of wrapping or overflowing. */
.table-fit td .field-row,
.table-fit td form.field-row {
  flex-wrap: nowrap;
  min-width: 0;
}

.table-fit td .field-row > *,
.table-fit td form {
  min-width: 0;
}

/* A control in a table cell is sized by the cell, not by its own content: a `<select>` is otherwise as wide
   as its longest option (a model id here is 28 characters) and `input[type=date]` carries an intrinsic width
   that no `width` alone overrides. `max-width` on the individual inputs still applies where it is set. */
.table-fit td select,
.table-fit td input {
  width: 100%;
}

/* Buttons keep their own size — a button squeezed to 20 px is a button nobody can read or hit. They are the
   part of the row that must not scale, which is why the controls beside them must. */
.table-fit td .btn {
  flex: 0 0 auto;
}

/* Fixed layout hands out widths; it does **not** clip what does not fit. A cell whose content cannot be
   broken silently draws over its neighbour — the same "плывёт" symptom, one column to the right. So every
   cell here is allowed to break anywhere, and the exceptions are opt-in per cell rather than the default.
   `.badge` keeps its own `nowrap`, so a badge breaks between badges and never inside one. */
.table-fit td {
  overflow-wrap: anywhere;
}

/* The last resort, and it is deliberately last: the row of controls stays on one line as long as the column
   can hold it (that is the point of `min-width: 0` above), and only a genuinely too-narrow window makes it
   take a second line. A second line is ugly; drawing on top of the next column is broken. */
.table-fit td > .field-row {
  flex-wrap: wrap;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  line-height: 20px;
}

th {
  text-transform: uppercase;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-2);
  text-align: left;
  padding: 6px 10px;
  white-space: nowrap;
  border-bottom: 1px solid var(--separator-2);
}

td {
  padding: 10px;
  border-bottom: 1px solid var(--separator-2);
  vertical-align: middle;
}

/* The dense variant: the same table with the air taken out.

   Every row of the user list carries a form — a plan, a date and three buttons — and at the default
   padding a page of fifty accounts is a scroll of several screens with two or three rows visible at a
   time. Comparing accounts is the entire job of that screen, and you cannot compare what you cannot see
   at once. Nothing here changes what a cell contains, only how much room it takes. */
.table-dense {
  font-size: 13px;
  line-height: 18px;
}

.table-dense th {
  padding: 5px 8px;
}

/* `middle`, not `top`: with every cell one line tall there is nothing to align to the top of, and a select
   sitting a few pixels above the address it belongs to is what makes a dense table look broken. */
.table-dense td {
  padding: 3px 8px;
  vertical-align: middle;
}

.table-dense .badge {
  height: 20px;
  padding: 0 8px;
  font-size: 11px;
  letter-spacing: 0.3px;
}

/* `margin-block: 0` and `min-height: 0` are not tidying — they are the rule.

   The shared controls are built for a form on a phone: 40 px tall with 4 px of margin above and below, so
   three of them in a table cell is a 48 px line whatever `height` says. `min-height` in particular is what
   `input[type=date]` carries, and it wins over a plain `height` every time. Setting all three is the
   difference between a 32 px row and a 55 px one, across fifty of them. */
.table-dense .btn,
.table-dense input,
.table-dense select {
  margin-block: 0;
  min-height: 0;
  height: 26px;
  font-size: 12px;
}

.table-dense .btn {
  padding: 0 9px;
  white-space: nowrap;
}

.table-dense input,
.table-dense select {
  padding: 0 6px;
}

/* Row height is set by the tallest thing in the row, and that is this cell's controls. Nothing here is
   allowed to start a second line. */
.table-dense form {
  margin: 0;
}

/* The address column, and the one place in this table allowed to take a second line.

   It used to be `nowrap`, which kept every row exactly one line tall and pushed the overflow sideways —
   the card scrolled, and reading the tenth column meant losing the first. The user list is now the full
   width of the window **and does not scroll** (see `.table-fit`), so the width has to come from
   somewhere: it comes from here, because an address is the only cell whose second line costs nothing to
   understand. It is a login and what an operator searches by, so it is wrapped rather than truncated —
   an elided address is one you cannot compare against the one in the support mail.

   `anywhere` rather than `break-word`: a 60-character address with no separator is a single word, and
   `break-word` leaves it overflowing its column. */
.table-dense td:first-child {
  overflow-wrap: anywhere;
}

/* Tight, because this gap is repeated four times across every row of fifty. `row-gap` matters only in the
   filter bar and the plans table, where a `.field-row` may still wrap; inside `.table-fit` nothing wraps. */
.table-dense .field-row {
  gap: 4px;
  row-gap: 3px;
}

/* The quieter half of a cell that says two things — "до 2027-01-01" beside a plan, "· был 2026-08-26"
   beside a registration date. Inline rather than a block, because a second line in any cell is a taller
   row for every account on the page. */
.sub-num {
  color: var(--text-2);
  font-size: 12px;
  line-height: 15px;
}

/* The filter bar. A grid rather than the usual flex row: seven controls of different natural widths wrap
   into a ragged two-line shape under `field-row`, and a filter you have to hunt for is one nobody uses. */
/* The filter bar. A grid rather than the usual flex row: seven controls of different natural widths wrap
   into a ragged two-line shape under `field-row`, and a filter you have to hunt for is one nobody uses.

   **The upper bound on a column is as load-bearing as the lower one.** With `1fr` the seven controls
   stretched to fill whatever width the page had — on a full-width page that is a 300 px search box next to a
   300 px dropdown holding the word «любой», i.e. a form that looks broken because it is proportioned by the
   monitor rather than by its content. Capped at 220 px they keep their shape and the row simply ends where
   the controls end. */
.filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 220px));
  justify-content: start;
  gap: 8px;
  align-items: end;
}

.filters label {
  display: block;
  font-size: 12px;
  line-height: 16px;
  color: var(--text-2);
  margin-bottom: 2px;
}

.filters select,
.filters input {
  width: 100%;
  box-sizing: border-box;
}

tr:last-child td {
  border-bottom: 0;
}

td.num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-2);
}

/* ------------------------------------------------------------------ поля прямо в таблице

   Инпуты в ячейках таблиц админки (тарифы, продукты). Форма при этом объявлена вне строки, а поля привязаны
   к ней атрибутом `form` — HTML не даёт <form> обернуть несколько <td>, а подписи колонок обязаны стоять над
   своими полями. До этого форма занимала одну ячейку с colspan, и шесть инпутов лежали в ней подряд: числа
   не совпадали с заголовками, и «токены» вводили в колонку «дней доступа».

   Ширина — от содержимого, а не фиксированная в пикселях: 1000 и 1 000 000 000 в одной колонке
   («токенов/мес») требуют разного места, а `width: 100%` растянул бы узкие колонки по всей странице. */
.cell-num,
.cell-text {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.cell-num {
  font-variant-numeric: tabular-nums;
  text-align: right;
  /* Числовое поле не должно быть шире своего заголовка: 4–5 цифр это ~7ch плюс стрелки браузера. */
  max-width: 11ch;
}

.cell-text {
  min-width: 12ch;
}

/* Monospace only where a hash or a version has to be compared by eye. */
code,
.mono {
  font-family: ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
  font-size: 13px;
  word-break: break-all;
}

/* ------------------------------------------------------------------ screenshots

   Thumbnails, and the size is the whole point.

   A phone screenshot is 461×1024 — 2.22 tall for every unit wide — so a column width is a *height*
   multiplied by two and a bit. At the old `minmax(150px, 1fr)` four of them fitted the 720 px page at
   163 px wide, which is 362 px tall each and a wall of ten of them roughly 1150 px high: the section
   called «how it looks» was taller than everything the page actually says. Fixing the figure width
   instead of asking the grid for one is what makes the height predictable — 96 px lands at ~213 px tall,
   and `max-height` is the belt to that braces for an image with another aspect ratio.

   None of them is legible at this size and none was at 163 px either — the small print on a bottle does
   not survive a thumbnail. That is why each one is a link to its own file now: the gallery is for
   recognising a screen, and a click is for reading it. No JavaScript, no lightbox, no second copy of the
   image at a second size. */

.shots {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 10px;
  margin: 4px 0 0;
}

.shots figure {
  margin: 0;
  width: 96px;
}

/* The link is a block so it has the picture's own box and nothing else: an inline `<a>` around an image
   adds a baseline gap under it, which reads as a crooked caption. */
.shots a {
  display: block;
}

.shots img {
  width: 100%;
  height: auto;
  max-height: 220px;
  object-fit: contain;
  display: block;
  border-radius: 10px;
  border: 1px solid var(--separator-2);
  background: var(--surface);
}

.shots figcaption {
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.4px;
  color: var(--text-2);
  margin-top: 6px;
  text-align: center;
}

/* ------------------------------------------------------------------ feature tiles (2.20.0)

   The "what's inside" grid. A tile is a card, so it inherits the surface and the radius; what it adds is
   one big glyph and two lines. Deliberately not a bulleted list: the six things this page is selling are
   each one sentence, and a list of one-sentence items is a wall. */

.feats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 8px;
  margin: 0 0 8px;
}

.feat {
  background: var(--surface);
  border-radius: 16px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.feat .ico {
  font-size: 26px;
  line-height: 32px;
}

.feat .t {
  font-size: 16px;
  line-height: 22px;
  font-weight: 500;
  letter-spacing: 0.15px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.feat .d {
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.25px;
  color: var(--text-2);
  margin: 0;
}

/* "ИИ" / "офлайн" markers on a tile. Same pill as .badge but sized to sit inside a 22px line. */
.tag {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: 999px;
  font-size: 11px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.5px;
  background: var(--surface-2);
  color: var(--text-2);
  white-space: nowrap;
}

.tag-ai {
  background: var(--accent-soft);
  color: var(--on-accent-soft);
}

.tag-off {
  background: color-mix(in srgb, var(--good) 18%, var(--surface));
  color: var(--text);
}

/* ------------------------------------------------------------------ bottom tab bar */

/*
 * The app's tab bar, and the one thing about it that must not be "fixed": there is no pill behind the
 * active item. Activity is carried by colour alone — that is what makes it an iOS tab bar.
 */
.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  height: 80px;
  display: flex;
  padding-bottom: env(safe-area-inset-bottom, 0);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-top: 1px solid var(--separator-2);
}

.tabbar a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-decoration: none;
  color: var(--text-2);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: 0.5px;
}

.tabbar a .ico {
  font-size: 20px;
  line-height: 24px;
}

.tabbar a[aria-current='page'] {
  color: var(--accent);
}

footer.legal {
  position: relative;
  z-index: 1;
  max-width: 720px;
  margin: 24px auto 0;
  padding: 14px;
  font-size: 12px;
  line-height: 18px;
  color: var(--text-2);
  text-align: center;
}

footer.legal a {
  color: var(--accent);
  text-decoration: none;
}

a {
  color: var(--accent);
}

/* ------------------------------------------------------------------ prompt editor (admin only)

   Two rules the rest of the sheet had no need for: a multi-line editor, and a block that shows a prompt
   exactly as the model receives it.

   `word-break: normal` on both overrides the `code, .mono` rule above, which breaks mid-word. That is right
   for a hash and wrong for prose — a 4 KB Russian prompt broken at arbitrary characters is unreadable, and
   reading it is the entire reason this screen exists. */

.prompt-editor,
.prompt-view {
  font-family: ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
  font-size: 13px;
  line-height: 19px;
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--separator);
  border-radius: 10px;
  padding: 10px 12px;
  background: var(--surface-2);
  color: var(--text);
  /* Newlines are load-bearing: the model is sent the line breaks the operator typed. */
  white-space: pre-wrap;
  word-break: normal;
  overflow-wrap: anywhere;
}

.prompt-editor {
  resize: vertical;
  min-height: 160px;
}

.prompt-editor:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Read-only: taller content is common (an assembled prompt runs to 8 KB), so it scrolls in place rather
   than pushing the rest of the page down. */
.prompt-view {
  max-height: 60vh;
  overflow: auto;
  margin-top: 8px;
}

summary {
  cursor: pointer;
}

/* ------------------------------------------------------------------ landing page (08.09.2026)

   Five components, all of them on `/` only, added when the first screen was rebuilt around one question:
   what does a stranger see before they scroll.

   What was there before and why it did not work: a centred `<h1>` reading «Панацея» (a name, not a
   reason), the whole proposition as one six-line paragraph, three equal-weight buttons — of which the
   leading one went to a store page describing a different product — and the screenshots 400 px further
   down at 96 px wide. Nothing above the fold showed the app.

   The one rule these keep from the rest of the file: **the page stays a single 720 px column of grouped
   cards.** No full-bleed band, no shadow, separation by surface colour and hairlines. A marketing band
   across this site would be the only element on it that is not a card. */

/* The hero, left-aligned and with a real screenshot beside it.

   Left, not centred: a two-line headline centred over a 700 px measure has a ragged middle, and the eye
   has to find the start of every line. Centred type is right for the short brand line the other pages
   have — and those keep it, because `.landing` is a modifier and not a redefinition. */
.hero.landing {
  text-align: left;
  padding-bottom: 10px;
}

.hero-split {
  display: grid;
  /* 208 px of picture is 462 px tall at the phone's 2.22 ratio — the tallest thing that still leaves the
     headline and the button above the fold on a laptop. `minmax(0, 1fr)` rather than `1fr` because a long
     unbroken word in the headline would otherwise push the grid wider than its column. */
  grid-template-columns: minmax(0, 1fr) 208px;
  gap: 24px;
  align-items: center;
}

.hero.landing .mark {
  margin: 0 0 14px;
}

.hero.landing h1 {
  font-size: 30px;
  line-height: 38px;
  margin: 0 0 10px;
}

.hero.landing p {
  margin: 0 0 10px;
  max-width: none;
}

.hero.landing .actions {
  justify-content: flex-start;
  margin-top: 12px;
}

.hero-shot {
  margin: 0;
}

.hero-shot img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 12px;
  border: 1px solid var(--separator-2);
  background: var(--surface);
}

/* Two entry points, because two different people arrive here.

   Reuses `.feats` and only widens the minimum: at 220 px these two cards sat side by side with their
   titles wrapping to three lines, and the whole point of them is that each is read as one choice. */
.feats.two {
  grid-template-columns: repeat(auto-fit, minmax(304px, 1fr));
}

/* "How it works" — three steps, each with the screen it happens on.

   `margin-top: auto` on the picture is what keeps the three images aligned along their tops when the
   three captions run to different numbers of lines. Without it the row reads as three unrelated cards. */
.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(196px, 1fr));
  gap: 8px;
  margin: 0 0 8px;
}

.step {
  background: var(--surface);
  border-radius: 16px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.step .n {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--on-accent-soft);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 600;
  flex: none;
}

.step .t {
  font-size: 16px;
  line-height: 22px;
  font-weight: 500;
  letter-spacing: 0.15px;
}

.step .d {
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.25px;
  color: var(--text-2);
  margin: 0;
}

/* Cropped to the top of the screen, not scaled down whole.
   A 461×1024 screenshot at a 200 px column is 444 px tall, and three of them make this row 700 px high —
   the section explaining three steps would be taller than the three steps' worth of product above it. The
   crop keeps the part that identifies the screen (its title bar and first rows) at a size where the text
   is still legible, which scaling to fit would not. `object-position: top` is the whole trick: the default
   centre crop cuts off exactly the header that says which screen this is. */
.step img {
  width: 100%;
  height: 230px;
  object-fit: cover;
  object-position: top;
  display: block;
  margin-top: auto;
  border-radius: 10px;
  border: 1px solid var(--separator-2);
  background: var(--surface);
}

/* One screen shown big, with what it found listed beside it.

   The picture is on the LEFT here and on the right in the hero, on purpose: this block is read as
   "evidence, then what it means", and the evidence goes first. */
.showcase {
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.showcase img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 12px;
  border: 1px solid var(--separator-2);
  background: var(--surface);
}

/* Questions and answers, as one grouped card of disclosures.

   `<details>` rather than script: the answers have to be in the HTML for a search engine to read them
   (that is the whole point of marking them up as a FAQ), and a page whose content depends on JavaScript
   would be the first thing on this site to do so. */
.faq {
  background: var(--surface);
  border-radius: 16px;
  overflow: hidden;
  margin: 0 0 8px;
}

.faq details {
  padding: 0 14px;
}

.faq details + details {
  border-top: 1px solid var(--separator-2);
}

.faq summary {
  /* Both lines are needed: `list-style` covers Firefox and the pseudo-element covers WebKit, and the
     default marker is a triangle that fights with the +/− on the other side of the row. */
  list-style: none;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 14px 0;
  font-size: 16px;
  line-height: 22px;
  letter-spacing: 0.15px;
  color: var(--text);
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary::after {
  content: '+';
  margin-left: auto;
  flex: none;
  color: var(--text-2);
  font-size: 20px;
  line-height: 22px;
}

.faq details[open] summary::after {
  content: '−';
}

.faq details > p {
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.25px;
  color: var(--text-2);
  margin: 0 0 14px;
}

/* ------------------------------------------------------------------ narrow screens */

@media (max-width: 520px) {
  .hero h1 {
    font-size: 28px;
    line-height: 34px;
  }

  /* On a phone the row is three thumbnails wide at 96 px and the flex line wraps by itself — there is
     no column count left to override. What is worth saying is that they may shrink to fit: 3×96 plus
     two gaps is 308 px, which a 320 px screen has and a 300 px one does not. */
  .shots figure {
    width: min(96px, 30%);
  }

  /* One tile per row rather than two squeezed ones: a 220px minimum would otherwise wrap the two-word
     titles onto three lines. */
  .feats {
    grid-template-columns: 1fr;
  }

  /* The hero and the showcase stack, and the picture goes UNDER the words in both.

     The screenshot is not hidden on a phone — it is the product, and this is the screen most of these
     visitors are on. It is capped and centred instead: at full column width a 461×1024 image is 700 px
     tall and pushes the button below two screenfolds. */
  .hero-split,
  .showcase {
    grid-template-columns: 1fr;
  }

  .hero-shot,
  .showcase img {
    max-width: 200px;
    margin-inline: auto;
  }

  .hero.landing h1 {
    font-size: 26px;
    line-height: 33px;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

/*
 * Numbered clauses of a contract — the public offer, and nothing else so far.
 *
 * The number is the point: the whole document refers to itself by clause («п. 5.2»), so the marker has to
 * be a real ordered-list marker that renumbers itself when a clause is inserted. Hand-typed numbers in the
 * markup are how one published copy comes to disagree with another about which clause is which.
 *
 * Kept visually quieter and tighter than `ul.dots`: this is long-form text somebody reads through, not a
 * feature list somebody scans, and the nested `ul.dots` inside a clause (the acceptance list, the
 * consultant's duties) has to sit under it without the two markers competing.
 */
ol.legal-list {
  margin: 0;
  padding-left: 22px;
}

ol.legal-list > li {
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.25px;
  margin: 0 0 10px;
}

ol.legal-list > li:last-child {
  margin-bottom: 0;
}

/* A bullet list nested in a clause: pulled in a little and set off from the clause's own text. */
ol.legal-list ul.dots {
  margin: 6px 0 0;
}
