/* Storefront-only styles.
 *
 * The storefront loads styles.css whole and adds this file on top: the design system is
 * inherited, not restated (docs/design-system.md). Everything here is a shape the two
 * application surfaces have no equivalent of — a site header instead of a sidebar, a
 * marketing hero, a pricing card, a documentation column.
 *
 * No token is redefined. A second palette would diverge from the first, and a diverged
 * storefront is one that reads as a different product from the one behind the login.
 *
 * Sizes come from content, never from a fixed width: model ids, tariff titles and
 * translated copy all vary in length (AGENTS.md). Logical properties throughout, for the
 * same reason the panel uses them.
 */

/* The application surfaces are a shell with its own scroll containers. This one is an
   ordinary document, so it scrolls as one and the header sticks. */
body.public {
  display: flex;
  flex-direction: column;
  overflow: auto;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  min-height: 100dvh;
}

body.public > .site-main {
  flex: 1 0 auto;
}

/* 0. Conversion path ===================================================== */
.quick-start {
  display: grid;
  grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.8fr) auto;
  gap: 28px;
  align-items: center;
  margin-block-start: 18px;
  padding: 22px 24px;
  border-block: 1px solid var(--line);
  background: var(--surface);
}

.quick-start-heading h2 {
  max-width: 280px;
  margin: 6px 0 0;
  font-size: 18px;
  letter-spacing: -0.012em;
}

.quick-start-steps {
  display: block;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

/* Full-width ruled rows, the reference's dominant shape: the number holds a fixed column,
   the label the next, and the description takes the remainder of the measure. A three-column
   grid of small cards left two thirds of the row empty and made every section on the page
   look like the one above it. */
.quick-start-steps li {
  display: grid;
  grid-template-columns: 56px minmax(0, 240px) minmax(0, 1fr);
  gap: 32px;
  align-items: baseline;
  padding-block: 22px;
  border-block-start: 1px solid var(--line);
}

.quick-start-steps li:last-child {
  border-block-end: 1px solid var(--line);
}

/* The markup wraps label and description in one span. `display: contents` dissolves it so
   the two become the row's second and third columns, which is what lets the number, the
   label and the description share one baseline grid. */
.quick-start-steps li > span { display: contents; }

.quick-start-steps b {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  color: var(--text-faint);
}

.quick-start-steps strong,
.quick-start-steps small { display: block; }
.quick-start-steps strong { font-size: 17px; font-weight: 500; letter-spacing: -0.012em; color: var(--text); }
.quick-start-steps small { margin: 0; max-width: var(--measure-wide); font-size: 14px; line-height: 1.6; color: var(--text-muted); }

.quick-start-proof {
  display: grid;
  gap: 3px;
  min-width: 132px;
  padding-inline-start: 22px;
  border-inline-start: 1px solid var(--line);
  font-size: 12px;
}

.quick-start-proof strong { font-family: var(--font-mono); font-size: 16px; }
.quick-start-proof span { color: var(--text-muted); }
.quick-start-proof a { margin-block-start: 8px; color: var(--accent); font-size: 12px; }
.quick-start-proof svg { width: 13px; height: 13px; vertical-align: -2px; }

@media (max-width: 900px) {
  .quick-start { grid-template-columns: 1fr; gap: 18px; }
  .quick-start-heading h2 { max-width: 520px; }
  .quick-start-proof { padding-block-start: 16px; padding-inline-start: 0; border-block-start: 1px solid var(--line); border-inline-start: 0; }
}

@media (max-width: 520px) {
  .quick-start { padding: 18px 16px; }
  /* Below this width three columns cannot hold a line each, so the row becomes two: the
     index beside the label, the description under both. */
  .quick-start-steps li { grid-template-columns: 40px minmax(0, 1fr); gap: 6px 16px; padding-block: 18px; }
  .quick-start-steps small { grid-column: 2; }
}

/* 1. Header ============================================================== */
/* Pricing mode choice */
.pricing-modes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  border-block-start: 1px solid var(--line);
}
.pricing-mode { display: grid; gap: 6px; padding: 20px 22px 22px 0; border: 0; border-block-end: 1px solid var(--line); border-radius: 0; background: transparent; }
.pricing-mode strong { font-size: 15px; }
.pricing-mode span { color: var(--text-muted); font-size: 13px; line-height: 1.45; }
.pricing-mode a { margin-block-start: 5px; color: var(--accent); font-size: 13px; }
@media (max-width: 520px) { .pricing-modes { grid-template-columns: 1fr; } }
.page-intro-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-block-start: 18px; }
.model-use-link { margin-inline-start: 10px; color: var(--accent); font-size: 11px; white-space: nowrap; }
.model-use-link:hover { color: var(--text); }

.site-head {
  position: sticky;
  inset-block-start: 0;
  z-index: 60;
  border-block-end: 1px solid var(--line);
  /* Translucent rather than solid: the hero's mark scrolls under it, and a solid bar
     would cut the page in two at a line nothing else explains. The fallback matters —
     without backdrop-filter the bar must still be opaque enough to read against. */
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.site-head-inner {
  display: flex;
  align-items: center;
  gap: 22px;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: 12px clamp(16px, 4vw, 40px);
}

/* The lockup is a link here, unlike in the panel where it is a label: on a storefront
   the mark is how a visitor gets back to the front page. */
.site-head .brand {
  flex: none;
  text-decoration: none;
  color: inherit;
}

.site-head .brand:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-inline-end: auto;
  min-width: 0;
}

.site-nav a {
  padding: 7px 11px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.site-nav a:hover {
  color: var(--text);
  background: var(--surface-hover);
}

.site-nav a:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

/* The current section is marked by weight and colour together, not by colour alone:
   colour as the only marker fails for a reader who cannot distinguish it. */
.site-nav a[aria-current="page"] {
  color: var(--text);
  font-weight: 560;
  background: var(--surface-2);
}

.site-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* The live verdict, as a link to its own page. Three states, and each is marked by the
   dot's colour *and* by its text — "operational" next to a green dot is redundant on
   purpose, because the dot alone is not readable to everyone. */
.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  font-size: 12px;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
}

.status-chip:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.status-chip:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

.status-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--text-faint);
  flex: none;
}

.status-chip.operational {
  border-color: var(--ok-line);
  color: var(--ok);
}

.status-chip.operational .status-dot {
  background: var(--ok);
}

.status-chip.degraded {
  border-color: var(--warn-line);
  color: var(--warn);
}

.status-chip.degraded .status-dot {
  background: var(--warn);
}

.status-chip.down {
  border-color: var(--danger-line);
  color: var(--danger);
}

.status-chip.down .status-dot {
  background: var(--danger);
}

/* The honest fourth state: the status request failed, so the chip says
   "неизвестно" instead of keeping a stale verdict or lying "operational".
   Neutral, not warn: an unanswered request is not an observed outage. */
.status-chip.unknown {
  border-color: var(--neutral-line);
  color: var(--text-muted);
}

.status-chip.unknown .status-dot {
  background: var(--text-faint);
}

@keyframes public-health-pulse {
  0%, 100% { opacity: .58; transform: scale(.86); }
  50% { opacity: 1; transform: scale(1); }
}

.status-dot[data-icon-state="health"] {
  transform-origin: center;
  animation: public-health-pulse 4.5s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .status-dot[data-icon-state="health"] { animation: none; }
}

/* The burger only exists below the width where the nav stops fitting. */
.nav-toggle {
  display: none;
}

/* 2. Page frame ========================================================== */

.site-main {
  max-width: var(--content-max);
  margin-inline: auto;
  padding: clamp(28px, 5vw, 56px) clamp(16px, 4vw, 40px) 72px;
  /* The main region is focusable so a route change can move focus into it. Without
     removing the ring the outline would flash on every navigation. */
  outline: none;
}

/* A page's own heading block. Wider max-width than the panel's, because this copy is
   prose meant to be read rather than a label above a table. */
.page-intro {
  max-width: var(--measure-wide);
  margin-block-end: 34px;
  position: relative;
  padding-block-end: 24px;
  border-block-end: 1px solid var(--line);
}

.page-intro h1 {
  font-family: var(--font-display);
  font-size: var(--text-editorial);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: var(--text-editorial-leading);
}

.page-intro p {
  margin-block-start: 14px;
  font-size: var(--text-body);
  line-height: var(--text-body-leading);
  color: var(--text-muted);
}

/* The eyebrow above a heading. It is the service layer, not prose: monospace, spaced,
   uppercase, faint. Without a base rule these labels rendered as ordinary 14px body text,
   which is why a section opened with what looked like a stray sentence.

   The reference numbers its sections, and the number is the whole of what an eyebrow has
   to say when the heading already says the rest. The counter is what supplies it. */
.overline {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.site-main {
  counter-reset: section;
}

.site-section,
.quick-start {
  counter-increment: section;
}

.site-section > .overline::before,
.site-section .section-heading-row .overline::before,
.quick-start .quick-start-heading .overline::before {
  content: counter(section, decimal-leading-zero) " — ";
  color: var(--text-faint);
}

/* 3. Hero ================================================================ */

.hero {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  min-height: 0;
  margin-block-end: 24px;
}

.hero-copy {
  position: relative;
  z-index: 1;
  max-width: 700px;
}

.mascot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.mascot {
  z-index: 0;
}


/* The sprite carries its own greys, so it reads on paper and on a dark page without a
   per-theme inversion or a per-theme opacity. That is the whole reason the figure is a
   drawing of a grey object rather than a photograph. */

/* Editorial storefront: the artwork occupies the right, not the copy. */
.public .site-main { max-width: 1280px; padding-inline: clamp(20px, 5vw, 64px); }
.public .hero { min-height: auto; margin-bottom: var(--section-gap); padding-block: clamp(40px, 6vw, 88px) clamp(24px, 4vw, 56px); }
.public .hero-copy { max-width: 620px; width: 54%; }
.public .hero-actions { max-width: 460px; }
.public .hero-copy .overline { background: transparent; }
/* Editorial storefront: the figure occupies the right, not the copy. */
.public .hero .mascot { inset: 0 0 0 auto; width: 46%; height: 100%; z-index: 0; opacity: .8; }
.public .hero-copy > p { max-width: 490px; }
.public .quick-start { grid-template-columns: 1fr; gap: 28px; padding: var(--section-gap) 0; background: transparent; }
.public .quick-start-heading h2 { max-width: 760px; font-size: clamp(22px, 2.5vw, 30px); }
.public .site-section { margin-block: 0; padding-block: var(--section-gap); }
.public .hero-copy > .overline { font-size: 11px; line-height: 16px; letter-spacing: .1em; margin: 0 0 16px; }
.public .hero-copy > p:not(.overline):not(.hero-note) { margin-top: 24px; }
.public .hero-actions { gap: 12px; margin-top: 24px; }
.public .hero-copy > .hero-note { margin-top: 16px; }
.public .section-heading-row { gap: 24px; margin-bottom: 24px; }
.public .card-grid { gap: 16px; }
.public .quick-start { margin-bottom: 0; }
.public .info-card { gap: 10px; }
.public .info-card h3, .public .info-card p { margin: 0; }
/* A ruled entry, not a panel: the storefront's cards are rows of a document, and the
   surface fill belongs to the page rather than to each item. */
.public .info-card { padding: 24px 26px 26px 0; min-height: 0; background: transparent; border-inline-end: 1px solid var(--line); }
.public .info-card h3 { font-size: 17px; line-height: 1.3; }
.public .info-card p { font-size: 14px; line-height: 1.65; }

/* The ruled list — the storefront's second composition. Sections alternate between a row of
   cards and a list of full-width entries: five sections that were all a three-card grid read
   as one repeated block, and the reference never repeats a shape twice in a row. Each entry
   keeps the reference's line: index, label, then the description at the measure.

   The index is the `::before` counter `.info-card` already carries — not a second element.
   The counter's box has to be named explicitly, because an implicit one would be created
   before the generated content and would swallow the first column. */
.public .ruled-list { display: block; }

.public .ruled-list .info-card {
  grid-template-columns: 56px minmax(0, 240px) minmax(0, 1fr);
  grid-template-areas: "index label copy";
  column-gap: 32px;
  align-items: baseline;
  padding: 22px 0;
  min-height: 0;
  background: transparent;
  border-block-start: 1px solid var(--line);
  border-block-end: 0;
  border-inline-end: 0;
}

.public .ruled-list .info-card::before { grid-area: index; }
.public .ruled-list .info-card h3 { grid-area: label; }
.public .ruled-list .info-card p { grid-area: copy; }

.public .ruled-list .info-card:last-child {
  border-block-end: 1px solid var(--line);
}
.public .hero-actions .btn { min-height: 46px; }
@media (max-width: 900px) {
  .public .hero { min-height: 0; display: flex; flex-direction: column; align-items: stretch; gap: 0; padding-bottom: 8px; margin-bottom: 4px; }
  /* Both canvases leave the absolute flow at this width. The portrait becomes a block
     above the copy, and the CRT field joins it: left absolute it kept its own 58%-wide
     column and drew scanlines across the headline — measured at 390px, the field's box
     covered the h1 while the portrait it belongs to had already moved above it. Following
     the portrait is the only correct answer, because the field is that screen's texture. */
  /* The figure leaves the absolute flow at this width and becomes a block above the copy.
     It was absolute until 900px, where a 46%-wide column beside the headline stops being
     readable: at 390px the figure's box covered the h1. */
  .public .hero .mascot { position: relative; flex: none; order: -1; inset: auto; width: 100%; height: 236px; }
  .public .hero-copy > .overline { margin-top: 0; }
  .public .info-card { min-height: 0; padding: 20px 0 22px; border-inline-end: 0; }
  .public .ruled-list .info-card { grid-template-columns: 40px minmax(0, 1fr); grid-template-areas: "index label" "index copy"; row-gap: 6px; }
  .public .quick-start { margin-top: 0; gap: 12px; }
  .public .hero-copy { width: 100%; max-width: 620px; }
  .public .hero .mascot { top: 0; width: 100%; height: 236px; opacity: .82; }
  .public .card-grid { grid-template-columns: 1fr; }
  .public .section-heading-row { flex-direction: column; align-items: start; }
}
@media (max-width: 520px) {
  .public .site-main { padding-top: 12px; }
  .public .hero { min-height: 0; padding-block: 0 8px; }
  .public .hero .mascot { height: 264px; opacity: .9; }
  .public .hero-copy > .hero-note { margin-top: 8px; font-size: 12px; }
  .public .hero-actions .btn.ghost { min-height: 36px; }
  .public .hero-copy h1 { text-wrap: balance; }
  .public .hero-note { font-family: var(--font-sans); font-size: 12px; line-height: 1.6; }
  .public .hero-actions { margin-top: 20px; }
  .public .hero-actions { display: grid; grid-template-columns: 1fr; }
  .public .hero-actions .btn { justify-content: center; }
}

.hero-copy h1 {
  font-family: var(--font-display);
  /* The reference's display step: it clamps against the viewport instead of stepping at
     breakpoints, so the headline is never a size somebody picked for a different screen.
     Weight 600 and tracking -0.058em are the reference's own measured values; the lighter
     500 it replaced is what made the headline read as an enlarged paragraph. */
  font-size: var(--text-display);
  font-weight: 600;
  letter-spacing: var(--text-display-tracking);
  line-height: var(--text-display-leading);
}

.hero-copy > p {
  margin-block-start: 20px;
  max-width: var(--measure-narrow);
  font-size: var(--text-body);
  line-height: var(--text-body-leading);
  color: var(--text-muted);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-block-start: 28px;
}
 
 .hero-note { margin-block-start: 14px; color: var(--text-faint); font-family: var(--font-mono); font-size: 11px; line-height: 1.5; }
 .section-heading-row { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-block-end: 22px; }
 .section-heading-row > div { min-width: 0; }
 .section-heading-row > div > p:last-child { margin-block-end: 0; }
 .supplier-cta { padding-block: clamp(30px, 5vw, 58px); border-block: 1px solid var(--line); }
 .supplier-cta h2 { max-width: 620px; }
 .supplier-cta p:not(.overline) { max-width: 650px; }
 .intro-section { padding-block-start: clamp(24px, 4vw, 48px); }
 @media (max-width: 700px) { .section-heading-row { align-items: start; flex-direction: column; gap: 14px; } }


/* 4. Feature and pricing cards =========================================== */

.card-grid {
  display: grid;
  gap: 0;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  border-block-start: 1px solid var(--line);
}

/* A card is a ruled entry, not a tile: the reference reads as a document, and a grid of
   bordered boxes is what made the storefront look like a template. The rule is the
   separator, the number is the index, and the surface stays the page's own. */
.info-card {
  display: grid;
  gap: 8px;
  align-content: start;
  padding: 22px 24px 24px 0;
  border: 0;
  border-block-end: 1px solid var(--line);
  border-inline-end: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  counter-increment: card-index;
  transition: border-color var(--dur-fast) var(--ease);
}

.info-card:last-child {
  border-inline-end: 0;
}

.info-card::before {
  content: counter(card-index, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}

.card-grid {
  counter-reset: card-index;
}

.info-card:hover {
  border-color: var(--line-strong);
}

.info-card > svg {
  width: 20px;
  height: 20px;
  /* Filled like the rest of the set; the stroke here was for the old drawings. */
  fill: var(--text-muted);
  stroke: none;
  stroke-linejoin: round;
}

.info-card h3 {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
}

.info-card p {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-muted);
}

/* A tariff as a customer chooses it: the name, the promise, and what it does not say.
   No price on the card — a tariff is a set of vendors, and its price is per model. */
.tariff-card {
  display: grid;
  gap: 12px;
  align-content: start;
  padding: 22px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.tariff-card h3 {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 500;
}

.tariff-card .promise {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-muted);
}

/* Периодные планы на витрине. Карточки приходят из /api/public/offers: цена периода
   у подписки, суточная квота у FREE. Это не чекаут: кнопка ведёт в аккаунт, списание
   происходит в кабинете. Featured — акцент, а не второй продукт.

   Full-width ruled rows, not a row of cards. The earlier rule capped the track at 420px
   with `justify-content: start`, which was written to stop a single offer stretching to the
   whole row — and it did, but the section still measured 1145px wide with one 420px card in
   it: two thirds of the row empty, measured at 1440. A deployment with one plan is the
   normal case, and the plan set is a comparison, so the shape is the reference's own ruled
   list: title, price, what it includes, and the action at the end of the line. */
.plan-grid {
  display: block;
}

.plan-card {
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 220px) minmax(0, 1fr) auto;
  grid-template-areas: "title price points action";
  column-gap: 32px;
  align-items: baseline;
  padding: 24px 0;
  border: 0;
  border-block-start: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  transition: border-color var(--dur-fast) var(--ease);
  position: relative;
  overflow: hidden;
}

.plan-card:last-child {
  border-block-end: 1px solid var(--line);
}

.plan-card > h3 { grid-area: title; }

/* Price and period are one cell and a real stack, not two elements sharing a grid area.
   Sharing an area means both are placed at its origin and drawn over each other: `$0.00`
   is short enough to miss, so the collision only showed on the one card whose price is a
   phrase — "По запросу" wrapped to two lines at 28px and the period, pinned to the bottom
   of the area, landed on top of it (measured 26 September 2026: 33px of overlap at 1440).
   The column is wide enough for that phrase on one line, so the wrapping is gone as well. */
.plan-card > .plan-cost {
  grid-area: price;
  display: grid;
  gap: 2px;
  align-content: baseline;
}
.plan-card > ul { grid-area: points; margin: 0; }
.plan-card > p:last-of-type { grid-area: action; margin: 0; }
.plan-card > .plan-details-btn { grid-area: action; align-self: end; }

.plan-card:hover {
  border-color: var(--line-strong);
}

/* Featured is weight, not a second product: the ok tokens mark the recommended period.
   The fill is gone with the card shape — a tinted band across a full-width row would read
   as a selected row rather than as a recommendation, so the rule that is left is the
   hairline, which is what every other emphasised row on the site uses. */
.plan-card.featured {
  border-color: var(--ok-line);
}

.plan-card.featured:hover {
  border-color: var(--ok);
}

.plan-card h3 {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.012em;
}

.plan-price {
  font-family: var(--font-mono);
  font-size: clamp(22px, 3vw, 28px);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.plan-period {
  font-size: 13px;
  color: var(--text-muted);
}

.plan-card ul {
  display: grid;
  gap: 8px;
  margin: 4px 0 2px;
  padding: 0;
  list-style: none;
}

.plan-card li {
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text-muted);
}

.plan-disclaimer {
  margin-block-start: 14px;
  max-width: 720px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-faint);
}

/* Below the point where four columns cannot hold a line each, the row becomes two: title
   and price on the first line, what the plan includes under them, the action last. */
@media (max-width: 900px) {
  .plan-card {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "title action" "price action" "points points";
    row-gap: 6px;
  }
  .plan-card > .plan-cost { margin-block-start: 2px; }
  .plan-card > ul { margin-block-start: 12px; }
}


/* Plan pop-up ------------------------------------------------------------- */

/* The pricing page's fine print (§14.6 p.6). Opened rarely, so the motion gate
   permits a real entrance: --ease-spring is the curve for surfaces that arrive
   as physical objects, and a dialog is one. Only transform and opacity move. */
.plan-details-btn {
  justify-self: start;
  padding: 6px 2px;
  border: none;
  background: none;
  font-size: 13px;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.plan-details-btn:hover {
  color: var(--text);
}

.plan-details-btn:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

#plan-pop {
  width: min(460px, calc(100vw - 40px));
  padding: 26px 28px 22px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
}

#plan-pop[open] {
  animation: plan-pop-in var(--dur-base) var(--ease-spring);
}

#plan-pop::backdrop {
  background: color-mix(in srgb, var(--bg) 62%, transparent);
}

.plan-pop-close {
  position: absolute;
  top: 14px;
  right: 14px;
}

#plan-pop h3 {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 500;
}

.plan-pop-price {
  margin: 0 0 14px;
  font-size: 15px;
  color: var(--text-muted);
}

.plan-pop-points {
  display: grid;
  gap: 8px;
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
}

.plan-pop-points li {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-muted);
}

#plan-pop .plan-disclaimer {
  margin: 0;
}

@keyframes plan-pop-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.96);
  }
}

@media (prefers-reduced-motion: reduce) {
  #plan-pop[open] {
    animation: none;
  }
}

/* 5. Section rhythm ====================================================== */

/* Sections are ruled chapters, not floating blocks: a hairline above each one is what
   makes a long page read as a document instead of a stack of panels. */
.site-section {
  margin-block-start: clamp(44px, 6vw, 76px);
  padding-block-start: clamp(20px, 2.4vw, 30px);
  border-block-start: 1px solid var(--line);
}

.site-section > h2 {
  font-family: var(--font-display);
  font-size: var(--text-editorial);
  /* 600 and -0.02em: the reference sets its 36px section headings at 600 with -0.54px of
     tracking on 36px, which is -0.015em. The lighter 500 it replaced left section headings
     reading at the same weight as the prose beneath them. */
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: var(--text-editorial-leading);
  margin-block-end: 12px;
}

.site-section > h2 + p {
  max-width: var(--measure-wide);
  margin-block-end: 26px;
  font-size: var(--text-body);
  line-height: var(--text-body-leading);
  color: var(--text-muted);
}

/* 6. Enquiry layout (shared with the old calculator grid) ================ */

.calc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 0.7fr);
  gap: 20px;
  align-items: start;
}

.calc-form {
  display: grid;
  gap: 14px;
  padding: 20px 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

/* The answer, large enough to be the point of the page. */
.calc-result {
  display: grid;
  gap: 10px;
  padding: 22px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  position: sticky;
  inset-block-start: calc(var(--topbar-h) + 18px);
}

.calc-note {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-faint);
}

/* The answer, set as a figure rather than as prose: this is the one number the calculator
   exists to produce, and it has to be readable from across a desk. Tabular figures so it
   does not shift width while a visitor tries a second model. */
.calc-figure {
  font-family: var(--font-mono);
  font-size: clamp(26px, 3.4vw, 34px);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

/* Inside the documentation the calculator lives in a 74ch reading measure, where two
   columns leave neither the form nor the answer enough room. It stacks there, and the
   answer stops being sticky: a pinned panel inside a column of prose reads as a second
   thing to scroll past rather than as the result of the form above it. */
.doc-body .calc {
  grid-template-columns: minmax(0, 1fr);
}

.doc-body .calc-result {
  position: static;
}

/* 7. Documentation ======================================================= */

/* Prose measured for reading: 74 characters is the upper end of comfortable, and the
   panel has no equivalent because it has no prose. */
.doc-layout {
  display: grid;
  gap: 32px;
  align-items: start;
}

.doc-toc {
  display: none;
}

.doc-toc a {
  display: block;
  padding: 6px 0;
  font-size: 13px;
  color: var(--text-muted);
  text-decoration: none;
}

.doc-toc a:hover {
  color: var(--text);
}

.doc-toc a:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

.doc-body h2 {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 500;
  margin-block: 34px 10px;
  scroll-margin-block-start: calc(var(--topbar-h) + 16px);
}

.doc-body h2:first-child {
  margin-block-start: 0;
}

.doc-body {
  max-width: 74ch;
  min-width: 0;
  width: 100%;
  overflow-wrap: anywhere;
}

.doc-body h3 {
  font-size: 15px;
  font-weight: 560;
  margin-block: 24px 8px;
}

.doc-body p,
.doc-body li {
  font-size: 14.5px;
  line-height: 1.68;
  color: var(--text-muted);
}

.doc-body p {
  margin-block-end: 12px;
}

.doc-body ul {
  margin-block-end: 14px;
  padding-inline-start: 20px;
  display: grid;
  gap: 6px;
}

.doc-body li {
  list-style: disc;
}

.doc-body strong {
  color: var(--text);
  font-weight: 560;
}

.doc-body code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 1px 5px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text);
}

/* A copyable snippet. The button sits inside the box rather than above it, so the
   relationship needs no label. */
.snippet {
  position: relative;
  margin-block-end: 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.snippet pre {
  margin: 0;
  padding: 16px 18px;
  /* The one place a horizontal scroll is correct: a shell command must not be wrapped,
     because a wrapped command copied by eye is a broken command. */
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--text);
  white-space: pre;
}

.snippet .copy-btn {
  position: absolute;
  inset-block-start: 8px;
  inset-inline-end: 8px;
}

/* 8. Status ============================================================== */

/* The status verdict is a statement, not a panel: the dot and the sentence carry it, and
   a box around one line was the third surface on a page with nothing else in it. */
.status-hero {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 0 22px;
  border: 0;
  border-block-end: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  margin-block-end: 22px;
}

/* A section here carried four infrastructure rows, each with a hard-coded "Работает" and a
   green dot, while the same screen could print `state: down` with zero models — the
   contradiction a status page exists to prevent, and the exact failure the API test names
   ("a status page that says it works while every request fails"). The gateway is not
   instrumented per component in a way the public API can read: it reports one verdict plus
   two figures, so the page now shows only what it can measure. The rules for those rows
   were removed with them rather than left to style nothing. */

.status-hero .status-dot {
  width: 12px;
  height: 12px;
}

.status-hero strong {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 500;
}

.status-hero p {
  margin-block-start: 4px;
  font-size: 13.5px;
  color: var(--text-muted);
}

/* 8a. Enquiry form ======================================================= */

/* The three steps beside the form. A numbered list rather than three cards: the order is the
   content — nothing here happens before the step above it — and cards would present them as
   independent choices. */
.step-list {
  margin: 0;
  padding-inline-start: 20px;
  display: grid;
  gap: 8px;
}

.step-list li {
  list-style: decimal;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-muted);
}

/* The message field sizes to its rows attribute, and resizing is left to the visitor: a
   fixed height truncates a long enquiry into a scrollbar three lines tall. Vertical only,
   because horizontal resizing breaks out of the form's column. */
#lead-message {
  resize: vertical;
  min-height: 90px;
}

/* 9. Model catalogue ===================================================== */

/* Панель над таблицей: поиск, лаба, порядок и счётчик. Своя строка, а не внутри
   .page-heading, потому что счётчик стоит рядом с полями, а у заголовка нет слота под
   пару. */
.catalogue-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-block-end: 16px;
}

/* Поиск получает вдвое больший запас роста: подстрока — основной способ найти имя, а
   два селектора рядом читаются целиком и в своей ширине. Все три сжимаются до 150px,
   после чего перенос ставит их столбиком — на 390px иначе получается три обрезанных
   поля в одну строку. */
.catalogue-bar .field {
  flex: 1 1 180px;
  min-width: 150px;
}

.catalogue-bar .field:first-child {
  flex: 2 1 240px;
}

.catalogue-count {
  font-size: 12.5px;
  color: var(--text-faint);
  white-space: nowrap;
}

.lab-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 8px;
  margin-block: 14px 20px;
}

.lab-chip {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-muted);
  text-align: start;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), transform var(--dur-instant) var(--ease);
}

.lab-chip:hover,
.lab-chip.active {
  border-color: var(--accent);
  color: var(--text);
}

.lab-chip:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

.lab-chip:active { transform: translateY(1px); }

/* The lab chip carries a vendor mark, which is stroke-drawn (see the base rule): the
   vendored set has no brand logos, so these keep their own drawing. */
.lab-chip svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linejoin: bevel;
}

.lab-chip span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}

.lab-chip b {
  font: 500 11px var(--font-mono);
  color: var(--text-faint);
}


/* 10. Footer ============================================================= */

/* The footer reads as the instrument strip at the end of a document: a row of stated
   facts, then the columns a reader leaves by. Facts are monospace and keyed; links are
   prose. No shadows, no card, one rule above. */
.site-foot {
  border-block-start: 1px solid var(--line);
  background: transparent;
}

.foot-instrument {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: clamp(16px, 4vw, 64px);
  border-block-end: 1px solid var(--line);
}

.foot-cell {
  display: grid;
  gap: 6px;
  padding: 20px 0;
  border-inline-start: 1px solid var(--line);
  padding-inline-start: 18px;
}

.foot-cell:first-child {
  border-inline-start: 0;
  padding-inline-start: 0;
}

.foot-key {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.foot-value {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text);
}

.foot-value code {
  padding: 1px 5px;
  border: 1px solid var(--line);
  font-size: 12px;
  color: var(--text-muted);
}

.site-foot-inner {
  display: grid;
  grid-template-columns: minmax(240px, 1.6fr) repeat(3, minmax(130px, 1fr));
  gap: 28px;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: 32px clamp(16px, 4vw, 64px) 40px;
}

.site-foot-brand {
  display: grid;
  gap: 10px;
  max-width: 420px;
  align-content: start;
}

.site-foot-brand p {
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-muted);
}

.site-foot-column {
  display: grid;
  gap: 10px;
  align-content: start;
}

.site-foot-column a {
  font-size: 13.5px;
  color: var(--text-muted);
  text-decoration: none;
  width: fit-content;
}

.site-foot-column a:hover {
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.site-foot-column a:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

@media (max-width: 900px) {
  .site-foot-inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
  }

  .site-foot-brand {
    grid-column: 1 / -1;
  }
}

/* 11. Narrow widths ====================================================== */

@media (min-width: 901px) {
  .doc-layout {
    grid-template-columns: minmax(140px, 180px) minmax(0, 1fr);
  }

  .doc-toc {
    display: grid;
    gap: 2px;
    position: sticky;
    inset-block-start: calc(var(--topbar-h) + 16px);
  }
}

/* 900px is where the two-column layouts stop having room for the second column, not a
   device: the same breakpoint the panel uses for the same reason. */
@media (max-width: 900px) {
  .hero,
  .calc {
    grid-template-columns: minmax(0, 1fr);
  }


  .calc-result {
    position: static;
  }
}

/* Below 800px the horizontal nav no longer fits beside the lockup and the actions. It
   becomes a panel the burger opens, positioned under the header rather than over the
   page: a full-screen overlay for five links is a heavier gesture than the content. */
@media (max-width: 800px) {
  .nav-toggle {
    display: inline-flex;
  }

  .site-nav {
    position: absolute;
    inset-inline: 0;
    inset-block-start: 100%;
    display: none;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 10px clamp(16px, 4vw, 40px) 14px;
    border-block-end: 1px solid var(--line);
    background: var(--bg);
  }

  .site-nav.open {
    display: flex;
  }

  .site-head-inner {
    position: relative;
  }

  .site-nav a {
    padding: 10px 12px;
    font-size: 14.5px;
  }

  /* The theme toggle and the status chip stay; the chip's text is the shortest of the
     three states, so it survives the narrowest width without truncation. */
  .site-head-actions {
    margin-inline-start: auto;
  }
}

/* Below 520px the header runs out of room, and it overflowed rather than adapting: five
   controls plus the lockup measured 441px inside a 420px viewport, which gave the whole
   document a horizontal scrollbar — on every page, because the header is on every page.

   The status chip is what goes first: the nav has become a dropdown and its «Статус»
   entry leads to the same verdict, while the lockup is the product's own name — with
   the chip gone the wordmark fits again at 390px and the first viewport names the
   product instead of only drawing its mark. */
@media (max-width: 520px) {
  .site-head-inner {
    gap: 12px;
    flex-wrap: wrap;
  }

  .site-head-actions {
    gap: 6px;
  }

  /* The status chip goes, not the wordmark: at 390px the five controls measured 355px
     inside 358px of free width, and the document scrolled sideways by 25px on every
     page. The chip is the one control that is redundant here rather than merely nice —
     the nav has become a dropdown, and its «Статус» entry leads to the same verdict in
     full words. The alternative — keeping the chip and hiding its text — would leave a
     coloured dot as the only marker of a three-state answer, which is exactly what the
     design system forbids. Same trade the portal makes with its balance chip below
     700px. */
  .status-chip {
    display: none;
  }

  .catalogue-bar {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: end;
  }

  .catalogue-bar .field {
    min-width: 0;
    margin-bottom: 0;
  }

  .catalogue-bar .field:first-child {
    grid-column: 1 / -1;
  }

  .catalogue-count {
    justify-self: end;
    align-self: center;
  }

}

/* 12. Motion ============================================================= */

/* Everything in this section is gated on `.motion`, which public.js sets on <html> before
   the first page renders. Without it the storefront is the document it was: a reveal that
   starts an element at `opacity: 0` and waits for a script to reveal it would leave a
   scripting-blocked visitor on a blank page. The class is the guard, not a feature flag —
   nothing here checks it twice.

   The frequency table in `.cursor/rules/interface-motion.mdc` decides what may move at all.
   A storefront section is read once per visit, which is the row the budget lives in; the
   header, which is on every page and always visible, gets nothing but a one-pixel progress
   line whose purpose is stated below. */

/* Reveal on entry. Purpose: explanation — a page of six sibling cards gives no reading
   order, and arriving in sequence says which comes first. Only `opacity` and `transform`,
   so a reveal is a compositor job and a section entering costs no layout.

   The pre-state belongs to the element the observer actually watches. `armReveal` skips a
   node whose ancestor already matches the selector, so a card inside a section never gets
   `data-shown` of its own — the markup's `data-reveal` was enough to hide it forever. The
   children therefore key off the section's own attribute: the section arrives, and the
   cards stagger inside it. */
.motion [data-reveal] {
  opacity: 0;
  transform: translateY(12px);
}

.motion [data-reveal][data-shown] {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--dur-reveal) var(--ease) var(--reveal-delay, 0ms),
    transform var(--dur-reveal) var(--ease) var(--reveal-delay, 0ms);
}

/* A nested reveal has no observer of its own: it is shown by its section, and it slides a
   shorter distance because it is already inside a moving group. */
.motion [data-reveal] [data-reveal] {
  transform: translateY(8px);
}

.motion [data-reveal][data-shown] [data-reveal] {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--dur-reveal) var(--ease) var(--reveal-delay, 0ms),
    transform var(--dur-reveal) var(--ease) var(--reveal-delay, 0ms);
}

/* The heading's two lines are blocks whether or not anything animates: the markup carries a
   pair of spans instead of the <br> it used to, and a pair of inline spans would render as
   one line. Only the mask is conditional. */
.hero-copy h1 .line {
  display: block;
}

/* The hero heading, once per visit, per line. Each line is masked by its own box and rises
   into it: the words arrive as one object per line rather than as a block fading in, which
   is the difference between a page that starts and a page that appears.

   `overflow: clip` rather than `hidden`, because `hidden` would make each line a scroll
   container and clip the descender of «у» a pixel early. */
.motion .hero-copy h1 .line {
  overflow: clip;
}

.motion .hero-copy h1 .line > span {
  display: block;
  animation: line-rise var(--dur-reveal) var(--ease) both;
  animation-delay: var(--reveal-delay, 0ms);
}

@keyframes line-rise {
  from {
    opacity: 0;
    /* Its own height, so the line starts exactly outside its mask and no distance has to be
       guessed against a clamped font size. */
    transform: translateY(100%);
  }
}

/* The scroll line. Purpose: state — a storefront page is between two and five screens tall,
   and how much of one is left is otherwise unknowable without dragging the scrollbar. One
   pixel, no colour of its own, and driven by a transform: it is the cheapest honest answer.
   `--scroll` is written by public.js on <html> from one rAF-throttled listener. */
.scroll-line {
  position: absolute;
  inset-block-end: -1px;
  inset-inline: 0;
  height: 1px;
  background: var(--accent);
  opacity: 0.5;
  transform: scaleX(var(--scroll, 0));
  transform-origin: 0 50%;
  pointer-events: none;
}

/* Hover on a card. Purpose: response — a card that is a link target and a card that is not
   look identical at rest, and a border that sharpens under the pointer is the whole of
   the difference a visitor needs. No lift: depth is surface and line, not shadow
   (design-system §3), and these cards sit in flow.

   The sheen is the same trick the reference uses: a gradient large enough to travel inside
   its own box, masked to the border ring so only the outline lights, and paused until the
   pointer is on the card. A paused animation costs nothing; a running one costs a
   background-position interpolation on one composited element. */
.info-card,
.plan-card,
.tariff-card {
  position: relative;
  transition: border-color var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .info-card:hover,
  .plan-card:hover,
  .tariff-card:hover {
    border-color: var(--line-strong);
  }
}

@keyframes card-sheen {
  from {
    background-position: 15% 15%;
  }
  to {
    background-position: 85% 85%;
  }
}

/* The trailing icon on a call to action. Purpose: response, and it is the arrow's own
   direction — two pixels along the axis it points. The leading icon on `.btn.secondary`
   is not moved: an icon that labels does not travel. */
@media (hover: hover) {
  .site-main .btn > svg:last-child {
    transition: transform var(--dur-fast) var(--ease);
  }

  .site-main .btn:hover > svg:last-child {
    transform: translateX(2px);
  }
}



/* Section headings stay while their section passes. Purpose: orientation — the storefront's
   long pages (docs, models) scroll past the heading that says what is being read.
   Translucent rather than solid so the content does not appear to end at a line, matching
   the header's own treatment; the fallback background is opaque enough to read against
   where `backdrop-filter` is missing.

   The offset is the header's height plus a hairline, not less: the header is `position:
   sticky` at `z-index: 60` and this is at 2, so a heading pinned any higher is a heading
   drawn underneath the nav. Measured — at `--topbar-h` minus 24px the pinned heading sat
   23px inside the header's box and its cap line was hidden.

   Only from 901px. Below that the header is already 62px of a short viewport, and a second
   pinned band would leave a phone reading through a slot. */
@media (min-width: 901px) {
  .site-section > h2 {
    position: sticky;
    inset-block-start: calc(var(--topbar-h) + 1px);
    z-index: 2;
    padding-block: 6px;
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: blur(10px);
  }
}

/* The footer wordmark. Decoration, on the one surface where the page has already ended:
   nothing below it is read, and a visitor who reached it has finished. Clipped by the
   footer, never selectable, never announced. */
.site-foot {
  position: relative;
  overflow: hidden;
}

.foot-ghost {
  position: absolute;
  inset-block-end: -0.24em;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: 0;
  font-family: var(--font-display);
  font-size: clamp(56px, 12vw, 160px);
  font-weight: 500;
  line-height: 0.78;
  letter-spacing: -0.04em;
  color: var(--text);
  opacity: 0.035;
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
}

.site-foot-inner {
  position: relative;
  z-index: 1;
}

/* Route change. Purpose: spatial coherence — the storefront replaces `#page` wholesale, and
   a cross-fade of a quarter of a second says the address changed rather than that the
   document blinked. The panel deliberately has none of this (its section switch is the
   operator's most frequent action); a visitor changes storefront page a handful of times.

   Named on the two boxes that actually differ. Without a name the whole root cross-fades,
   which would fade the header and the footer as well — they did not change. */
.site-main {
  view-transition-name: page;
}

::view-transition-old(page),
::view-transition-new(page) {
  animation-duration: var(--dur-base);
  animation-timing-function: var(--ease);
}

::view-transition-old(page) {
  animation-name: vt-out;
}

::view-transition-new(page) {
  animation-name: vt-in;
}

@keyframes vt-out {
  to {
    opacity: 0;
    transform: translateY(-6px);
  }
}

@keyframes vt-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* Reduced motion. The blanket rule in styles.css already collapses every duration here, so
   this block only handles the two cases where collapsing is not enough: a reveal that never
   arrives and a sheen frozen mid-travel.

   The reveal is not merely instant — the pre-state is dropped entirely, so an element is
   visible even if its observer never fires. The lift on hover stays: it is two pixels under
   the pointer, not a surface crossing the viewport, and losing it would cost the one signal
   that says a card is interactive. */
@media (prefers-reduced-motion: reduce) {
  .motion [data-reveal] {
    opacity: 1;
    transform: none;
  }

  .motion .hero-copy h1 .line > span {
    animation: none !important;
  }

  .info-card::after,
  .plan-card::after,
  .tariff-card::after {
    display: none;
  }
}

/* 13. Free key check ===================================================== */

/* The security promises beside the title. Three badges that reassure the visitor
   before they paste a credential. Zero-retention is the critical one: we do not store
   the key in any database or request log. */
.check-security-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-block-start: 16px;
}

.check-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  font-size: 12px;
  color: var(--text-muted);
}

.check-pill svg {
  width: 14px;
  height: 14px;
  fill: var(--accent);
  stroke: none;
  flex: none;
}

/* The form is a column, not a grid: three fields where each hint is a sentence, and a
   two-column layout would put a hint beside the wrong input on the first narrow width. */
.check-form {
  display: grid;
  gap: 18px;
  max-width: 620px;
  padding: 22px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.check-sample-models {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-block-start: 6px;
}

.sample-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}

.sample-chip:hover {
  border-color: var(--accent);
  color: var(--text);
  background: var(--surface-3);
}

.sample-chip:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

.check-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.check-note {
  font-size: 12.5px;
}

.check-output {
  margin-block-start: 24px;
  max-width: 720px;
}

.check-progress {
  display: grid;
  gap: 10px;
  padding: 18px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.check-progress .meter-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 560;
  color: var(--text);
}

.meter-pulse {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--ok);
  animation: public-health-pulse 1.8s ease-in-out infinite;
  flex: none;
}

.check-progress-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12.5px;
}

.check-error-count {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--warn);
}

.check-report {
  display: grid;
  gap: 16px;
  padding: 22px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

/* The verdict carries its own border colour and nothing else. No filled banner: a red
   block over somebody else's vendor reads as an accusation, and what we have is one run of
   observations (docs/upstream-audit.md, «Находка, а не приговор»). */
.check-verdict {
  display: grid;
  gap: 6px;
  padding-inline-start: 14px;
  border-inline-start: 2px solid var(--line-strong);
}

.check-verdict-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.check-verdict strong,
.check-verdict-badge {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  color: var(--text);
}

.check-verdict p,
.check-verdict-note {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-muted);
  margin: 0;
}

.check-verdict[data-verdict="pass"] {
  border-inline-start-color: var(--ok);
}

.check-verdict[data-verdict="risk"] {
  border-inline-start-color: var(--warn);
}

.check-verdict[data-verdict="fail"] {
  border-inline-start-color: var(--danger);
}

/* An error and "no conclusion" share a colour on purpose: both mean we have nothing to
   report, and giving the failure a danger colour would suggest a finding about the
   vendor. */
.check-verdict[data-verdict="error"],
.check-verdict[data-verdict="inconclusive"],
.check-verdict[data-verdict="unknown"] {
  border-inline-start-color: var(--line-strong);
}

.check-findings-block {
  display: grid;
  gap: 12px;
}

.check-findings-head {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
  margin: 0;
}

.check-findings {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.check-findings li {
  display: grid;
  gap: 3px;
  padding: 11px 0;
  border-block-start: 1px solid var(--line-soft);
}

.check-finding-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.check-severity-tag {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px 7px;
  border-radius: var(--radius-xs);
  font: 600 10px/1.2 var(--font-mono);
  letter-spacing: 0.04em;
}

.check-severity-tag.tag-fail {
  background: var(--danger-soft);
  color: var(--danger);
  border: 1px solid var(--danger-line);
}

.check-severity-tag.tag-warn {
  background: var(--warn-soft);
  color: var(--warn);
  border: 1px solid var(--warn-line);
}

.check-severity-tag.tag-info {
  background: var(--neutral-soft);
  color: var(--text-muted);
  border: 1px solid var(--neutral-line);
}

.check-finding-title {
  font-size: 13.5px;
  font-weight: 560;
  color: var(--text);
}

.check-finding-detail {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
  /* A vendor's own text, which regularly arrives as one unbroken line. */
  overflow-wrap: anywhere;
}

/* Severity colours the title, and it is never the only marker: the finding's own words say
   what happened, which is what the four accessibility signals require (docs/design-system.md
   §8). A coloured row would also read as a verdict about the vendor rather than as one
   observation among several. */

.check-findings li[data-severity="fail"] .check-finding-title {
  color: var(--danger);
}

.check-findings li[data-severity="warn"] .check-finding-title {
  color: var(--warn);
}

.check-postcorp-hint {
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 13px;
  line-height: 1.55;
}

.check-postcorp-hint strong {
  display: block;
  margin-block-end: 4px;
  color: var(--text);
  font-weight: 560;
}

.check-postcorp-hint p {
  margin: 0;
  color: var(--text-muted);
}

.check-disclaimer {
  font-size: 12.5px;
  line-height: 1.55;
}

.check-report-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

/* 14. Физика и оптика ===================================================== */
/* Раздел закреплён `docs/design-system.md` §10 и `SYSTEM_RULES.md` D-8. До него витрина
   не имела ни одного `:active`: отклик появлялся на `click`, то есть после отпускания,
   и нажатие читалось как «ничего не произошло». */

/* §10.1 — отклик возникает на нажатии. Сдвиг на один пиксель и смена поверхности
   вместо масштабирования: карточки витрины крупные, и уменьшение целой карточки на
   касание читается как «уехала», а не как «нажата». */
.site-nav a:active,
.doc-toc a:active,
.status-chip:active,
.plan-details-btn:active,
.sample-chip:active,
.check-pill:active {
  background: var(--surface-3);
  transform: translateY(1px);
  transition-duration: var(--dur-instant);
}

.info-card:active,
.plan-card:active,
.tariff-card:active,
.lab-chip:active {
  transform: translateY(1px);
  transition-duration: var(--dur-instant);
}


/* §10.5 — граница плавающей обвязки с содержимым: затухание вместо линии в 1px. Линия
   разрезала страницу там, где ничего не заканчивается; маска показывает, что содержимое
   уходит под материал. Линия возвращается ниже — там, где материал становится плотным
   и затухание перестаёт читаться. */
.site-head {
  border-block-end: 0;
  box-shadow: 0 1px 0 0 color-mix(in srgb, var(--line-soft) 60%, transparent),
    var(--shadow-md);
}

/* §10.7 — плотный материал, когда пользователь просил меньше прозрачности. Размытие
   снимается целиком: полупрозрачный слой без размытия — это просто мутный фон. */
@media (prefers-reduced-transparency: reduce) {
  .site-head {
    background: var(--bg);
    backdrop-filter: none;
    border-block-end: 1px solid var(--line);
    box-shadow: none;
  }

  .plan-card,
  .info-card,
  .tariff-card {
    backdrop-filter: none;
  }
}

/* §10.7 — больше контраста: почти сплошной фон и явная граница вместо мягких линий,
   потому что мягкая линия при повышенном контрасте не читается вовсе. */
@media (prefers-contrast: more) {
  .site-head {
    background: var(--bg);
    backdrop-filter: none;
    border-block-end: 1px solid var(--line-strong);
    box-shadow: none;
  }

  .plan-card,
  .info-card,
  .tariff-card,
  .status-chip,
  .plan-details-btn,
  .sample-chip,
  .check-pill {
    border-color: var(--line-strong);
  }

  .site-nav a[aria-current="page"] {
    outline: 1px solid var(--line-strong);
    outline-offset: -1px;
  }
}


/* §10.1 + §10.7 — уменьшенное движение не отменяет отклик: перелёты и сдвиги убираются,
   смена поверхности остаётся, иначе нажатие снова становится незаметным. */
@media (prefers-reduced-motion: reduce) {
  .meter-pulse {
    animation: none;
  }

  .site-nav a:active,
    .doc-toc a:active,
  .status-chip:active,
  .plan-details-btn:active,
  .sample-chip:active,
  .check-pill:active,
  .lab-chip:active,
  .info-card:active,
  .plan-card:active,
  .tariff-card:active {
    transform: none;
  }

  /* The storefront has no hero-star producer; reduced motion only keeps real controls calm. */

  /* Карточки двигались на один пиксель и больше ничем не отвечали, поэтому без движения
     нажатие переставало читаться вовсе. Поверхность и линия — отклик, который не двигает
     ничего на экране. */
  .info-card:active,
  .plan-card:active,
  .tariff-card:active {
    background: var(--surface-2);
    border-color: var(--line-strong);
  }
}
