/* The reused vocabulary of 05-design-system.md §9, defined once for every
   surface. Every surface loads tokens.css, base.css and this file, in that
   order.

   §4 decides what belongs here rather than elsewhere: an object a control
   renders — a button, a field, a table, a badge, a flash, an empty state — is
   here; the shell around it (.topbar, .strip, .container, .stack, .skip-link)
   is base.css; anything with a single consumer in the SPA is that component's
   own styles.module.css. The panel's .admin-* layout is admin.css, by the
   same rule — one consumer, so it earns no place here.

   Nothing here is written for a page. /_styleguide is what shows every class
   in every palette, and it is what fails when one arrives with no row. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--control-height);
  padding: 0 var(--space-4);
  border: var(--border-width) solid var(--color-line-strong);
  border-radius: var(--radius);
  background: var(--color-bg);
  color: var(--color-fg);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  /* Colour only. No lift, no shadow, no transform. `color` is in the list for
     the THEME FLIP, not for hover: no `.btn` variant moves its `color` on
     hover (`:hover` changes `border-color`, and `--primary`'s also changes
     `background`), but a flip moves every one of the three at once, and
     without `color` the label would snap while the ground it sits on faded. */
  transition: border-color var(--duration), background var(--duration),
              color var(--duration);
}

.btn:hover {
  border-color: var(--color-fg);
}

/* Ink, not accent (plans/deviations.md): §2 gives the hierarchy to type and
   hairlines, and the one filled object on a page of paper is the thing it
   asks you to do. --color-accent survives as the colour that MEANS something —
   /app's revealed answer and openable row, a landing's marks and its charged
   price — which is why this rule spends the ink token directly rather than
   repointing the accent one. */
.btn--primary {
  background: var(--color-fg);
  border-color: var(--color-fg);
  color: var(--color-bg);
}

/* The fill MOVES rather than inverting. An outline-on-hover primary is
   byte-identical to a hovered .btn, which deletes the only signal that this
   is the primary action at the moment a pointer is over it. Declared after
   `.btn:hover`, which it ties with on specificity. */
.btn--primary:hover {
  background: var(--color-muted);
  border-color: var(--color-muted);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field__label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

/* Wired to its input with aria-describedby, never signalled by colour alone.

   The WEIGHT is r12-5's, out of r12-4's handover list. `.field__hint` below is
   the same size and sits `--space-1` above this, so the standing explanation
   and the refusal were two lines four pixels apart with hue the only thing
   between them — §12 satisfied for a screen reader and nothing at all for a
   sighted one. `--weight-medium` is the second channel, and it is the weight
   `.field__label` already spends, so no new vocabulary. Space was the
   alternative: at `--space-1` the next step is four pixels, and a larger one
   detaches the error from the input it belongs to. */
.field__error {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--color-danger);
}

/* §9 states these as one object: "inputs share --control-height, --radius, a
   1px border that becomes --color-line-strong on focus". Three copies of that
   paragraph is how a select and an input drift apart within a release, so the
   shared surface is written once and only the geometry that genuinely differs
   is per class. */
.input,
.select,
.textarea {
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius);
  background: var(--color-bg);
  color: var(--color-fg);
  font: inherit;
  transition: border-color var(--duration);
}

.input,
.select {
  height: var(--control-height);
  padding: 0 var(--space-3);
}

/* The one control whose content has a KNOWN length (r12-5, out of r12-4's
   handover list): `/login/verify`'s six-digit code, which without this takes
   the whole of `.narrow-page__card` — 334px of box for six characters, the one
   thing on that page that looked unconsidered.

   On the CONTROL and not on `.field` or the form. `.field` is worn 30 times
   across seven templates and twenty-three of those are panel fields — a course
   name, a slug, a URL — that all want the full track; the form is worse still,
   because `verify.html`'s holds the `.form-actions` row and the submit button
   would move with it. A known content length is a property of one control and
   of nothing else.

   `ch` so it tracks the type: against the shipped Inter a `0` advances
   0.6309em, so six digits are ~61px and `.input`'s border and padding add 26,
   which 12ch (~121px) clears with room for a caret and for a numeric face
   wider than Inter's. `min(…, 100%)` is `.narrow-page__card`'s own pattern and
   is inert today — the card is 24rem and the box never reaches it — and is
   what stops the modifier overflowing a narrower parent later.

   `--short` and not `--code`: admin.css already has a `--code` modifier, on the
   textarea, and there it means SOURCE code. Two `--code`s meaning two things in
   one vocabulary is worse than a name that states the property. */
.input--short {
  width: min(12ch, 100%);
}

/* No --control-height: it is the one control that grows with its content. */
.textarea {
  padding: var(--space-2) var(--space-3);
  line-height: var(--leading-body);
}

.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--color-line-strong);
}

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

/* Stated explicitly, and after `.btn:hover`: that rule sets --color-fg at the
   same specificity, so source order is the only thing keeping the intent
   colour on a danger button the pointer is over. */
.btn--danger:hover {
  border-color: var(--color-danger);
}

/* Quiet, not invisible (r12-4). It carried `transparent` until a reader said
   the controls wearing it read as text — the logout in the bar, the footer's
   locale switch, the strip's retry, Account's three. It is a BUTTON, and §9
   makes a button a bordered box; what `--quiet` buys is the softer hairline,
   `--color-line` against `.btn`'s `--color-line-strong`, so it sits under a
   primary in the same row without competing with it. Hover still goes to
   `--color-fg` through `.btn:hover`, so the whole ramp is line → line-strong
   → fg and the modifier is one step down rather than a different object. */
.btn--quiet {
  border-color: var(--color-line);
}

/* Square, so the glyph sits centred; §2 allows the one radius on controls. */
.btn--icon {
  width: var(--control-height);
  padding: 0;
}

/* AFTER `.btn--icon` — the last of the `.btn--*` rules, which is what settles
   this rather than specificity alone. At 0-2-0 it beats `.btn--primary`,
   `.btn--danger` and `.btn--quiet` (0-1-0) wherever it sits, and it ties with
   `.btn:hover`, `.btn--primary:hover` and `.btn--danger:hover` (0-2-0), which
   source order then decides. `:hover` is NOT a special case here: a browser
   stops dispatching mouse EVENTS to a disabled control, but the `:hover`
   selector still matches it, so a rule declared earlier would be overwritten
   under the pointer. The one rule this placement does not beat is
   `.plan-card > :last-child` at 0-1-1 further down, which sets `margin-top`
   and nothing this rule sets.

   Both spellings, one treatment: `:disabled` is every in-flight write in the
   product, `[aria-disabled="true"]` is the step page's locked next-step
   control, which stays focusable because its content is a sentence to read.

   The DELAY is what makes this safe to ship product-wide. A disabled .btn has
   been byte-identical to a live one since r0-4, and every one of them until now
   was a sub-second busy flicker; painting them all would trade "no signal" for
   "a grey flash on every press". Nothing happens for the first 120ms and the
   full treatment lands at 240ms, so a fast write still shows nothing and a slow
   one reports itself. Declared here and not on `.btn`, so re-enabling is
   instant: a transition reads its timing off the after-change style, which is
   `.btn`'s own shorthand and its zero delay. */
.btn:disabled,
.btn[aria-disabled="true"] {
  border-color: var(--color-line);
  background: var(--color-bg);
  color: var(--color-muted);
  cursor: default;
  transition-delay: var(--duration);
}

/* The standing explanation of a field, always present. The error below it is
   the one that comes and goes, and both are wired through aria-describedby. */
.field__hint {
  font-size: var(--text-sm);
  color: var(--color-muted);
}

/* The platform's own control, tinted. Redrawing a checkbox means redrawing
   indeterminate, disabled and the focus ring with it. */
.checkbox {
  accent-color: var(--color-accent);
}

/* Byte-identical to `.row` below, and deliberately NOT merged with it (§15.13):
   "the buttons that submit this form" and "things on one line" are different
   purposes, and the day one of them wants `justify-content` or a top border it
   must be able to take it without moving the other. */
.form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Hairline rows, no zebra, no outer border (§9). */
.table {
  width: 100%;
  border-collapse: collapse;
}

/* The row rule, measured. --color-line is the palette's quietest hairline —
   1.29:1 on --color-bg in light, 1.33:1 in dark — and forty rows of it read as
   a wash; --color-line-strong is the same hairline one step up (1.78:1 light,
   1.83:1 dark), which is what a table of records needs. Repaired here and not
   forked into admin.css: the only other page that renders `.table` is
   /_styleguide, whose job is showing what the panel renders. */
.table th,
.table td {
  border-bottom: var(--border-width) solid var(--color-line-strong);
  text-align: left;
  padding: var(--space-2) 0;
}

/* tabular-nums, so a money column lines up under its own heading. */
.table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.table__actions {
  text-align: right;
  white-space: nowrap;
}

/* The two layout primitives beside .stack: things on one line, and things
   pushed to opposite ends of it. */
.row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.split {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

/* A bordered label in the intent colour, never a filled pill (§9). */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  border: var(--border-width) solid var(--color-line-strong);
  border-radius: var(--radius);
  font-size: var(--text-sm);
}

.badge--ok {
  border-color: var(--color-success);
  color: var(--color-success);
}

.badge--warn {
  border-color: var(--color-warning);
  color: var(--color-warning);
}

.badge--danger {
  border-color: var(--color-danger);
  color: var(--color-danger);
}

/* A price and the price it replaced (r10-2). Here and not in marketing.css
   because two surfaces render it — the landing page and /app/plans — which is
   §4's promotion rule, and the styleguide row comes with it. `.plan-card__price`
   was the marketing-only version and is gone.

   No rule for a struck-through amount standing alone: app/commerce/queries.py
   emits the "was" amount and the badge together or emits neither, so the
   line-through can never appear without the text beside it that says what it
   means (§12). `<s>` and not `<del>`, which claims an edit to the document
   rather than a change of price. */
.price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
}

.price__was {
  color: var(--color-muted);
  text-decoration: line-through;
}

.price__now {
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
}

/* The outcome of something the reader just did, in the flow of the page. Not
   a toast: it does not slide, does not float and does not leave (§7). */
.flash {
  padding: var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--color-line);
}

/* No ground: the asymmetry is §3's registry, which has a --color-danger-weak
   and names .flash--error as its consumer, and no success equivalent. A
   --color-success-weak would be a token added for symmetry, which is the one
   reason §3 says is not a reason. */
.flash--ok {
  border-color: var(--color-success);
}

.flash--error {
  border-color: var(--color-danger);
  background: var(--color-danger-weak);
}

/* THE OUTLINE (§4, promoted at r12-3). A bordered block that stacks what is
   inside it — the shape five consumers draw: the landing's plan card, the
   door's language card, `/login`'s narrow-page card, `/app/account`'s
   subscription card and the empty state. §4 promotes on
   the SECOND consumer, and r12-3's `.language-card` was the third, so this is
   overdue rather than early.

   Four declarations, and the divergence is deliberate. The border and the
   radius ARE the card — since r12-5 no BLOCK in the product is a filled box,
   which is the rule `.empty` was the last exception to — and the flex column is
   what a card does with its contents. What is NOT here is the gap and the
   padding: those are 3/4/2/4/0 and 6/6/4/6/6-4 across the five, because how tightly
   a card packs is a property of what is in it, and §15.13 refuses to merge what
   merely looks alike.

   Worn beside the consumer's own class — `class="card plan-card"` — so the
   call site says both what the thing is and what draws it. */
.card {
  display: flex;
  flex-direction: column;
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius);
}

/* A sentence and one link (§8) — no illustration, no dashed border, no centred
   icon. The GROUND is r12-5's departure: §8 sets this on `--color-surface`, and
   it is an outline now, worn as `class="card empty"` beside every one of its
   ten call sites (plans/deviations.md). r12-1 took the fill out of every other
   block in the product and this was the last one left, so on paper it read as
   the one thing nobody had redrawn.

   `display: block` is the one declaration that CONTRADICTS `.card` rather than
   completing it, and it is the reason this rule is declared after it — the two
   selectors tie at 0-1-0. `.card`'s other four consumers stack blocks and want
   the column; this one holds a SENTENCE with an inline link in it, and as flex
   items the words and the link are separate rows. So the fifth consumer wears
   the outline and declines the column, which is a smaller divergence than a
   second copy of the hairline and the radius would be.

   The padding is unchanged from the filled version. */
.empty {
  display: block;
  padding: var(--space-6) var(--space-4);
}

/* THE PRICE BAND (§4, promoted at r12-5). Two surfaces render it now — the
   landings and `/app/plans`, one hop apart in a learner's session — and the
   SPA does not load marketing.css, so a learner who pressed Subscribe on a
   landing met a different-looking page for the same three offers.

   This rule is the BAND, and only the band: the flex column that holds the
   <h2> off the cards (base.css zeroes its margin), the gap, and the space
   that opens it. Escaping `.container`'s middle track is `.bleed`'s, in
   base.css beside the grid it escapes — the markup reads
   `class="pricing bleed"`, so a call site says both.

   `margin-block-start` and not padding, so the band opens on the same rhythm
   every `.section` above it does on a landing. It has no hairline of its own:
   the band IS the separation.

   Four rules stayed in marketing.css because each still has ONE consumer, and
   that sheet's header is where they are enumerated. They stay landing-only by
   the mechanism that already exists: `/app` does not load it. */
.pricing {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  margin-block-start: var(--space-12);
}

/* THREE IN A ROW, OR ONE UNDER THE OTHER, AND NEVER ANYTHING BETWEEN (r12-2,
   moved here by r12-5's promotion).

   `--plan-columns` IS THE PARAMETER: how many plan cards a row holds, named
   and set here rather than computed. It replaces r12-1's
   `repeat(auto-fit, minmax(16rem, 1fr))`, which is structurally incapable of
   the rule — `auto-fit` fits as MANY as it can, so at any width between two
   floors and three it renders two cards and orphans the third onto a row of
   its own. Measured on the shipped page, the band is 1040px at a 1280px
   viewport (three fit) and 868px at 900px (three need 848px, so they still
   fit) — but at 860px it is 828px and the third plan drops. There is no floor
   that removes that window; only a fixed column count does.

   Mobile-first, so ONE is the default and the query is additive `min-width`,
   which is the polarity §5 fixes and stylelint enforces (`max-width` is a
   disallowed media feature).

   WHERE 55rem COMES FROM, because a bare breakpoint is §15.1's defect. Three
   columns need `3 x 16rem + 2 x --space-8` = 768px + 80px = 848px of band.
   `base.css`'s `.bleed` sets the band's width from that same expression, and
   `.container`'s inline padding is `--space-4` a side, so at a narrower
   viewport the band is `viewport - 32px`. 848 + 32 = 880px = 55rem, where the
   three columns are exactly 16rem each. The two rules are in two sheets since
   the promotion and `16rem` appears in both, which is the one place this
   arithmetic can now drift: change either and check the other.

   Fixed at 3 rather than driven by `plans | length`: `style="--plan-columns:
   {{ … }}"` is an inline style and `app/http/headers.py`'s CSP is
   `style-src 'self'` with no `'unsafe-inline'`, so the browser would drop it
   and the band would silently render one column. Every market this site has a
   landing for sells exactly three plans (content/plans/*.yaml). */
.pricing__cards {
  --plan-columns: 1;

  display: grid;
  grid-template-columns: repeat(var(--plan-columns), 1fr);
  gap: var(--space-8);
}

@media (min-width: 55rem) {
  .pricing__cards {
    --plan-columns: 3;
  }
}

/* An OUTLINE card (r12-2, plans/deviations.md). The hairline, the 3px radius
   and the column are `components.css`'s `.card` since r12-3 promoted them on
   their third consumer — the markup wears `class="card plan-card"` — and what
   is left here is how tightly THIS card packs. The argument for the shape is
   still this one: a hairline border, the radius the token already carries,
   and NO fill. §2's anti-reference is *"large
   rounded cards floating on a tinted ground"* — three properties, and the fill
   and the large radius are two of them. A bordered outline on the page's own
   ground is a drawn edge, not a floating object: nothing is tinted, nothing
   recesses, and at 3px the corner is a cut rather than a pill.

   The border is what the card needs and the space could not give it. A price
   band is a COMPARISON, so a reader has to see where one offer stops and the
   next begins; r12-1 left that to --space-8 alone, and with three cards of
   different bullet counts the columns read as one ragged block. --space-6 of
   padding, because a border with text against it is a table cell. */
.plan-card {
  gap: var(--space-3);
  padding: var(--space-6);
}

/* The card's LAST CHILD sits on the last row whatever the bullets above it run
   to, so a short card's control lines up with a long card's.

   `:last-child` and not `.btn` (r12-5's review): on `/app/plans` the plan the
   learner already holds renders «Ваш тариф» as a `<p>` instead of a button —
   §6's change flow gives the held plan no Buy — so a `.btn` selector matched
   nothing on exactly one card of three and left its last line floating under
   the bullets while both neighbours' buttons were pushed down. What the rule
   is actually about is the last thing in the card, whatever element that is. */
.plan-card > :last-child {
  margin-top: auto;
}

/* The one thing base.css's h3 rule does not say about this element: a plan's
   name is the card's own heading and is set a step up from the document's.
   Margin, weight, leading and the serif all come from that rule since r12-1,
   and restating any of them here would be a second place to change them. */
.plan-card__title {
  font-size: var(--text-lg);
}
