/* ══════════════════════════════════════════════════════════════════════════════════════════
   GENERATED — do not edit. Source: public/store2.css · Regenerate: bun tools/sync-promo-store-css.mjs
   Kept honest by tests/promo_store_css_sync.test.mjs, which fails the suite if this drifts.

   The app's «Тариф и оплата» surface, served on promo.irikos.net. One Store v3 §1: one surface, one
   set of components, both hosts. promo cannot LINK the app's stylesheet — different origin under
   `default-src 'self'` — so it carries a copy, and this tool is what keeps it a copy.

   Below: 27 tokens the store CSS reads, scoped to `.s2` so promo's own palette (same
   names, different values) is untouched outside the store. Then public/store2.css verbatim.
   ════════════════════════════════════════════════════════════════════════════════════════════ */
.s2 {
  --accent: #13a292;
  --accent-aa: #0d766a;
  --accent-soft: color-mix(in oklab, var(--accent) 12%, var(--bg));
  --accent-tint: color-mix(in oklab, var(--accent) 8%, var(--card));
  --bg: #f3f6f5;
  --bg-2: #eef2f2;
  --card: #ffffff;
  --content-max: 100%;
  --display: "Onest", "Golos Text", system-ui, sans-serif;
  --font-scale: 1;
  --gutter: 0px;
  --ink: #10201d;
  --ink-2: #4a5a57;
  --ink-3: #7c8c89;
  --ink-4: #a7b3b0;
  --mono: var(--sans);
  --no: #d2483f;
  --ok: #1f9d57;
  --ok-soft: color-mix(in oklab, var(--ok) 16%, var(--bg));
  --pagebar-h: 0px;
  --radius-md: 10px;
  --rail-w: 0px;
  --rule: #e2e8e7;
  --rule-2: #d2dcda;
  --sans: "Golos Text", system-ui, -apple-system, "Segoe UI", sans-serif;
  --warn: #d99411;
  --warn-soft: color-mix(in oklab, var(--warn) 18%, var(--bg));
}
[data-theme="dark"] .s2 {
  --accent: #2bbfa9;
  --accent-aa: #2bbfa9;
  --accent-soft: color-mix(in oklab, var(--accent) 20%, var(--bg-2));
  --accent-tint: color-mix(in oklab, var(--accent) 13%, var(--card));
  --bg: #0c1413;
  --bg-2: #1b2724;
  --card: #15201d;
  --ink: #e8efed;
  --ink-2: #a9b7b4;
  --ink-3: #7b8a87;
  --ink-4: #566763;
  --no: #e0604f;
  --ok: #34b169;
  --ok-soft: color-mix(in oklab, var(--ok) 22%, var(--bg-2));
  --rule: #24302d;
  --rule-2: #31403c;
  --warn: #e0a02e;
  --warn-soft: color-mix(in oklab, var(--warn) 22%, var(--bg-2));
}

/* ── host bridge: promo base styles the app did not have ─────────────────────── */
.s2 a { text-decoration: none; }

/* ── public/store2.css, verbatim ──────────────────────────────────────────────────────────── */
/* ══ Store v2 — «Тариф и оплата» ══════════════════════════════════════════════
   Ported from design_handoff_store_v2 (Claude Design). Three zones: A состояние
   (facts, flush on --bg) · B прилавок (the one raised card) · C записи (hairlines).

   EVERY class here carries the `s2` prefix. Twenty-five of the design's names —
   .chip .note .item .sheet .modal .st .tag .txt .warn .ok among them — already
   exist somewhere in the app's 36 stylesheets, and a screen that silently
   inherits `.note` from writing-fb.css is the kind of bug that shows up as
   "the padding is wrong on one phone". The prefix is mechanical, not selective.

   Tokens: the app's own --ink/--bg/--card/--rule/--accent are used as-is (21 of
   the design's 38 already matched). The seven type steps and the button/shadow
   pair are defined here because the app had no equivalent. Nothing below
   hardcodes a colour or a px font-size.
   ════════════════════════════════════════════════════════════════════════════ */
.s2 {
  /* Seven steps, each defined once. The design's rule is that every font-size in
     the screen references one of these — that is what makes --font-scale: 1.3
     (Settings → XL) survive without a single clipped label. */
  --t-xs:  calc(11.5px * var(--font-scale));
  --t-sm:  calc(13px   * var(--font-scale));
  --t-md:  calc(14.5px * var(--font-scale));
  --t-lg:  calc(17px   * var(--font-scale));
  --t-xl:  calc(21px   * var(--font-scale));
  --t-2xl: calc(27px   * var(--font-scale));
  --t-3xl: calc(35px   * var(--font-scale));
  --btn-bg: var(--accent-aa);
  --btn-ink: #fff;
  --warn-ink: #8a5c05;
  --sh: 0 1px 2px rgba(16,24,32,.05);
  --sh-pop: 0 2px 6px rgba(16,24,32,.08), 0 26px 60px -30px rgba(16,24,32,.4);
  --scrim: rgba(13,22,20,.45);
  font-size: var(--t-md);
  line-height: 1.5;
}
/* Two rules that lived in the design's theme-scope block, above the product CSS
   this file was cut from — and both are load-bearing. Without `.s2vh` the real
   radio inputs behind every price cell render as native circles floating above
   the cards, which is exactly what the first port looked like. They stay
   FOCUSABLE (opacity, not display:none) because they are the actual control. */
.s2 .s2vh { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.s2 :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.s2 * { box-sizing: border-box; }
:root[data-theme="dark"] .s2 {
  --btn-ink: #0e1a17;
  --warn-ink: var(--warn);
  --sh: 0 1px 2px rgba(0,0,0,.3);
  --sh-pop: 0 2px 5px rgba(0,0,0,.4), 0 26px 60px -30px rgba(0,0,0,.8);
  --scrim: rgba(2,6,5,.66);
}

/* ══ §5.1 · THE LAYOUT ════════════════════════════════════════════════════════
   Rebuilt against design/imports/page-chrome-2026-08-16 (audit row 01, verdict «rebuild the
   layout»; frames screenshots/after/A01-billing.png and P07-billing.png).

   WHAT THE OLD GEOMETRY WAS, measured in the browser before the change so the numbers below are
   receipts rather than intentions (side panel 236px, `state=plus`, light):

     viewport   column x   column w   gutter from the pane   rail
       1600        478        880             242px          none
       1280        318        880              82px          none
       1100        236        864               0px          none     ← touching the panel
        768        236        532               0px          none

   `.s2dwrap` was `max-width:880px; margin:0 auto`, so the whole screen centred itself inside a
   pane it never filled, and the page bar centred WITH it (the bar was a child of the column). R8
   is the fix: content left-aligned at a 32px gutter, capped at --content-max, and the bar spans
   the pane.

   R9 adds the 300px rail. It is separated by ONE VERTICAL HAIRLINE — `border-left` on the rail,
   26px of padding behind it — not by a bordered box; that is T6, and it is the same rule that
   strips the rounded boxes from the sections below.
   ════════════════════════════════════════════════════════════════════════════ */
.s2scr{background:var(--bg);font-size: var(--t-md);line-height:1.5}
/* The body owns the gutter; the page bar has its own (`.pc-pagebar` pads by --gutter too), so the
   bar's back pill and the content below it line up on the same left edge. */
.s2body{padding:26px var(--gutter) 40px}
.s2is-phone .s2body{padding:14px 16px calc(96px + env(safe-area-inset-bottom))}
/* R8 — max 1200px, LEFT-aligned. No `margin:0 auto`: that one declaration is what created the
   242px of dead pane on either side at 1600px. */
.s2work{width:100%;max-width:var(--content-max);display:grid;grid-template-columns:minmax(0,1fr) var(--rail-w);gap:26px;align-items:start}
.s2col{min-width:0;display:flex;flex-direction:column}
/* No rail (the wallet has not loaded) → the reading column keeps the width it has WITH the rail,
   1200 − 300 − 26. Letting it stretch to 1200 would slide the whole page sideways the moment the
   balance arrives, and would set the plan matrix at a width nothing else on the screen uses. */
/* ⚠️ 874px WAS «the width this column has WHEN A RAIL SITS BESIDE IT» — 1200 − 300 − 26. The rail
   was removed on 2026-08-20 (the wallet moved under «Что у вас есть»), so the reservation became
   dead space: the whole surface rendered in a 874px column, left-aligned, with ~300px of empty
   pane to its right. Owner: «there is a bunch of space on the right … please use all the space».

   Now it follows the design's own container instead — `.dwrap{max-width:1160px;margin:0 auto}` —
   so the two columns get the room §9 draws them at, and the block is centred rather than pinned
   left against a gutter it no longer shares with anything. */
.s2solo{width:100%;max-width:1160px;margin-inline:auto}
/* R9 · the rail: one hairline, not a box. */
.s2rail{min-width:0;display:flex;flex-direction:column;
  padding-left:26px;border-left:1px solid var(--rule);
  position:sticky;
  /* ⚠️ NOT the standard's flat `calc(var(--pagebar-h) + 20px)`. `.pc-pagebar` grows with the text
     size (chrome.css lets every bar do that at S→XL), so a fixed 88px offset parks the rail under
     the bar at --font-scale 1.3. Scaling the whole expression tracks the bar, exactly as the full
     test's answer sheet does for the same reason (styles.css, `.ft-shell .ft-aside`). */
  top:calc((var(--pagebar-h, 68px) + 20px) * var(--font-scale))}
.s2rblk{display:flex;flex-direction:column;gap:9px;padding:16px 0;border-top:1px solid var(--rule)}
.s2rblk:first-child{border-top:0;padding-top:2px}
.s2rail-h{font-size: calc((11px) * var(--font-scale));font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4)}
.s2vault{display:flex;flex-direction:column}
.s2vrow{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:8px 0;
  border-bottom:1px solid var(--rule);font-size: var(--t-sm);color:var(--ink-3)}
.s2vrow:last-child{border-bottom:0}
.s2vrow b{font-family:var(--mono);font-weight:700;font-size: var(--t-md);
  color:var(--ink);font-variant-numeric:tabular-nums}
/* R9 · «stacking below 1100px». Measured against the VIEWPORT, as the standard's QA item states it
   — and it is if anything generous here, because this app's pane is 236px narrower than the
   window: at a 1100px viewport the pane is 864px, which a 300px rail plus two 32px gutters would
   leave 538px of reading column. Deliberately in THIS stylesheet rather than chrome.css so the
   screen owns its own breakpoint; chrome.css takes only `pc-` selectors. */
@media (max-width: 1100px) {
  .s2work{grid-template-columns:minmax(0,1fr);gap:0}
  .s2rail{position:static;padding-left:0;border-left:0;border-top:1px solid var(--rule);margin-top:20px;padding-top:4px}
  .s2rail .s2rblk:first-child{border-top:0}
}
/* The phone keeps the same content in the same order (§5.1.6): the rail's blocks simply become
   rows under the reading column, which is what the ≤1100 rule above already does. */
/* `.s2wrap` / `.s2dwrap` / `.s2hdr` / `.s2dhdr` are DELETED, not left behind. They were the two
   centred columns and the two hand-rolled headers this rebuild replaced; a dead copy of a shape
   the standard removed is how the removed shape gets copied back in by the next porter. */
.s2flash{display:flex;align-items:center;gap:9px;background:var(--accent-soft);color:var(--accent-aa);border-radius:10px;padding:11px 13px;font-size: var(--t-sm);font-weight:600;margin:0 0 16px}
.s2flash svg{width:16px;height:16px;flex:none}
.s2flash.s2bad{background:var(--warn-soft);color:var(--warn-ink)}
/* ══ T6 · «a card is a thing you act on, not a region of the page» ═════════════
   Every region on this screen used to be a rounded box: the plan block sat above a rule, the plan
   matrix was a radius-20 card with a shadow, the one-off pack a radius-16 card, each coin pack a
   radius-12 tile. None of them is a thing you act on — they are sections of a reading page — so
   they become a heading, a hairline and 20px of air. The things you DO act on keep their borders:
   the plan radio cells (`.s2cell`, `.s2prow`), the buttons, and the coin rows' hover state.
   ════════════════════════════════════════════════════════════════════════════ */
.s2sect,.s2state,.s2sec,.s2seatblk,.s2calcblk{padding:20px 0;border-top:1px solid var(--rule);margin-top:0}
.s2col>:first-child,.s2col>.s2flash+*{border-top:0;padding-top:2px}

/* ── zone A · state ── */
.s2eyebrow{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size: var(--t-xs);letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);font-weight:600;margin:0 0 11px}
.s2chip{font-size: var(--t-xs);font-weight:600;letter-spacing:.01em;text-transform:none;padding:3px 9px;border-radius:6px;background:var(--bg-2);color:var(--ink-2)}
.s2chip.s2ok{background:var(--accent-soft);color:var(--accent-aa)}
.s2chip.s2warn{background:var(--warn-soft);color:var(--warn-ink)}
.s2chip.s2off{background:var(--bg-2);color:var(--ink-3)}
.s2state-t{font-family:var(--display);font-weight:800;font-size: var(--t-xl);letter-spacing:-.025em;line-height:1.04;margin:0;text-wrap:balance}
.s2state.s2lead .s2state-t{font-size:var(--t-3xl)}
.s2state-s{font-size: var(--t-md);color:var(--ink-2);margin:7px 0 0;max-width:70ch;text-wrap:pretty}
.s2state-p{font-size: var(--t-sm);color:var(--ink-2);margin:12px 0 0;max-width:56ch;line-height:1.6;text-wrap:pretty}
.s2kv{margin:15px 0 0}
.s2kv>div{display:flex;justify-content:space-between;align-items:baseline;gap:18px;padding:11px 0;border-top:1px solid var(--rule)}
.s2kv dt{font-size: var(--t-sm);color:var(--ink-3);flex:none}
.s2kv dd{margin:0;font-size: var(--t-md);font-weight:600;text-align:right;font-variant-numeric:tabular-nums}
.s2kv dd.s2warn{color:var(--warn-ink)}
.s2kv dd.s2mut{font-weight:500;color:var(--ink-2)}
.s2prog{height:8px;border-radius:99px;background:var(--bg-2);overflow:hidden;margin:17px 0 0}
.s2prog i{display:block;height:100%;background:var(--accent);border-radius:99px}
.s2prog.s2warn i{background:var(--warn)}
.s2prog-m{display:flex;justify-content:space-between;gap:14px;margin-top:9px;font-size: var(--t-sm);color:var(--ink-3);font-variant-numeric:tabular-nums}
.s2prog-m b{color:var(--ink);font-weight:600}
.s2sheet .s2kv,.s2modal .s2kv{margin-top:15px}
.s2swbtn{display:inline-flex;align-items:center;gap:10px;min-height:44px;background:none;border:0;padding:0;cursor:pointer;font:inherit;color:inherit;font-weight:600}
.s2trk{width:42px;height:25px;border-radius:99px;background:var(--rule-2);padding:2.5px;display:inline-flex;flex:none}
/* ⚠️ THE KNOB IS `--card` WHEN OFF AND WHITE ONLY WHEN ON (design `.sw i` / `.sw.on i`).
   Hard-coded `#fff` in both states made a DARK-theme switch read as ON while it was off: a white
   knob is the whole "on" signal, and against the dark track it looked exactly like the accent
   state one line below. The design says it in two rules and this is those two rules. */
.s2trk i{width:20px;height:20px;border-radius:50%;background:var(--card);box-shadow:0 1px 2px rgba(0,0,0,.3)}
.s2swbtn[aria-checked=true] .s2trk{background:var(--accent);justify-content:flex-end}
.s2swbtn[aria-checked=true] .s2trk i{background:#fff}
.s2acts{display:flex;gap:9px;flex-wrap:wrap;margin-top:16px}
/* ── ONE CARD PER SUBSCRIPTION, WITH ITS OWN ACTIONS (design 2026-08-18) ────────────────────────
   Zone A used to state ONE subscription and hang every control off the page, so a teacher holding
   Teacher Pro AND a class bundle read one credit line computed against two different bases, and
   «Продлить» could not say WHICH of the two it meant. The design's rule is that the action belongs
   to the subscription, not to the screen — so each one gets a card and carries its own buttons.

   This is the ONLY new component in zone A; everything else here (.s2prog, .s2pips, .s2kv,
   .s2acts, .s2note, .s2chip) is the existing vocabulary, reused rather than re-cut. Deliberately a
   BOX, in a file whose §T6 strips boxes from reading sections: the boxes are what say «these are
   separate things with separate dates and separate buttons», which is the whole point of the
   rework. One subscription still renders flat, with no card around it. */
/* ⚠️ ONE COLUMN, ALWAYS — the mockup's `.holds` is `grid-template-columns:minmax(0,1fr)` and the
   cards stack. The desk rule below used to force `1fr 1fr` unconditionally, so a SINGLE hold was
   laid into a two-column grid and rendered at half width: the card came out 190px wide inside a
   394px column, with «Осталось 268 дн. из 365» wrapping onto three lines while every section under
   it ran the full width. Invisible until the card became the universal shape (it only ever drew
   for two-plus subscriptions before), and then it was the first thing wrong on the page.
   Two side by side is still right when there ARE two, so the pairing is conditional now. */
/* ── ⭐ ONE CARD PER ROW, ALWAYS (owner 2026-08-20) ────────────────────────────────────────────
   «have each subscription go under each other instead of being side by side».
   The pairing rule that used to live here (`:has(> .s2hold ~ .s2hold)` → `1fr 1fr`) halved an
   already-narrow column: a card in a 380px track became ~183px, and these cards are dense —
   name + two chips + term + progress bar + a key/value table + a note + up to four buttons. Every
   one of those wraps. Stacked, each card gets the whole column, which is also what the mockup's
   `.holds` does at every width (`grid-template-columns:minmax(0,1fr)`, no pairing rule anywhere). */
.s2holds{display:grid;grid-template-columns:minmax(0,1fr);gap:10px;margin:12px 0 0}
.s2is-desk .s2holds{gap:14px}
.s2hold{background:var(--card);border:1px solid var(--rule);border-radius:16px;box-shadow:var(--sh);padding:15px 16px 16px;align-content:start}
/* Read-only = paid by somebody else. Tinted and un-shadowed rather than greyed: it is ACTIVE value,
   not a disabled control, and the absence of buttons is already the whole message. */
.s2hold.s2ro{background:var(--accent-tint);border-color:var(--accent-soft);box-shadow:none}
.s2hold-hd{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.s2hold-nm{font-family:var(--display);font-size: var(--t-lg);font-weight:700;letter-spacing:-.015em;margin:0}
.s2hold-hd .s2who{margin-left:auto;font-size: var(--t-xs);color:var(--ink-3);white-space:nowrap;font-variant-numeric:tabular-nums}
.s2hold-s{font-size: var(--t-sm);color:var(--ink-2);margin:5px 0 0;text-wrap:pretty}
.s2hold .s2prog{margin-top:13px}
.s2hold .s2kv{margin-top:11px}
.s2hold .s2acts{margin-top:14px}
.s2hold .s2kv>div:first-child{border-top:0;padding-top:4px}
/* §5.1.3 — the autopay note is «a LEFT-RULED PARAGRAPH», not a tinted box. A reading page has no
   boxes (T6), and a filled panel around one sentence of fact reads as a warning about it. The tone
   survives as the colour of the 2px rule, and it is never the ONLY carrier: every one of these
   notes states its condition in words («Автоматических списаний нет…», «Подписку оформил
   преподаватель…»). Icon suppressed rather than removed — S2Note is shared with the four sheets,
   where a boxed notice inside a dialog is still right (restored at the bottom of this file). */
/* ── ⭐ ONE NOTE, ONE FORM — A TINTED BOX WITH A VISIBLE ICON (One Store v3 §22) ───────────────
   This used to be TWO components wearing one class name: a left-ruled, iconless, untinted
   paragraph on the page, and a tinted box with an icon inside the sheets (the override at the
   bottom of this file). The flat form came from the store-v2 audit's T6, which stripped the boxes
   from the page — and v3 supersedes that round wholesale (IMPLEMENTATION.md §10 lists the prompts
   it replaces). The mockup settles it directly: `holdRenewOff`, `holdBundle` and `proOnly` all
   call `note('info', I.info, …)` ON THE PAGE, and every one renders as a tinted box.
   Two forms also meant the icon was dead weight on the page — `svg{display:none}` threw away an
   element every caller passes — and a warning about money read as body copy in the one place a
   reader is scanning past it. */
.s2note{display:flex;gap:10px;padding:12px 13px;margin-top:13px;border-radius:12px;
  font-size: var(--t-sm);line-height:1.55}
.s2note svg{display:block;width:17px;height:17px;flex:none;margin-top:2px}
.s2note p{margin:0;max-width:70ch;text-wrap:pretty}
.s2note b{font-weight:600}
.s2note.s2warn{background:var(--warn-soft);color:var(--warn-ink)}
.s2note.s2info{background:var(--bg-2);color:var(--ink-2)}
.s2note.s2acc{background:var(--accent-soft);color:var(--accent-aa)}
/* ── buttons ── */
.s2b1,.s2b2,.s2b3{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:var(--sans);font-size: var(--t-md);font-weight:600;min-height:calc(44px * var(--font-scale));padding:0 20px;border-radius:10px;cursor:pointer;white-space:nowrap;line-height:1.2}
.s2b1{background:var(--btn-bg);color:var(--btn-ink);border:0}
.s2b2{background:var(--card);color:var(--ink);border:1px solid var(--rule-2)}
.s2b3{background:none;border:0;color:var(--accent-aa);padding:0 6px}
.s2b1[disabled],.s2b2[disabled],.s2b3[disabled]{opacity:.45;cursor:not-allowed;filter:none}
/* ── §9 ASKS FOR HOVER **AND** :focus-visible ON ALL CONTROLS — focus was covered (line 42), hover
   was not. Four rules existed (`.s2exrow`, `button.s2orow`, `.s2buyline`, `.s2mail`) and the five
   below did not, so the page's primary button, both secondaries and both radio-card idioms gave no
   pointer feedback at all. These are the design's own rules verbatim (`.b1:hover` … `.prow:hover`);
   `:not([disabled])` is the one addition, because a disabled «Перейти к оплате» that brightens
   under the cursor is an invitation to press a button that will not respond. */
.s2b1:hover:not([disabled]){filter:brightness(1.07)}
.s2b2:hover:not([disabled]){border-color:var(--accent);color:var(--accent-aa)}
.s2b3:hover:not([disabled]){color:var(--accent)}
.s2cell:hover,.s2prow:hover{border-color:var(--accent)}
/* ── §9's TOUCH FLOOR WINS OVER THE MOCKUP'S PIXEL VALUE ──────────────────────────────────────
   IMPLEMENTATION.md §9 asks for «цели касания ≥44px»; the mockup's own `.b2.sm` is 38px, and the
   two cannot both be honoured. §9 is the written requirement and the one with a reason behind it —
   these are the card's REAL actions on a phone («Продлить», «Купить ещё места»), not decorative
   chrome. The `.sm` variant keeps what actually distinguishes it — the smaller type and the
   tighter horizontal padding — and gives up only the sub-floor height. Same call at `.s2tabs
   .s2cell` and `.s2mx-btn` below. */
.s2b2.s2sm,.s2b3.s2sm{font-size: var(--t-sm);padding:0 14px;min-height:calc(44px * var(--font-scale))}
.s2b1 svg,.s2b2 svg,.s2b3 svg{width:16px;height:16px}
.s2wfull{width:100%}
/* ── zone B · counter ── */
/* ── ⭐ THE CATALOGUE IS A RAISED CARD AGAIN (One Store v3 §23) ───────────────────────────────
   T6 (store v2) flattened it, arguing that the largest card on the page should not wrap a REGION.
   v3 supersedes that round (IMPLEMENTATION.md §10) and screenshot 01-never-app.png settles it: the
   «Тарифы» block is a white card with a 20px radius standing off the page background, and it has
   to be — on the v3 desktop it shares the right column with «Разовые покупки», «Промокод» and
   «Заказы», which are all FLAT rows. Without the card the one buying surface reads as a fourth
   list of rows, and the top border was doing the whole job of separating it. */
.s2buy{background:var(--card);border:1px solid var(--rule);border-radius:20px;box-shadow:var(--sh);padding:18px;margin-top:18px}
.s2buy-hd{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.s2buy-hd h2{font-family:var(--display);font-size: var(--t-lg);font-weight:700;letter-spacing:-.015em;margin:0}
.s2buy.s2lead .s2buy-hd h2{font-size: var(--t-2xl);letter-spacing:-.022em}
/* THE CLASS BUNDLE'S THIRD AXIS (2026-08-15). Plans × periods is a table; size does not fit in one,
   so it is chosen first and the table re-prices. Deliberately built from `.s2seg` + `.s2cell` rather
   than a new vocabulary: this is the same screen making the same kind of choice, and a second visual
   language here would make one screen read as two products.
   Six bands do not fit one row on a phone, so the strip wraps instead of scrolling — a horizontally
   scrolled radio group hides options, and the last band («больше 65») is the one nobody would think
   to look for. */
.s2bands{margin-top:18px}
/* The «больше 65» line stands where the table would have been, so it needs the table's own breathing
   room — otherwise it reads as a footnote to the size strip rather than as the answer to it. */
.s2quoteline{margin-top:18px}
.s2bands-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.s2bands-t{font-weight:700;font-size: var(--t-sm);color:var(--ink)}
.s2bands-n{font-size: var(--t-xs);color:var(--ink-3);text-wrap:pretty}
/* ⚠️ `.s2seg.s2seg-bands`, not `.s2seg-bands`. `.s2seg` is `display:flex` and is declared LATER in
   this file, so at equal specificity it wins and the grid silently never applies — six bands then
   flex-shrink to 52px each and overflow by 100px. Measured before and after; do not "simplify" this
   selector back to one class. */
.s2seg.s2seg-bands{display:grid;grid-template-columns:repeat(auto-fit,minmax(104px,1fr));gap:8px}
.s2seg.s2seg-bands .s2cell{padding:10px 8px}
.s2buy-sub{font-size: var(--t-sm);color:var(--ink-3);margin:7px 0 0;max-width:70ch;text-wrap:pretty}
.s2tabs{display:flex;gap:3px;margin-left:auto;background:var(--bg-2);border-radius:10px;padding:3px}
.s2tabs .s2cw{display:block}
.s2tabs .s2cell{display:flex;align-items:center;justify-content:center;min-height:calc(44px * var(--font-scale));padding:0 15px;border:0;border-radius:8px;background:none;font-size: var(--t-sm);font-weight:600;color:var(--ink-2);cursor:pointer}
.s2tabs input:checked+.s2cell{background:var(--card);color:var(--ink);box-shadow:0 1px 3px rgba(0,0,0,.14)}
.s2tabs.s2w{margin:15px 0 0;width:100%}
.s2tabs.s2w .s2cw{flex:1}
.s2pend .s2txt{flex:1 1 190px;min-width:0}
.s2item .s2pr{margin-left:auto}
.s2b3.s2no{color:var(--no)}
.s2modal .s2sh-foot .s2wfull{width:auto}
.s2modal .s2sh-foot .s2b3{margin-left:0}
.s2cw{cursor:pointer}
/* ⚠️ `.s2mx` — the desktop plans×periods MATRIX — was deleted on 2026-08-20 with the markup that
   used it. The design has ONE catalogue shape at every width (`catalog()`: tabs, `.seg` periods,
   `.plans` rows) and no table anywhere; the matrix was store v2's, and once §9's right column
   became the narrow one it had ~330px for four price columns plus a 44% plan column and printed
   «6 МЕСЯЦЕВ» over «186 дней». `.s2mx-btn` below is unrelated — it is the modal's close button. */
.s2nm{font-family:var(--display);font-size: var(--t-lg);font-weight:700;letter-spacing:-.015em;display:block;color:var(--ink)}
.s2ct{display:block;font-size: var(--t-sm);color:var(--ink-2);line-height:1.55;margin-top:5px;max-width:70ch;text-wrap:pretty}
.s2ct b{font-weight:600;color:var(--ink)}
.s2cell{display:block;border:1.5px solid var(--rule-2);border-radius:12px;background:var(--card);padding:12px 10px;text-align:center;min-height:calc(44px * var(--font-scale))}
/* A band that is priced «по запросу» is not a price — it is a different action (the paybar swaps
   to «Написать нам»). Muted rather than disabled: it is selectable, it just does not end in a
   checkout, and a disabled-looking control would say the wrong thing about that. */
.s2cell-quote .s2u{opacity:.72;font-style:italic}
/* A quote-only UPGRADE row: a link, not a radio, so it has no `.s2rad` and needs the same left
   inset the priced rows get from theirs. Muted like the band cell it corresponds to — «по запросу»
   is a different action, not a cheaper price. */
.s2cw-quote{display:block;text-decoration:none;color:inherit}
.s2cw-quote .s2prow{padding-left:14px}
.s2cw-quote .s2pr{opacity:.82;font-style:italic}
.s2cell .s2p{display:block;font-family:var(--display);font-size: var(--t-lg);font-weight:700;letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap}
.s2cell .s2u{display:block;font-size: var(--t-xs);color:var(--ink-3);margin-top:3px}
.s2cell .s2u:empty{display:none}
.s2cw input:checked+.s2cell{border-color:var(--accent);background:var(--accent-tint);box-shadow:inset 0 0 0 1px var(--accent)}
.s2cw input:focus-visible+.s2cell,.s2cw input:focus-visible+.s2prow{outline:2px solid var(--accent);outline-offset:2px}
.s2seg{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px}
/* ⚠️ `min-width` IS LOAD-BEARING, and it was missing. `flex:1 1 28%` alone lets a cell shrink
   below its content — and `.s2 *{min-width:0}` above makes that the default — so in the v3 right
   column, which is narrower than the mockup's, four period tiles («Месяц · 3 месяца · 6 месяцев ·
   Год») crushed into one row and printed their labels over their own day counts instead of
   wrapping to two rows. The design writes `min-width:88px` for exactly this. */
.s2seg .s2cw{flex:1 1 28%;min-width:88px}
.s2seg .s2cell{padding:10px 6px}
.s2seg .s2cell .s2p{font-size:var(--t-md)}
.s2plans{display:flex;flex-direction:column;gap:9px;margin-top:10px}
.s2prow{display:flex;align-items:flex-start;gap:12px;border:1.5px solid var(--rule-2);border-radius:14px;background:var(--card);padding:14px}
.s2cw input:checked+.s2prow{border-color:var(--accent);background:var(--accent-tint);box-shadow:inset 0 0 0 1px var(--accent)}
.s2rad{width:20px;height:20px;border-radius:50%;border:1.5px solid var(--rule-2);background:var(--card);flex:none;margin-top:3px}
.s2cw input:checked+.s2prow .s2rad{border:6px solid var(--accent)}
.s2prow .s2txt{min-width:0;flex:1}
.s2prow .s2pr{margin-left:auto;text-align:right;flex:none;font-family:var(--display);font-size: var(--t-lg);font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.s2prow .s2pr small{display:block;font-family:var(--sans);font-size: var(--t-xs);font-weight:400;color:var(--ink-3);margin-top:2px}
/* ══ «Продлить» — периоды строками с ВЫРОВНЕННЫМИ колонками (One Store v3 §5) ═══════════════
   «Колонки выровнены: период · "станет до {дата}" · цена.»

   The plain `.s2prow` puts the date inside the name cell as flowing caption text, so five rows put
   five dates at five different x-positions and the reader has to hunt for each one. The whole point
   of showing every legal period at once is that the rows are COMPARABLE — and a column you cannot
   scan down is not a column. A grid with a header does what the flex row cannot: the dates line up,
   the prices line up, and each column says what it is exactly once.

   Deliberately its own set of classes rather than a modifier on `.s2prow`: that row is used by the
   plan matrix and the upgrade list, where the caption really is prose and must keep flowing. */
.s2exhead{display:grid;grid-template-columns:20px 1fr minmax(104px,auto) minmax(80px,auto);gap:12px;
  padding:14px 14px 6px;font-size: var(--t-xs);font-weight:600;
  letter-spacing:.07em;text-transform:uppercase;color:var(--ink-4)}
.s2exhead span:nth-child(3),.s2exhead span:nth-child(4){text-align:right}
.s2exlist{display:grid;gap:8px}
.s2exrow{display:grid;grid-template-columns:20px 1fr minmax(104px,auto) minmax(80px,auto);gap:12px;align-items:center;
  border:1.5px solid var(--rule-2);border-radius:12px;background:var(--card);padding:12px 14px}
.s2exrow:hover{border-color:var(--accent)}
.s2cw input:checked+.s2exrow{border-color:var(--accent);background:var(--accent-tint);box-shadow:inset 0 0 0 1px var(--accent)}
.s2cw input:focus-visible+.s2exrow{outline:2px solid var(--accent);outline-offset:2px}
/* The design's `.exrow.two`: a pack has a name and a price and nothing between them, so the
   «станет до» column that a period row needs is dropped rather than left empty. */
.s2exrow.s2two{grid-template-columns:20px 1fr minmax(80px,auto)}
.s2exrow .s2rad{margin-top:0}
.s2cw input:checked+.s2exrow .s2rad{border:6px solid var(--accent)}
.s2xnm{font-family:var(--display);font-size: var(--t-md);font-weight:700;min-width:0}
.s2xnm i{font-style:normal;display:block;font-family:var(--sans);font-size: var(--t-xs);
  font-weight:400;color:var(--ink-3);margin-top:1px}
.s2xtill{font-size: var(--t-sm);color:var(--ink-2);text-align:right;
  font-variant-numeric:tabular-nums;white-space:nowrap}
.s2xpr{font-family:var(--display);font-size: var(--t-md);font-weight:700;text-align:right;
  font-variant-numeric:tabular-nums;white-space:nowrap}
/* Narrow phones: the two right-hand columns stop fitting beside a period name long enough to wrap.
   They drop under the name rather than squeezing — still aligned with each other, which is the
   property that mattered. */
@media (max-width: 400px) {
  .s2exhead{display:none}
  .s2exrow,.s2exrow.s2two{grid-template-columns:20px 1fr minmax(72px,auto)}
  .s2exrow .s2xtill{grid-column:2/-1;text-align:left;margin-top:2px}
}
/* ══ ONE STORE v3 — THE ROW IDIOM, PORTED FROM THE MOCKUP ══════════════════════════════════════
   `mockup/support/onestore3.js` builds the whole surface out of four primitives, and the row is the
   one that carries every fact: `orow(title, caption, value)` → title and caption stacked left, the
   number right-aligned in tabular figures. Sections above them are `.osec` — small, uppercase,
   letter-spaced, NOT a card heading.

   ⚠️ I BUILT THIS AS TWO SIDE-BY-SIDE CARDS FIRST, and it was wrong — right facts, wrong form, so
   the owner looked at staging and correctly said the design was not there. The mockup's own source
   (state 12) is THREE ROWS under one heading, buy-row included:

     ПРОВЕРКИ ПИСЬМА — ДВА СЧЁТЧИКА, ДВА СРОКА
       Ежемесячные из Premium   обнулятся 1 сентября…            6 из 10
       Купленный пакет          до 2 августа 2027…               7 из 10
       Купить ещё пакет         10 проверок · 12 месяцев…         499 ₽

   Ported to `s2*` names rather than used verbatim: this stylesheet is scoped by `.s2`, and the
   mockup's bare `.orow` / `.ot` / `.op` would collide with the app's own two-letter classes. */
/* ⚠️ `.s2sec>h2` (line ~426) sets the OLD card-title look on any h2 inside a section, so `.s2osec`
   has to win there too — «Заказы» lives inside `.s2sec` and would otherwise render at card-title
   weight while every other heading on the surface is the small uppercase one. Same specificity
   trick as the rule it overrides, not `!important`. */
.s2osec,.s2sec>h2.s2osec,.s2sec-h>h2.s2osec{display:block;
  font-family:var(--sans);font-size: var(--t-xs);font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);margin:20px 0 9px;
  text-wrap:balance;line-height:1.35}
.s2orow{display:flex;align-items:baseline;gap:14px;padding:12px 2px;border-top:1px solid var(--rule)}
.s2osec+.s2orow,.s2orow:first-of-type{border-top:0}
.s2ot{display:grid;gap:3px;min-width:0;flex:1}
.s2ot b{font-size: var(--t-md);font-weight:600;color:var(--ink)}
/* ⚠️ THE ICON NEEDS ITS OWN SIZE. An inline <svg> with a viewBox and no width/height fills its
   container — the coin rendered ~250px tall and pushed the row's title below it. The mockup sets
   16px on `.oico svg`; nothing else on this page had constrained a bare svg before. */
.s2oico{display:inline-flex;vertical-align:-3px;margin-right:6px;flex:none}
.s2oico svg{width:16px;height:16px;display:block}
.s2ot i{font-style:normal;font-size: var(--t-xs);color:var(--ink-3);
  line-height:1.5;max-width:70ch;text-wrap:pretty}
.s2op{margin-left:auto;text-align:right;flex:none;font-family:var(--display);
  font-size: var(--t-md);font-weight:700;font-variant-numeric:tabular-nums;
  white-space:nowrap;color:var(--ink)}
.s2op small{display:block;font-family:var(--sans);font-size: var(--t-xs);
  font-weight:400;color:var(--ink-3);margin-top:2px}
/* A row that is a CONTROL rather than a statement — «Купить ещё пакет» — reads as a row and acts as
   a button, so the whole row is the hit target and it gets the affordances a button gets. */
button.s2orow{width:100%;background:none;border:0;border-top:1px solid var(--rule);border-radius:0;
  text-align:left;font:inherit;cursor:pointer;min-height:calc(44px * var(--font-scale))}
button.s2orow:hover .s2ot b,button.s2orow:hover .s2op{color:var(--accent-aa)}
button.s2orow:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.s2orow-off .s2op,.s2orow-off .s2ot b{color:var(--ink-4)}
/* ══ §9 · THE DESKTOP IS TWO COLUMNS FROM 900px ════════════════════════════════════════════════
   «≥900px профиль — две колонки (identity+holds | калитка)». Ported from the mockup's `.dwrap`:
   `minmax(280px,380px) minmax(0,1fr)` with a clamped gutter. Left = who you are and what you hold;
   right = everything purchasable, which is also the `data-gate="store"` region §8 describes.

   Below 900 it is one column in the same DOM order, so the phone keeps identity → holds → shop. */
.s2v3{display:grid;grid-template-columns:minmax(0,1fr);gap:0 clamp(24px,4vw,48px);align-items:start}
.s2v3-l,.s2v3-r{min-width:0;display:flex;flex-direction:column}
@media (min-width: 900px) {
  /* ⚠️ `minmax(0,380px)`, not `minmax(280px,380px)`. The media query measures the VIEWPORT while
     this grid lives inside the app's reading column, which is narrower — the pane is ~236px in
     from the window and the rail takes 300 more. With a hard 280px floor the left column keeps
     its width and the RIGHT one is crushed to 0px, which is exactly what the probe rendered at a
     430px container: the entire shop present in the DOM and invisible. Yielding instead means the
     worst case is two narrow columns rather than one empty one. */
  /* ── ⭐ THE LEFT COLUMN IS THE WIDE ONE (owner 2026-08-20) ─────────────────────────────────
     «the cards with the subscription are quite squished, while the right part of the page has a
     lot of empty space, dedicate more space to the left side … and take space from the right,
     they dont need that much space».
     The mockup's ratio (`minmax(280px,380px) minmax(0,1fr)`) assumed a full-width browser page.
     In the app this grid sits inside the reading column, so a 380px cap left the cards cramped
     while the right column — rows, one input and a list — ran on with air to spare.
     BOTH TRACKS ARE `minmax(0, …fr)` ON PURPOSE. A hard floor on either side is what crushed the
     other to 0px before (a 280px floor on the left emptied the right at a 430px container). With
     no floor at all they simply split ~59/41 at every width, so the worst case is two narrow
     columns rather than one invisible one. */
  .s2v3{grid-template-columns:minmax(0,1.45fr) minmax(0,1fr)}
  /* ⚠️ ONE COLUMN WHEN THERE IS NOTHING IN THE SECOND. In a native build the whole
     <section data-gate="store"> is absent, so the right track held nothing and the page kept a
     41% empty gutter at ≥900px — on the SHIPPED APP build, which is the one place nobody sees
     while developing on the web. `:only-child` needs no flag and no new prop: it is true exactly
     when the storefront section did not render. */
  .s2v3>.s2v3-l:only-child{grid-column:1 / -1}
  /* «Заказы» sits OUTSIDE the `data-gate="store"` region (a failed subscription read keeps them,
     a native build does not), so it needs its own place in the grid — the second column, under
     whatever the gate rendered there. Without this it lands in column 1 beneath the holds. */
  .s2v3-orders{grid-column:2}
  /* The left column's first heading and the right column's first heading should sit on one line —
     without this the right column starts 20px lower, because `.s2osec` carries a top margin meant
     to separate it from the block above it, and in the right column there is nothing above it. */
  .s2v3-r > .s2osec:first-child,.s2v3-r > *:first-child > .s2osec:first-child{margin-top:0}
}
/* The holds section is a plain region, not a card: `.osec` heading + the cards under it. It must
   NOT keep `.s2state`'s bottom rule and lead spacing, which framed the old prose block. */
.s2holdsec{padding:0 0 4px}
.s2holds-note{font-size: var(--t-sm);color:var(--ink-2);
  margin:0 0 10px;max-width:70ch;line-height:1.55;text-wrap:pretty}
/* IDENTITY — «Кто вы», the first thing on the surface (§2). */
.s2idb{display:flex;align-items:center;gap:12px;padding:4px 0 16px}
.s2idb-av{width:42px;height:42px;border-radius:50%;background:var(--accent-soft);color:var(--accent-aa);
  font-family:var(--display);font-weight:700;font-size: calc((15px) * var(--font-scale));
  display:grid;place-items:center;flex:none}
.s2idb-t{display:grid;gap:2px;min-width:0}
.s2idb-t b{font-size: var(--t-md);font-weight:700;color:var(--ink)}
.s2idb-t i{font-style:normal;font-size: var(--t-xs);color:var(--ink-3)}
/* «Своя подписка» (state 8д): three labelled groups — экзамен → период → тариф — in one sheet.
   Labelled because the sheet asks three different questions in a row and an unlabelled segmented
   control is only self-evident when there is one of them. */
.s2osgrp{margin-top:16px}
.s2osgrp .s2seg,.s2osgrp .s2tabs,.s2osgrp .s2plans{margin-top:8px}
.s2oslbl{font-size: var(--t-xs);font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--ink-3)}
.s2paybar{display:flex;align-items:center;gap:18px;margin-top:18px;padding-top:16px;border-top:1px solid var(--rule)}
.s2paybar .s2sel{font-size: var(--t-sm);color:var(--ink-2);min-width:0}
.s2paybar .s2tot{display:block;font-family:var(--display);font-size: var(--t-2xl);font-weight:800;letter-spacing:-.022em;color:var(--ink);font-variant-numeric:tabular-nums;margin-bottom:2px}
.s2paybar .s2b1,.s2paybar .s2b2{margin-left:auto;flex:none}
.s2is-phone .s2paybar{flex-direction:column;align-items:stretch;gap:12px}
.s2is-phone .s2paybar .s2b1,.s2is-phone .s2paybar .s2b2{margin-left:0;width:100%}
.s2paybar.s2blocked{align-items:flex-start}
.s2paybar .s2why{font-size: var(--t-sm);color:var(--ink-2);line-height:1.55;text-wrap:pretty;margin:0}
/* 64ch at 11.5px is ~430px — it wrapped to three lines inside a 651px reading column while the
   frame prints it in two, so it was widened. 90ch was too far: §9 caps the measure at 70ch and the
   design's own `.fine` is 70ch. 70ch is ~470px here — still two lines in that column, and it stops
   the fine print running the full width of the solo layout. */
.s2fine{font-size: var(--t-xs);line-height:1.6;color:var(--ink-3);margin:13px 0 0;max-width:70ch;text-wrap:pretty}
/* §8's one permitted line in a native build. It is the whole right-hand column there, so it needs
   the air a section would have had — the design sets `margin-top:26px` on it for the same reason. */
.s2webnote{margin-top:26px}
/* pending order strip */
.s2pend{display:flex;align-items:center;gap:14px;flex-wrap:wrap;background:var(--warn-soft);border-radius:14px;padding:14px 16px;margin-top:22px}
.s2pend .s2t{font-size: var(--t-md);font-weight:600;color:var(--warn-ink)}
.s2pend .s2d{font-size: var(--t-sm);color:var(--warn-ink);opacity:.85;margin-top:3px;text-wrap:pretty}
/* The button rendered inside .s2pend is .s2b1 (store.jsx, the pending-order strip), not .s2b2 —
   this rule had been targeting a class that is not there, so the push-right never applied. */
.s2pend .s2b1{margin-left:auto}
/* ── the ЮKassa return card ──
   Same geometry as .s2pend (it stands in the same slot, directly under Zone A) but with a
   tone per outcome, because this card is the one place on the screen whose MEANING changes
   between renders. Waiting is deliberately NEUTRAL — a green card that later turns out to be
   an unconfirmed payment reads as "paid" from across the room, and that is the exact false
   impression the whole return flow exists to prevent. Only `granted` is green.
   Both sheets: every colour is a token, so light/dark come from the theme, not from a second
   copy of this rule. */
.s2vfy{display:flex;align-items:flex-start;gap:13px;flex-wrap:wrap;border-radius:14px;padding:14px 16px;margin-top:22px;
  background:var(--bg-2);border:1px solid var(--rule)}
.s2vfy>svg{width:18px;height:18px;flex:none;margin-top:2px;color:var(--ink-3)}
.s2vfy .s2txt{flex:1 1 210px;min-width:0}
.s2vfy .s2t{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;font-size: var(--t-md);font-weight:600;color:var(--ink)}
.s2vfy .s2vnum{font-size: var(--t-xs);font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3)}
.s2vfy .s2d{font-size: var(--t-sm);color:var(--ink-2);margin-top:4px;line-height:1.55;max-width:60ch;text-wrap:pretty}
.s2vfy .s2b2,.s2vfy .s2b3{margin-left:auto;flex:none}
.s2vfy.s2vok{background:var(--ok-soft);border-color:transparent}
.s2vfy.s2vok>svg{color:var(--ok)}
.s2vfy.s2vok .s2t{color:var(--ok)}
.s2vfy.s2vno{background:var(--warn-soft);border-color:transparent}
.s2vfy.s2vno>svg,.s2vfy.s2vno .s2t{color:var(--warn-ink)}
/* The waiting mark: a pulsing dot, not a spinner. It occupies the icon slot exactly, so the
   card does not reflow when the phase changes and the dot is replaced by the info glyph. */
.s2vdot{width:11px;height:11px;border-radius:50%;flex:none;margin:6px 4px 0 3px;background:var(--accent);
  animation:s2pulse 1.4s ease-in-out infinite}
@keyframes s2pulse{0%,100%{opacity:.28;transform:scale(.82)}50%{opacity:1;transform:scale(1)}}
@media (prefers-reduced-motion: reduce){.s2vdot{animation:none;opacity:.75}}
/* On phone the card's action drops below the text and becomes the thing a thumb goes for, so it
   takes a full 44px target — measured at 38px with the shared .s2sm sizing, which is fine beside
   a desktop pointer and mean on a phone. */
.s2is-phone .s2vfy .s2b2,.s2is-phone .s2vfy .s2b3{margin-left:0;margin-top:10px;min-height:44px}
/* ── zone B2 · one-offs ── */
/* §5.1.3 — a section is a KICKER plus a hairline, not a display heading. «МОНЕТЫ» at 11px
   uppercase with «косметика, питомец и бустеры в игровой части» right-aligned beside it is the
   frame's own shape, and it is what stops four 17px headings from reading as four page titles. */
.s2sec-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:2px}
.s2sec>h2,.s2sec-h>h2{display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-family:var(--sans);
  font-size: calc((13px) * var(--font-scale));font-weight:600;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-3);margin:0}
.s2sec>p.s2lede{font-size: var(--t-sm);color:var(--ink-3);margin:6px 0 13px;text-wrap:pretty}
.s2sec-h>p.s2lede{margin:0 0 0 auto;text-align:right;font-size: calc((12.5px) * var(--font-scale));color:var(--ink-4)}
/* Right-aligned BESIDE the kicker is a wide-column shape. Once the reading column is narrow the
   explainer wraps to its own line, and a right-aligned ragged-left paragraph under a left-aligned
   kicker reads as a mistake — so below the rail's own breakpoint it simply sits under the kicker,
   which is what the phone frame draws. */
@media (max-width: 1100px) {
  .s2sec-h{display:block}
  .s2sec-h>p.s2lede{margin:6px 0 0;text-align:left}
}
.s2bal{display:inline-flex;align-items:center;gap:7px;margin-left:auto;font-size: var(--t-sm);font-weight:600;color:var(--ink-2);background:var(--bg-2);border-radius:99px;padding:6px 12px;font-variant-numeric:tabular-nums}
.s2bal svg{width:15px;height:15px}
/* T6 — the one-off pack was a radius-16 card with a shadow. It is one purchasable stated as a row,
   in the same idiom as the coin rows below it; the Buy button is the thing you act on. */
.s2item{display:flex;align-items:center;gap:16px;flex-wrap:wrap;background:none;border:0;
  border-bottom:1px solid var(--rule);border-radius:0;box-shadow:none;padding:14px 0;margin-top:9px}
.s2item .s2txt{flex:1 1 200px;min-width:0}
.s2item .s2t{font-size: var(--t-md);font-weight:600}
.s2item .s2d{font-size: var(--t-sm);color:var(--ink-3);margin-top:4px;line-height:1.55;text-wrap:pretty;max-width:70ch}
.s2item .s2pr{font-family:var(--sans);font-size: var(--t-lg);font-weight:600;letter-spacing:0;font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--ink-2)}
.s2is-phone .s2item .s2pr{margin-left:0}
/* ══ §5.1.3 · THE COIN PACKS ARE ROWS ═════════════════════════════════════════
   «four ROWS, not tiles: coin dot · amount (mono, tabular) · optional `чаще всего берут` tag ·
   price · `›`».

   THE DEFECT THIS FIXES, measured at four widths before the change (side panel 236px, light):
     1600px  .s2tag «чаще всего берут» given  66px, needs 108   ·  .s2p «199 ₽» given 20px, needs 40
     1280px  identical (the grid was width-independent at 4 columns)
     1100px  64px / 108                                          ·  19px / 40
      768px  83px / 108                                          ·  27px / 40
   The tile was ~200px wide and held amount, tag and price in ONE flex line; `.s2 *{min-width:0}`
   (added for a different reason — a long plan-contents line must wrap) let all three shrink below
   their text instead of wrapping, so they overprinted. A row gives the label column every pixel
   the amount and its tag need and puts the price in its own auto track, which cannot collide.
   The grid is the frame's: dot · label · price · chevron.
   ════════════════════════════════════════════════════════════════════════════ */
.s2coins{display:flex;flex-direction:column;margin-top:9px}
.s2buyline{display:grid;grid-template-columns:calc(15px * var(--font-scale)) minmax(0,1fr) auto calc(16px * var(--font-scale));
  gap:12px;align-items:center;width:100%;text-align:left;cursor:pointer;
  padding:13px 0;border:0;border-bottom:1px solid var(--rule);border-radius:0;background:none;
  min-height:calc(44px * var(--font-scale));font-family:var(--sans);color:var(--ink)}
.s2buyline:last-child{border-bottom:0}
.s2buyline:hover:not([disabled]){color:var(--accent-aa)}
.s2buyline[disabled]{opacity:.45;cursor:not-allowed}
.s2buyline .s2dot{display:flex;width:calc(15px * var(--font-scale));height:calc(15px * var(--font-scale))}
.s2buyline .s2dot svg{width:100%;height:100%}
/* min-width:0 on the label track, and the tag is allowed to WRAP under the amount rather than
   squeeze it — the phone frame draws exactly that (P07: the tag on its own line). */
.s2buyline .s2lbl{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.s2buyline .s2a{font-family:var(--mono);font-weight:700;font-size: var(--t-md);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.s2buyline .s2rub{font-size: var(--t-md);font-weight:600;color:var(--ink-2);
  font-variant-numeric:tabular-nums;white-space:nowrap;text-align:right}
.s2buyline .s2chv{width:calc(16px * var(--font-scale));height:calc(16px * var(--font-scale));color:var(--ink-4);justify-self:end}
.s2tag{font-size: var(--t-xs);font-weight:500;color:var(--ink-3);border:1px solid var(--rule-2);border-radius:99px;padding:2px 8px;white-space:nowrap}
/* ── zone C · records ── */
.s2rec{margin:0}
.s2rrow{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:15px 0;border-bottom:1px solid var(--rule)}
.s2rrow .s2k{font-size: var(--t-sm);color:var(--ink-3);flex:none}
.s2rrow .s2v{font-size: var(--t-md);font-weight:600;font-variant-numeric:tabular-nums}
.s2rrow .s2v.s2mut{font-weight:400;color:var(--ink-3)}
.s2rrow .s2b3,.s2rrow .s2b2{margin-left:auto}
.s2ords{margin-top:6px}
.s2ord{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:14px 0;border-bottom:1px solid var(--rule)}
.s2ord .s2oc{flex:1 1 auto;min-width:0}
.s2ord .s2id{font-family:var(--display);font-size: var(--t-xs);font-weight:600;letter-spacing:.06em;color:var(--ink-4);font-variant-numeric:tabular-nums}
.s2ord .s2w{font-size: var(--t-md);font-weight:600;margin-top:2px;text-wrap:pretty}
.s2ord .s2m{font-size: var(--t-xs);color:var(--ink-3);margin-top:3px;font-variant-numeric:tabular-nums}
/* The design's `.ost`: status and sum STACKED and right-aligned, so both columns line up down the
   list. It was a horizontal row, which put the amount at a different x on every order. */
.s2ord .s2oe{margin-left:auto;flex:none;display:grid;gap:4px;justify-items:end;align-content:start}
.s2ord .s2osum{font-family:var(--display);font-size: var(--t-md);
  font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--ink)}
.s2st{font-size: var(--t-xs);font-weight:600;padding:5px 10px;border-radius:6px;white-space:nowrap}
.s2st.s2wait{color:var(--warn-ink);background:var(--warn-soft)}
/* ⚠️ `--accent-soft`/`--accent-aa`, not `--ok` (design `.st.ok`). `--ok` is a DIFFERENT green from
   the surface's teal, so the one pill that appears most often on this page — a paid order — was the
   only element on it wearing a colour from outside the palette, sitting two rows under the teal
   «Активен» chip that means roughly the same thing. */
.s2st.s2ok{color:var(--accent-aa);background:var(--accent-soft)}
.s2st.s2off{color:var(--ink-3);background:var(--bg-2)}
/* ── the gift block under a paid gift order ────────────────────────────────────────
   Indented under the order line by a hairline rather than boxed in a card: it belongs TO that
   purchase, and the surrounding zone is deliberately hairline-dense with no cards (see the header
   comment in store.jsx, «Zone C · Записи»). Nothing here is a purchase surface — no price, no
   plan, no buy CTA — and nothing here is a gift CODE: the code lives in the buyer's e-mail, so
   there is deliberately no monospace code slot to style. */
.s2gift{margin-top:10px;padding-left:12px;border-left:2px solid var(--rule)}
.s2gh{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.s2gx{font-size: var(--t-xs);color:var(--ink-3);font-variant-numeric:tabular-nums}
.s2ga{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px}
.s2gift .s2fine{margin-top:8px}
/* The support address on the gift block (owner 2026-08-16). Underlined rather than tinted: this is
   a route, not an action, and the teal accent on this screen belongs to things that spend money. */
.s2mail{color:inherit;text-decoration:underline;text-underline-offset:2px;word-break:break-word}
.s2mail:hover{color:var(--ink)}
.s2empty{font-size: var(--t-sm);color:var(--ink-3);padding:20px 0 4px;text-wrap:pretty}
.s2fail{display:flex;gap:12px;align-items:flex-start;background:var(--card);border:1px solid var(--rule);border-radius:14px;padding:16px;margin-top:12px}
.s2fail svg{width:18px;height:18px;color:var(--warn-ink);flex:none;margin-top:2px}
.s2fail .s2t{font-size: var(--t-md);font-weight:600}
.s2fail .s2d{font-size: var(--t-sm);color:var(--ink-3);margin-top:4px;line-height:1.55;max-width:52ch;text-wrap:pretty}
.s2fail .s2b2{margin-top:12px}
/* ── overlays ── */
.s2ov{position:absolute;inset:0;background:var(--scrim);border-radius:34px}
.s2sheet{position:absolute;left:0;right:0;bottom:0;background:var(--card);border-radius:22px 22px 0 0;box-shadow:var(--sh-pop);padding:8px 20px 20px;max-height:92%;overflow:hidden;display:flex;flex-direction:column}
/* The scrolling half of the overlay. `min-height:0` is the load-bearing line: a flex ITEM defaults
   to min-height:auto, which refuses to shrink below its content — so the wrapper would grow past
   the sheet and clip exactly as before, with the scrollbar it now has never becoming reachable.
   The negative side margins let focus rings and card shadows bleed to the sheet's real edge while
   the padding keeps the text where it was. */
.s2shbody{overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;min-height:0;flex:1 1 auto;margin:0 -20px;padding:0 20px}
.s2grab{width:38px;height:4px;border-radius:99px;background:var(--rule-2);margin:5px auto 14px}
/* The desktop modal had NO max-height at all, so a long sheet grew past the frame in BOTH
   directions — `translate(-50%,-50%)` centres it, which puts the overflow above the top edge where
   no scrollbar can reach it. Capped and made a column for the same reason as the phone sheet. */
/* ⚠️ `min(560px,94%)`, NOT a fixed 460px (design `.mwin`). A fixed width has no lower bound: on a
   desktop window narrowed below ~490px — or a tablet in portrait, which is a real 768px-wide
   two-column layout — the modal was WIDER than the frame it is centred in, and `translate(-50%,-50%)`
   puts that overflow off both edges where no scrollbar reaches it. The design's own max-height is
   94%, not 88%. */
.s2modal{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(560px,94%);max-height:94%;background:var(--card);border-radius:20px;box-shadow:var(--sh-pop);padding:24px 26px 22px;display:flex;flex-direction:column}
.s2modal .s2shbody{margin:0 -26px;padding:0 26px}
.s2mx-btn{position:absolute;top:10px;right:10px;width:44px;height:44px;border-radius:50%;display:grid;place-items:center;color:var(--ink-3);background:none;border:0;cursor:pointer}
.s2mx-btn svg{width:15px;height:15px}
.s2sh-t{font-family:var(--display);font-size: var(--t-xl);font-weight:700;letter-spacing:-.02em;margin:0}
.s2sh-s{font-size: var(--t-sm);color:var(--ink-2);margin:7px 0 0;line-height:1.55;max-width:70ch;text-wrap:pretty}
.s2sh-foot{display:flex;flex-direction:column;gap:9px;margin-top:18px}
/* The design sets the pack list's top gap inline (`style="margin-top:12px"` / `14px`); a class
   keeps it out of the markup and out of the CSP's way. */
.s2packlist{margin-top:13px}
/* ── THE COMMIT BUTTON IS RIGHTMOST, THE WAY OUT SITS TO ITS LEFT (design `.mwin .sh-foot`) ──
   `row` + `margin-left:auto` on the pay button put «Отмена» to the RIGHT of it — the last thing
   under the cursor's resting corner was the button that abandons the purchase. `row-reverse` is
   what the design writes: the first DOM child (the primary) lands on the right edge and the
   secondary packs beside it, with no auto margins to argue about. */
.s2modal .s2sh-foot{flex-direction:row-reverse;align-items:center;gap:12px;justify-content:flex-start}
.s2osum{font-family:var(--display);font-size: var(--t-3xl);font-weight:800;letter-spacing:-.025em;font-variant-numeric:tabular-nums;margin:14px 0 0;line-height:1}
.s2steps{margin:16px 0 0;padding:0;list-style:none;counter-reset:s}
.s2steps li{position:relative;counter-increment:s;padding:0 0 0 30px;font-size: var(--t-sm);line-height:1.6;color:var(--ink-2);text-wrap:pretty}
.s2steps li+li{margin-top:11px}
.s2steps li::before{content:counter(s);position:absolute;left:0;top:1px;width:21px;height:21px;border-radius:50%;background:var(--bg-2);color:var(--ink-2);font-size: var(--t-xs);font-weight:700;display:grid;place-items:center;font-family:var(--display)}
.s2steps b{color:var(--ink);font-weight:600}
.s2code{font-family:var(--display);font-weight:700;letter-spacing:.06em;background:var(--bg-2);border-radius:6px;padding:2px 7px;color:var(--ink);font-variant-numeric:tabular-nums}
.s2fld{display:block;margin-top:14px}
.s2fld span{display:block;font-size: var(--t-sm);color:var(--ink-3);margin-bottom:6px}
.s2fld input{width:100%;font-family:var(--sans);font-size: var(--t-md);color:var(--ink);background:var(--bg-2);border:1.5px solid var(--rule-2);border-radius:10px;padding:0 13px;min-height:calc(48px * var(--font-scale))}
.s2fld input:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}


/* ── app adaptations ──────────────────────────────────────────────────────────
   Three places where the design targeted the DOC's device chrome rather than a
   real app. Kept as a separate block so the ported rules above stay diffable
   against the handoff.                                                        */

/* The overlay covered the phone mock's rounded screen; here it covers the
   viewport, and it must sit above the app's bottom nav rather than under it. */
.s2ov{position:fixed;inset:0;border-radius:0;z-index:120;display:block;background:var(--scrim)}
/* The overlay carries `.s2` for its tokens, which also gives it the screen's
   background — reset that so the scrim shows through. */
.s2.s2ov{font-size:var(--t-md)}
.s2is-desk .s2sheet{display:none}

/* The four sheets used to re-declare `.s2note` as a tinted box because the page's copy was flat.
   The page's copy IS the tinted box now, so there is nothing left to override — one component, one
   context. Only the measure differs: a dialog is already a narrow column, so the 70ch cap has
   nothing to do there and would only fight the padding. */
.s2modal .s2note p,.s2sheet .s2note p{max-width:none}

/* Horizontal containment, the same rule the full-test and practice screens
   needed: a long unbroken plan-contents line must wrap, never widen the page. */
.s2{max-width:100%;overflow-x:clip}
.s2 *{min-width:0}

/* ══ Переход на другой тариф — цена как расчёт, а не как число ═════════════════
   Ported from design_handoff_class_seats_upgrade/support/store2-upgrade.css.
   Additive: nothing above was modified, and every name carries the s2 prefix for
   the reason stated at the top of this file.

   The design's argument, kept intact: an upgrade price shown as a bare number
   looks like a mistake, because it is lower than the price on the plan card. So
   the list price is struck rather than replaced, the credit is its own line, and
   the amount due is the only figure set large. ══════════════════════════════ */
.s2calcblk .s2eyebrow{margin-bottom:11px}
.s2calcblk .s2state-t{font-variant-numeric:tabular-nums}
.s2strike{text-decoration:line-through;text-decoration-thickness:1.5px;color:var(--ink-3)}
.s2calc{margin:20px 0 0;border-top:1px solid var(--rule)}
.s2crow2{display:flex;align-items:baseline;gap:18px;padding:13px 0;border-bottom:1px solid var(--rule)}
.s2crow2 .s2lbl{font-size:var(--t-md);min-width:0;text-wrap:pretty}
.s2crow2 .s2lbl small{display:block;font-size:var(--t-xs);color:var(--ink-2);margin-top:4px;font-variant-numeric:tabular-nums;line-height:1.5}
.s2crow2 .s2pv{margin-left:auto;flex:none;font-family:var(--display);font-size:var(--t-lg);font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.s2crow2 .s2pv.s2old{color:var(--ink-3);text-decoration:line-through;text-decoration-thickness:1.5px;font-weight:600}
.s2crow2 .s2pv.s2neg{color:var(--accent-aa)}
.s2crow2.s2tot{border-bottom:0;padding:16px 0 0}
.s2crow2.s2tot .s2lbl{font-weight:600}
.s2crow2.s2tot .s2pv{font-size:var(--t-2xl);font-weight:800;letter-spacing:-.022em}
.s2said{margin:20px 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:14px}
.s2said li{display:flex;gap:12px;font-size:var(--t-sm);line-height:1.6;color:var(--ink-2);text-wrap:pretty;max-width:70ch}
.s2said li b{color:var(--ink);font-weight:600}
.s2said li::before{content:"";width:3px;flex:none;border-radius:2px;background:var(--rule-2);align-self:stretch}
.s2said li.s2wn::before{background:var(--warn)}
.s2calcnote{background:var(--accent-soft);color:var(--accent-aa);border-radius:14px;padding:15px 16px;margin:20px 0 0;font-size:var(--t-md);line-height:1.6;text-wrap:pretty}
.s2calcnote b{font-weight:700}
.s2calcnote.s2flat{background:var(--bg-2);color:var(--ink)}
.s2calcnote.s2flat b{color:var(--ink)}

/* ══ Места в классах ══════════════════════════════════════════════════════════
   Ported from design_handoff_class_seats_upgrade/support/store2-seats.css, s2-prefixed.
   The pips are aria-hidden on purpose: fifteen squares are a shape, not a reading.
   The line beneath them («11 занято из 15 · свободно 4») is the accessible one, and
   it is also the only place the numbers appear as words. ══════════════════════ */
.s2seatblk .s2eyebrow{margin-bottom:11px}
.s2seatblk .s2state-s b{font-weight:600;color:var(--ink)}
.s2pips{display:flex;flex-wrap:wrap;gap:5px;margin:18px 0 0}
.s2pip{width:calc(15px * var(--font-scale));height:calc(15px * var(--font-scale));border-radius:4px;border:1.5px solid var(--rule-2);background:var(--card)}
.s2pip.s2on{background:var(--accent);border-color:var(--accent)}
.s2pips.s2warn .s2pip.s2on{background:var(--warn);border-color:var(--warn)}
.s2seatbar{height:8px;border-radius:99px;background:var(--bg-2);overflow:hidden;margin:18px 0 0}
.s2seatbar i{display:block;height:100%;background:var(--accent);border-radius:99px}
.s2seatbar.s2warn i{background:var(--warn)}
.s2pipm{display:flex;justify-content:space-between;align-items:baseline;gap:14px;margin:11px 0 0;font-size:var(--t-sm);color:var(--ink-3);font-variant-numeric:tabular-nums}
/* «4 свободных места» — the reason a teacher opened this screen, so it is the largest text on the
   card (the mockup's `.seatlead`). */
.s2seatlead{font-family:var(--display);font-size:var(--t-xl);font-weight:800;letter-spacing:-.02em;margin:10px 0 0}
.s2hold .s2pips,.s2hold .s2seatbar{margin-top:14px}
.s2hold .s2clist{margin-top:12px}
.s2pipm b{color:var(--ink);font-weight:600}
.s2clist{margin:18px 0 0;border-top:1px solid var(--rule)}
.s2crow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid var(--rule)}
.s2crow .s2cnm{font-size:var(--t-md);font-weight:600}
.s2crow .s2use{margin-left:auto;font-size:var(--t-sm);color:var(--ink-2);font-variant-numeric:tabular-nums;white-space:nowrap}
.s2crow .s2use.s2mut{color:var(--ink-4);font-weight:400}

/* The report sheet's free-text box. Same ink and radius as every other field on this screen — a
   complaint form that looks foreign to the page reads as a third-party widget (owner 2026-08-19). */
.s2ta { width: 100%; margin-top: 12px; padding: 11px 13px; border-radius: 10px; box-sizing: border-box;
  border: 1.5px solid var(--rule-2); background: var(--card); color: var(--ink); resize: vertical;
  font: 400 14px/1.5 var(--sans); font-size: calc((14px) * var(--font-scale)); }
.s2ta:focus-visible { outline: none; border-color: color-mix(in oklab, var(--accent) 55%, var(--rule-2)); }

/* ── Промокод в магазине приложения (владелец, 19.08.2026) ──────────────────────────────────────
   Поле было ТОЛЬКО на промо-сайте: сервер принимал promoCode на всех рельсах, а в store.jsx этого
   слова не было вовсе — поэтому SUMMER «не работал» в приложении. Ни одного нового токена: те же
   --card / --rule / --ink*, та же высота и радиус, что у остальных полей экрана. */
.s2promo { margin-top: 18px; }
.s2promo-l { display: block; font-size: calc((12px) * var(--font-scale)); font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 8px; }
.s2promo-row { display: flex; gap: 8px; align-items: stretch; }
.s2promo-i { flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 10px 14px;
  border: 1px solid var(--rule-2); border-radius: var(--radius-md); background: var(--card);
  color: var(--ink); font: 700 15px var(--sans); font-size: calc((15px) * var(--font-scale));
  letter-spacing: .06em; text-transform: uppercase; }
.s2promo-i::placeholder { color: var(--ink-4); font-weight: 400; letter-spacing: 0; text-transform: none; }
.s2promo-i:focus { outline: none; border-color: var(--accent); }
.s2promo-h { font-size: calc((12.5px) * var(--font-scale)); color: var(--ink-4); margin-top: 7px; }
/* Предупреждение о том, что сумма на кнопке — без скидки (см. store.jsx). Тёплый акцент, не ошибка:
   это уточнение, а не отказ. */
.s2promo-w { color: var(--warn-ink, var(--ink-2)); font-weight: 600; }
