/* =============================================================================
   Demo landing template — styles
   Mobile-first. All colour, radius and typography comes from CSS custom
   properties that app.js sets from config.js. Four presets are expressed as
   body classes (.preset-*), never as separate stylesheets.
   ========================================================================== */

/* ---- Reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
  -webkit-text-size-adjust: 100%; scroll-behavior: smooth;
  /* Anchor targets must clear the sticky header. */
  scroll-padding-top: 84px;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
img, svg { display: block; max-width: 100%; height: auto; }
/*
 * `hidden` beats any display we set. app.js hides an element by setting the
 * attribute, and every rule here that sets `display` silently overrode it —
 * a lead with no hero badge still rendered the empty 34x21 chip, and the same
 * was true of any hidden element with a display rule. Two one-off patches
 * existed (.hero__visual[hidden], .contacts__map[hidden]); this closes the class.
 */
[hidden] { display: none !important; }
button, input, textarea { font: inherit; color: inherit; }
ul, ol, dl { list-style: none; padding: 0; }
a { color: inherit; text-decoration: none; }

/* ---- Tokens ------------------------------------------------------------ */
:root {
  /* One scheme, declared. Pairs with <meta name="color-scheme" content="light only">.
     `only light` also opts the UA form controls out of automatic darkening — a
     select or an input that inherits the UA surface is the first thing an in-app
     browser inverts. */
  color-scheme: only light;
  --ink:        #17130F;
  --ink-2:      #5D554E;
  /* Was #8C837B: 3.60:1 on --bg and 3.33:1 on --tint, an AA failure for every
     .fineprint, field label, service note and <dt> on any demo without a
     theme override. Caught by the rendered-contrast check, not by eye.
     #756D66 keeps the warm grey and clears 4.5:1 on all three grounds. */
  --ink-3:      #756D66;
  --bg:         #FCFBF9;
  --surface:    #FFFFFF;
  --tint:       #F5F2EF;
  --line:       rgba(23, 19, 15, 0.10);
  --line-2:     rgba(23, 19, 15, 0.16);

  /* Overwritten by app.js from config.design.
     Three accent roles, and they are not interchangeable:
       --accent       the brand hex itself. FILLS ONLY — something opaque with
                      a checked contrast (--accent-ink) sits on top of it.
                      A brand colour rarely clears AA as text on its own ground.
       --accent-text  the generated tone that clears 4.5:1 on this page's
                      ground. Every accent-coloured TEXT node uses this.
       --accent-line  the generated tone that clears 3:1. Borders, focus rings
                      and icon glyphs — WCAG's UI-component threshold.
     Defaults below match the default accent so the stylesheet is valid on its
     own; app.js replaces all three from the generated tokens. */
  --accent:      #1F7A8C;
  --accent-text: #1F7A8C;
  --accent-line: #1F7A8C;
  --accent-ink:  #FFFFFF;
  --accent-08:  rgba(31, 122, 140, 0.08);
  --accent-16:  rgba(31, 122, 140, 0.16);
  --grad:       var(--accent);

  --radius:     18px;
  --radius-sm:  12px;
  --radius-pill: 999px;

  --font-display: 'Inter', system-ui, sans-serif;
  --font-body:    'Inter', system-ui, sans-serif;

  --shell:      1180px;
  --gutter:     clamp(20px, 5vw, 40px);
  --section-y:  clamp(64px, 11vw, 132px);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --shadow-sm: 0 1px 2px rgba(23,19,15,.04), 0 4px 14px rgba(23,19,15,.05);
  --shadow-md: 0 2px 6px rgba(23,19,15,.05), 0 18px 44px rgba(23,19,15,.09);
}

/* ---- Radius scales ------------------------------------------------------
   Two, and only two. `soft` repeats the :root values on purpose: a config
   saying `radius: 'soft'` must correspond to a rule that exists, or the field
   is describing behaviour the code does not have. app.js rejects any other
   value. There is no `pill` scale — it was never designed, so it is not
   offered. Add one here first if a lead ever needs it.
   ------------------------------------------------------------------------ */
/* FROZEN AT G1 (brief 2026-09-05 §8): two scales, and only two. The brief
   expected `soft | pill` with no CSS behind them; what exists is `soft | sharp`,
   both implemented here and both used by a live demo. There is no `pill` value
   anywhere — only a --radius-pill token, which both scales define. Do not build
   a third scale. tests/config.test.mjs holds app.js's allow-list to whatever
   this file implements. */
body.radius-soft {
  --radius: 18px; --radius-sm: 12px; --radius-pill: 999px;
}
body.radius-sharp {
  --radius: 3px; --radius-sm: 2px; --radius-pill: 3px;
}

/* ---- Base -------------------------------------------------------------- */
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
/* Hide the unfilled shell for the single frame before config is applied. */
body.is-booting { opacity: 0; }
body { opacity: 1; transition: opacity .25s var(--ease); }

::selection { background: var(--accent-16); }

:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: #fff; padding: 12px 18px;
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---- Typography -------------------------------------------------------- */
h1, h2, h3 { font-family: var(--font-display); font-weight: 600; line-height: 1.1; letter-spacing: -0.02em; }
.section__title { font-size: clamp(28px, 5.2vw, 46px); }
.hero__title    { font-size: clamp(34px, 7.6vw, 68px); text-wrap: balance; }

.eyebrow {
  font-size: 13px; font-weight: 600; letter-spacing: .09em;
  text-transform: uppercase; color: var(--accent-text); margin-bottom: 14px;
}
.section__lead {
  color: var(--ink-2); font-size: clamp(16px, 2.2vw, 19px);
  max-width: 56ch; margin-top: 16px;
}
.fineprint { font-size: 13.5px; line-height: 1.55; color: var(--ink-3); }

/* ---- Buttons ----------------------------------------------------------- */
.btn {
  --btn-bg: transparent; --btn-fg: var(--ink); --btn-bd: var(--line-2);
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 13px 22px; min-height: 48px;
  background: var(--btn-bg); color: var(--btn-fg);
  border: 1px solid var(--btn-bd); border-radius: var(--radius-pill);
  font-weight: 600; font-size: 15.5px; letter-spacing: -0.005em;
  cursor: pointer; max-width: 100%; text-align: center;
  /* Never nowrap: a long Russian CTA label would otherwise size the grid
     column and push the entire hero off a 390px screen. */
  white-space: normal; text-wrap: balance;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn--primary {
  --btn-bg: var(--grad); --btn-fg: var(--accent-ink); --btn-bd: transparent;
  box-shadow: var(--shadow-sm);
}
.btn--primary:hover { box-shadow: var(--shadow-md); }
.btn--quiet { --btn-bg: var(--surface); }
.btn--quiet:hover { --btn-bd: var(--line-2); background: var(--tint); }
.btn--ghost { --btn-bd: transparent; padding-inline: 12px; }
.btn--ghost:hover { background: var(--tint); }
.btn--lg { min-height: 56px; padding: 16px 30px; font-size: 16.5px; }
@media (max-width: 560px) { .btn--lg { width: 100%; } .hero__actions { display: grid; } }
.btn--block { width: 100%; }

/* ---- Demo notice -------------------------------------------------------
   A demo carries a real clinic's name, address and phone. It says so, in the
   page, in Russian, above everything else.

   In flow, never fixed: a fixed strip would cover the hero headline at 390 and
   collide with the fixed action bar at the bottom. On --tint with ink text and
   one hairline, never on the accent — the CTA stays the only filled accent
   element (lp-design §4). No icon, no emoji, no animation.
   ---------------------------------------------------------------------- */
.demobanner {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 20px; background: var(--tint); color: var(--ink);
  border-bottom: 1px solid var(--line);
}
.demobanner__text {
  flex: 1; margin: 0; font-family: var(--font-body);
  font-size: 14px; line-height: 1.45; font-weight: 400;
}
.demobanner__close {
  flex: none; width: 48px; height: 48px; margin: -12px -8px -12px 0;
  display: grid; place-items: center;
  background: none; border: 0; cursor: pointer;
  color: var(--ink-2); font-size: 22px; line-height: 1;
}
.demobanner__close:hover { color: var(--ink); }
@media (min-width: 900px) {
  .demobanner { padding-inline: 40px; }
  .demobanner__text { font-size: 14.5px; }
}

/* ---- Header ------------------------------------------------------------ */
.site-header {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.site-header.is-stuck { border-bottom-color: var(--line); }
.site-header__inner { display: flex; align-items: center; gap: 16px; min-height: 68px; }

.brand { display: inline-flex; align-items: center; gap: 11px; margin-right: auto; }
.brand__mark {
  width: 34px; height: 34px; flex: none; border-radius: var(--radius-pill);
  background: var(--grad);
  display: grid; place-items: center;
  color: var(--accent-ink); font-weight: 700; font-size: 15px;
  font-family: var(--font-display); letter-spacing: 0;
}
.brand__text { display: flex; flex-direction: column; line-height: 1.15; }
.brand__name { font-family: var(--font-display); font-weight: 600; font-size: 18px; letter-spacing: -0.01em; }
.brand__suffix { font-size: 11.5px; color: var(--ink-3); letter-spacing: .05em; text-transform: lowercase; }

.site-nav { display: none; gap: 4px; }
.site-nav a { padding: 9px 13px; border-radius: var(--radius-pill); font-size: 15px; color: var(--ink-2); }
.site-nav a:hover { background: var(--tint); color: var(--ink); }
.site-header__phone { display: none; }

@media (min-width: 900px) {
  .site-nav { display: flex; }
  .site-header__phone { display: inline-flex; }
}

/* ---- Sections ---------------------------------------------------------- */
.sections { display: flex; flex-direction: column; }
.section { padding-block: var(--section-y); }
.section--tint { background: var(--tint); }
.section__head { margin-bottom: clamp(32px, 5vw, 56px); max-width: 62ch; }

/* ---- Hero -------------------------------------------------------------- */
.hero { padding-top: clamp(44px, 8vw, 88px); position: relative; overflow: hidden; }
.hero__inner { display: grid; gap: clamp(32px, 5vw, 56px); align-items: center; }
.hero__badge {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 15px 7px 12px; border-radius: var(--radius-pill);
  background: var(--accent-08); color: var(--accent-text);
  font-size: 14px; font-weight: 600; margin-bottom: 22px;
}
.hero__badge::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); flex: none;
}
.hero__subtitle {
  margin-top: 22px; font-size: clamp(17px, 2.4vw, 21px);
  color: var(--ink-2); max-width: 52ch; line-height: 1.55;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }

.hero__visual {
  position: relative; border-radius: var(--radius);
  min-height: 260px; overflow: hidden;
  background: var(--grad);
}
.hero__visual::after {
  /* Abstract, brand-toned depth. No stock photography. */
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(120% 90% at 15% 10%, rgba(255,255,255,.34), transparent 55%),
    radial-gradient(80% 70% at 95% 100%, rgba(0,0,0,.20), transparent 60%);
}
.hero__visual--photo { background: var(--tint); }
.hero__visual--photo img { width: 100%; height: 100%; object-fit: cover; }

/* Stats strip */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px 28px; margin-top: clamp(36px, 5vw, 52px);
  padding-top: clamp(26px, 4vw, 34px); border-top: 1px solid var(--line);
}
.stats li { display: flex; flex-direction: column; gap: 4px; }
.stats b {
  font-family: var(--font-display); font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; letter-spacing: -0.02em;
}
.stats span { font-size: 14px; color: var(--ink-3); line-height: 1.4; }
.stats:empty { display: none; }

/* ---- Hero archetypes -----------------------------------------------------
   One hero used seven times is what made seven demos read as one template.
   These are four different first screens over the same DOM, selected by
   `hero.archetype`. Composition only — palette, type and rhythm stay in each
   lead's own theme.css, so the two axes are independent.
   -------------------------------------------------------------------------- */

/* statement — type carries the screen; no visual, because a placeholder tile is
   a dead area rather than a picture, and three of our leads have nothing else. */
[data-archetype="statement"] .hero__inner { grid-template-columns: 1fr; }
[data-archetype="statement"] .hero__title { max-width: 18ch; }
[data-archetype="statement"] .hero__subtitle { max-width: 52ch; }
@media (min-width: 900px) {
  [data-archetype="statement"] .hero { padding-block: clamp(64px, 10vw, 132px) clamp(34px, 5vw, 60px); }
}

/* fact — the decisive fact sits ABOVE the headline at display scale. The h1
   stays the h1; the fact is a <p>, so the document outline is unchanged and a
   screen reader still meets one heading. */
.hero__fact { display: flex; flex-direction: column; gap: 4px; margin: 0 0 18px; }
.hero__fact-value {
  font-family: var(--font-display);
  font-size: clamp(38px, 8.4vw, 96px);
  font-weight: 800; line-height: .94; letter-spacing: -0.04em;
}
.hero__fact-label { font-size: 15px; color: var(--ink-3); line-height: 1.4; }
[data-archetype="fact"] .hero__inner { grid-template-columns: 1fr; }
/* The headline is quieter than the fact above it — one focal element per
   viewport (§4), and here the fact is it — but it is NOT allowed to fall below
   the §1 floors while doing that. The first version dropped the h1 to 34px/600
   and the computed audit failed both the scale jump (2x against a 17px body,
   floor 3x) and the weight extreme (600 against an h2 of 300, floor 400).
   Hierarchy between the fact and the headline is made by the fact being larger
   still, not by shrinking the heading under the rule. */
[data-archetype="fact"] .hero__title {
  font-size: clamp(30px, 4.4vw, 56px); font-weight: 700; letter-spacing: -0.02em; max-width: 24ch;
}

/* directory — addresses above the fold for a network. */
.hero__branches {
  list-style: none; margin: 26px 0 0; padding: 0;
  display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.hero__branch {
  display: flex; flex-direction: column; gap: 3px;
  padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface);
}
.hero__branch-name { font-size: 17px; font-weight: 700; letter-spacing: -0.015em; }
.hero__branch-hours { font-size: 14px; color: var(--ink-3); }
/* 48px minimum touch target, carried by the row itself (§4 Mobile). */
.hero__branch-phone { font-size: 15px; min-height: 48px; display: flex; align-items: center; }
[data-archetype="directory"] .hero__inner { grid-template-columns: 1fr; }
[data-archetype="directory"] .hero__title { max-width: 20ch; }

/* regalia — what they earned sits directly under the headline, and the strip is
   the second thing read rather than the last thing in the hero. For the one
   lead in the dossier that holds an award AND twenty years. */
[data-archetype="regalia"] .hero__inner { grid-template-columns: 1fr; }
[data-archetype="regalia"] .hero__title { max-width: 20ch; }
[data-archetype="regalia"] .hero .regalia {
  margin-top: clamp(22px, 3vw, 32px);
  padding-top: clamp(20px, 2.6vw, 26px);
  border-top-width: 2px;
}
[data-archetype="regalia"] .hero .regalia__item { font-size: 16px; }
[data-archetype="regalia"] .hero .regalia__item--lead { font-size: 18px; }
/* The strip is the second thing read, so it closes the hero rather than floating
   in the middle of a gap: the hero's own bottom padding comes off and the next
   section supplies the space. */
[data-archetype="regalia"] .hero { padding-bottom: clamp(20px, 2.4vw, 28px); }

/* offer — a promotion with a condition. The deadline is a rule-separated line
   above the headline rather than a soft pill, because the condition is what
   makes the offer honest and it must not read as decoration. */
[data-archetype="offer"] .hero__badge {
  font-size: clamp(15px, 1.6vw, 18px); font-weight: 700;
  padding: 0 0 12px; margin-bottom: 14px; background: none; border: 0;
  border-bottom: 2px solid var(--accent-line); border-radius: 0;
  color: var(--accent-text); align-self: start;
}
[data-archetype="offer"] .hero__badge::before { display: none; }
[data-archetype="offer"] .hero__title { max-width: 16ch; }
@media (min-width: 900px) {
  [data-archetype="offer"] .hero__inner { grid-template-columns: 1.25fr 0.75fr; align-items: start; }
}

/* proof — the number IS a column. Distinct from `fact`, which stacks the number
   above the headline across the full measure; here it stands beside the copy so
   the eye reads the count first and the sentence second. */
@media (min-width: 820px) {
  [data-archetype="proof"] .hero__inner { grid-template-columns: minmax(0, 0.42fr) minmax(0, 0.58fr); align-items: center; }
  [data-archetype="proof"] .hero__fact { margin: 0; grid-column: 1; }
  [data-archetype="proof"] .hero__body { display: contents; }
  [data-archetype="proof"] .hero__title,
  [data-archetype="proof"] .hero__subtitle,
  [data-archetype="proof"] .hero__actions,
  [data-archetype="proof"] .hero__badge { grid-column: 2; }
}
[data-archetype="proof"] .hero__fact-value { font-size: clamp(56px, 12vw, 132px); }
/* Same floors as `fact` above: the number leads because it is 132px, not
   because the headline was pushed under the audit's thresholds. */
[data-archetype="proof"] .hero__title { font-size: clamp(28px, 3.8vw, 54px); font-weight: 800; letter-spacing: -0.025em; }

/* ---- Hero image treatments -----------------------------------------------
   Every reference the operator gave leads with a photograph. Ours led with type
   on colour, which reads as unfinished on the one screen where an owner decides
   — and the marked blank is the strongest reply-hook we have, because filling it
   means writing to us.

   The marker is not optional and not subtle: docs/imagery-policy.md requires
   every instance to be visibly temporary, in the page's own language.
   -------------------------------------------------------------------------- */

/* beside — an image column next to the type. */
[data-hero-image="beside"] .hero__visual { min-height: clamp(260px, 34vw, 460px); }

/* band — full width, directly under the type. For a page that is short and
   confident on purpose: the image gives it a floor rather than a column. */
[data-hero-image="band"] .hero__inner { grid-template-columns: 1fr; }
[data-hero-image="band"] .hero__visual {
  grid-column: 1 / -1; width: 100%;
  min-height: clamp(180px, 22vw, 300px); margin-top: clamp(26px, 3.4vw, 40px);
}

/* bleed — the image runs to the edges of the section and the type overlaps its
   plane.

   The TYPE STILL SITS ON SOLID GROUND, deliberately. Text laid directly over a
   photograph is the one place tests/audit.mjs goes blind — it measures painted
   contrast from computed style and reports "sits on a gradient or image and
   cannot be measured" — and it is also half of the §1 ban on a stock photo with
   a dark overlay and white text. So the image bleeds, and the copy rides a
   solid panel in the page's own ground colour, which keeps every ratio
   measurable and every rule intact. */
[data-hero-image="bleed"] { position: relative; }
/*
 * `inset: 0` would resolve against .hero__inner — the centred shell, which is
 * the visual's own parent — so the image would stop at the shell edges and not
 * bleed at all. Pulling left and right out to the viewport gives the real
 * full-bleed; .hero already clips with overflow: hidden, so nothing scrolls.
 */
[data-hero-image="bleed"] .hero__visual {
  position: absolute; z-index: 0; min-height: 0; border-radius: 0;
  top: 0; bottom: 0; left: calc(50% - 50vw); right: calc(50% - 50vw);
}
[data-hero-image="bleed"] .hero__inner { position: relative; z-index: 1; }
/*
 * AND THE COPY HAS TO BE POSITIONED TOO.
 *
 * The visual is a CHILD of .hero__inner, so .hero__inner's own z-index does not
 * lift the text above it — inside that stacking context a positioned element at
 * z-index 0 paints above a STATIC sibling regardless of source order. The hero
 * rendered as a bare tile with the headline, the subtitle and both buttons
 * hidden underneath it, on a page where every automated check passed: the audit
 * measures contrast, scale, radii and overflow, and text painted behind an
 * opaque sibling trips none of them. Two screenshots and a DOM measurement
 * disagreed before this was found — the boxes were all in the right places.
 */
[data-hero-image="bleed"] .hero__body,
[data-hero-image="bleed"] .hero__branches { position: relative; z-index: 1; }
/*
 * The copy panel stays INSIDE the shell.
 *
 * The first version tried to bleed the panel to the viewport edge with
 * `margin-left: calc(50% - 50vw)`. The percentage resolves against the grid
 * COLUMN, not the centred shell, so on a 1440 desktop it computed to roughly
 * -370px, and `.hero { overflow: hidden }` clipped the headline, the subtitle
 * and both buttons straight off the page. The hero rendered as an empty tile
 * with a floating strip. Every automated check passed it — the audit measures
 * contrast, scale, radii and overflow, and text pushed outside a clipped parent
 * trips none of them. Found on the 1440 tile.
 */
[data-hero-image="bleed"] .hero__body {
  background: var(--bg);
  padding: clamp(24px, 3vw, 40px);
  border-radius: var(--radius-sm);
}
@media (min-width: 900px) {
  [data-hero-image="bleed"] .hero__inner { grid-template-columns: minmax(0, 0.66fr) minmax(0, 0.34fr); }
}
/* The regalia strip is part of the hero's own plane, so it rides a panel too. */
[data-hero-image="bleed"] .regalia {
  position: relative; z-index: 1; background: var(--bg);
  padding-inline: clamp(24px, 3vw, 40px);
  padding-bottom: clamp(14px, 1.8vw, 20px);
  border-radius: var(--radius-sm);
}

[data-hero-image="none"] .hero__visual { display: none; }
[data-hero-image="none"] .hero__inner { grid-template-columns: 1fr; }

/* The marker, made unmissable in the hero. It is the reply-hook: one obvious
   blank the owner wants to fill. */
.hero__visual .media__badge,
.hero .media__badge { font-size: 14px; padding: 9px 14px; }
/*
 * On `bleed` the image runs behind the copy panel, and the badge — anchored
 * bottom-left of the visual — ended up underneath it, invisible. That is not a
 * cosmetic miss: docs/imagery-policy.md requires the marker to be visible on
 * EVERY instance, because a reader must never have to guess whether a photo is
 * the clinic's. It moves to the exposed side of the image.
 */
[data-hero-image="bleed"] .hero__visual .media__badge {
  left: auto; right: clamp(16px, 3vw, 40px); bottom: clamp(16px, 3vw, 40px);
}

/* ---- Regalia -------------------------------------------------------------
   A strip of earned facts under the hero split: award, years, licence, rating,
   step-free access. Deliberately NOT built like `.stats` above it — that is a
   columnar grid of value-over-label, and repeating the shape twenty pixels
   later would read as two stat rows and land straight on the §1 ban about
   identical rhythm. This is a single run of sentences separated by hairlines.

   No boxes, no badges, no tint fills, no glyphs: a fact strip, not a trophy
   case, and the icon set is at its bound of twelve anyway.

   Mobile-first, and it stacks: at narrow widths the rules come off entirely and
   the facts become lines, because five inline items separated by vertical rules
   at 390px is a hedge, not a strip.
*/
.regalia {
  margin-top: clamp(30px, 5vw, 46px);
  padding-top: clamp(18px, 2.6vw, 24px);
  border-top: 1px solid var(--line);
}
.regalia__list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 9px;
}
.regalia__item { font-size: 15px; line-height: 1.45; color: var(--ink-2); }
/* The award. Weight and full ink, never colour: §1 bans a coloured word used
   for emphasis, and a gold badge here would be the trophy case. */
.regalia__item--lead { color: var(--ink); font-weight: 600; }
.regalia__item--quiet { color: var(--ink-3); }

@media (min-width: 720px) {
  /*
   * Separated by space, not by rules.
   *
   * The first version put a border-left on every item after the first. Flex wrap
   * does not know where a line starts, so a strip that wrapped — М-ДЕНТ's five
   * facts at 1440 — began its second line with a vertical rule hanging in front
   * of the text, reading as a stray mark. No pure-CSS selector expresses "first
   * on its line", so the rules come off and a wide column gap does the work.
   * Quieter too, which is what a fact strip is supposed to be.
   */
  .regalia__list { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 10px 32px; }
}
/*
 * A stat value is a single fact and reads as one — «10:00 — 20:00» broken over
 * two lines reads as two. At 390 the cell is 161px and Нұра's 26px display face
 * did not fit, so the hours wrapped.
 *
 * Both halves are needed. `nowrap` alone would let the text size the grid
 * column and push the page wider — the exact failure §4 of the skill warns
 * about — so the size comes down with it, to something that fits the narrowest
 * cell the auto-fit grid can produce.
 *
 * `.stats li > b` rather than `.stats b`: a lead's theme.css loads after this
 * file and sets its own `.stats b` size, so an equal-specificity rule here would
 * lose. This is a fit guarantee, not an aesthetic choice, and it has to hold on
 * every lead — which is why it lives in the template and not in one theme.
 */
@media (max-width: 420px) {
  .stats li > b { white-space: nowrap; font-size: clamp(17px, 5.4vw, 23px); }
}

/* ---- Services ---------------------------------------------------------- */
.service-groups { display: grid; gap: clamp(28px, 4vw, 44px); }
.service-group__name {
  font-family: var(--font-display); font-size: 15px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 16px;
}
.service-list { display: grid; gap: 10px; }
.service {
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  padding: 20px 22px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), transform .2s var(--ease);
}
.service:hover { border-color: var(--line-2); }
.service__name { font-weight: 500; flex: 1 1 200px; }
.service__note { display: block; font-size: 13.5px; color: var(--ink-3); margin-top: 3px; }
.service__price {
  font-family: var(--font-display); font-weight: 600; font-size: 18px;
  white-space: nowrap; letter-spacing: -0.01em;
}
.service__price--free { color: var(--accent-text); }

/* ---- Advantages -------------------------------------------------------- */
.advantages { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); }
.advantage {
  padding: 28px 26px 30px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.advantage__icon {
  width: 44px; height: 44px; border-radius: var(--radius-sm);
  display: grid; place-items: center; margin-bottom: 20px;
  background: var(--accent-08); color: var(--accent-line);
}
.advantage__icon svg { width: 22px; height: 22px; }
.advantage h3 { font-size: 19px; margin-bottom: 9px; }
.advantage p { color: var(--ink-2); font-size: 15.5px; line-height: 1.55; }

/* ---- Team -------------------------------------------------------------- */
.team { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.doctor {
  padding: 26px 24px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.doctor__avatar {
  width: 52px; height: 52px; border-radius: var(--radius-pill); margin-bottom: 18px;
  background: var(--accent-08); color: var(--accent-text);
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 600; font-size: 17px;
}
.doctor__name { font-size: 17.5px; margin-bottom: 6px; }
.doctor__role { color: var(--ink-2); font-size: 15px; }
.doctor__exp { color: var(--ink-3); font-size: 14px; margin-top: 6px; }

/* ---- Gallery ----------------------------------------------------------- */
.gallery { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.gallery li { border-radius: var(--radius); overflow: hidden; background: var(--tint); aspect-ratio: 4 / 3; }
.gallery img { width: 100%; height: 100%; object-fit: cover; }

/* ---- Contacts ---------------------------------------------------------- */
.contacts__grid { display: grid; gap: 20px; }
.contacts__list {
  display: grid; gap: 26px; align-content: start;
  padding: clamp(26px, 4vw, 38px); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.contacts__list dt {
  font-size: 13px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 7px;
}
.contacts__list dd { font-size: 18px; line-height: 1.45; }
.contacts__list dd a { border-bottom: 1px solid var(--accent-16); }
.contacts__list dd a:hover { border-bottom-color: var(--accent-line); }
.contacts__list .fineprint { margin-top: 5px; }

.contacts__map {
  position: relative; border-radius: var(--radius); overflow: hidden;
  min-height: 250px; display: grid; place-items: center; padding: 30px;
  background: var(--grad); color: var(--accent-ink); text-align: center;
}
.contacts__map::before {
  content: ''; position: absolute; inset: 0; opacity: .5;
  background:
    repeating-linear-gradient(0deg,  rgba(255,255,255,.16) 0 1px, transparent 1px 46px),
    repeating-linear-gradient(90deg, rgba(255,255,255,.16) 0 1px, transparent 1px 46px),
    radial-gradient(70% 60% at 50% 45%, rgba(0,0,0,.22), transparent 70%);
}
.contacts__map > * { position: relative; }
.contacts__map p { font-size: 19px; font-weight: 500; margin-bottom: 18px; max-width: 26ch; }
.contacts__map .btn { --btn-bg: var(--surface); --btn-fg: var(--ink); --btn-bd: transparent; }

/* ---- Lead form --------------------------------------------------------- */
.leadcard {
  display: grid; gap: clamp(28px, 4vw, 44px);
  padding: clamp(30px, 5vw, 60px);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.leadcard__title { font-size: clamp(26px, 4vw, 38px); }
.leadcard__lead { color: var(--ink-2); margin-top: 16px; max-width: 46ch; }
.leadcard__license { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--line); }

.leadform { display: grid; gap: 14px; align-content: start; }
.field { display: block; }
.field__label {
  display: block; font-size: 13px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 8px;
}
.field__input {
  width: 100%; padding: 15px 18px; min-height: 54px;
  background: var(--bg); border: 1px solid var(--line-2);
  border-radius: var(--radius-sm); font-size: 16.5px;
  transition: border-color .18s var(--ease), background-color .18s var(--ease);
}
/* Named explicitly, not inherited: an unstyled select/textarea takes the UA
   surface, which is what an in-app browser darkens first.
   `input` is here for the mirror-image reason. Line 26 gives every control
   `color: inherit`, so a field inside a lead's inverted (dark) .leadcard drew
   the visitor's typed name in white on the field's light ground — invisible,
   and invisible to the computed audit too, because an empty input has no text
   node to measure. The field's ground is always --bg, so its ink is always
   --ink, whatever card it is sitting in. */
input.field__input, select.field__input, textarea.field__input {
  background: var(--bg); color: var(--ink); appearance: none;
}
.field__input::placeholder { color: var(--ink-3); }
.field__input:focus { outline: none; border-color: var(--accent-line); background: var(--surface); }
.field__input[aria-invalid="true"] { border-color: #C4362F; }
.field__error { color: #C4362F; font-size: 14px; }

/* ---- Footer ------------------------------------------------------------ */
.site-footer { border-top: 1px solid var(--line); padding-block: clamp(40px, 6vw, 66px); }
.site-footer__inner { display: grid; gap: 26px; }
.site-footer__col { display: grid; gap: 7px; align-content: start; }
.site-footer__name { font-family: var(--font-display); font-weight: 600; font-size: 18px; }
.site-footer__suffix { color: var(--ink-2); font-size: 14px; margin-top: 2px; }
.site-footer__link { color: var(--ink-2); }
.site-footer__link:hover { color: var(--accent-text); }
.site-footer__legal { margin-top: 8px; }

@media (min-width: 760px) {
  .contacts__grid   { grid-template-columns: 1fr 1fr; }
  /* A lead with no verified map link leaves the second column empty, and half a
     section of nothing reads as a page that broke. Collapse to one column. */
  .contacts__grid:has(.contacts__map[hidden]) { grid-template-columns: 1fr; }
  .leadcard         { grid-template-columns: 1fr 1fr; align-items: start; }
  .site-footer__inner { grid-template-columns: 1.4fr 1fr 1.6fr; gap: 40px; }
  .service-list     { grid-template-columns: 1fr 1fr; }
}

/* =============================================================================
   PRESETS
   Hero composition, section order and card treatment. Same DOM, different rules.

   FROZEN: `elegant`, `bold` and `minimal` are untested on a real lead. Every
   demo built so far declares no preset and shapes itself with its own
   theme.css, so these three have only ever been rendered against
   tests/fixtures/. They are kept, not deleted — but do not select one for a
   lead without a design review of the result first. `bold` additionally pairs
   with fontPair 3 (Unbounded), which cannot render Kazakh: see
   docs/quality-gaps.md §2.
   ========================================================================== */

/* -- clinical: split hero, bordered cards, trust right after prices ------- */
@media (min-width: 900px) {
  .preset-clinical .hero__inner { grid-template-columns: 1.08fr 0.92fr; }
  .preset-clinical .hero__visual { min-height: 460px; }
}
.preset-clinical [data-section="hero"]       { order: 1; }
.preset-clinical [data-section="services"]   { order: 2; }
.preset-clinical [data-section="advantages"] { order: 3; }
.preset-clinical [data-section="team"]       { order: 4; }
.preset-clinical [data-section="gallery"]    { order: 5; }
.preset-clinical [data-section="contacts"]   { order: 6; }
.preset-clinical [data-section="lead"]       { order: 7; }

/* -- elegant: full-bleed hero wash, serif display, air, feeling before price */
/*    FROZEN — untested on a real lead; do not select without a review. */
.preset-elegant { --tint: #F6F2EE; }
.preset-elegant .hero {
  padding-block: clamp(72px, 12vw, 150px);
  color: var(--accent-ink); background: var(--grad);
}
.preset-elegant .hero::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(90% 80% at 70% 0%, rgba(255,255,255,.26), transparent 60%),
              linear-gradient(180deg, transparent 40%, rgba(0,0,0,.28));
}
.preset-elegant .hero__inner { position: relative; max-width: 900px; text-align: center; justify-items: center; }
.preset-elegant .hero__subtitle { color: inherit; opacity: .88; margin-inline: auto; }
.preset-elegant .hero__actions { justify-content: center; }
.preset-elegant .hero__badge { background: rgba(255,255,255,.18); color: inherit; }
.preset-elegant .hero__badge::before { background: currentColor; }
.preset-elegant .stats { border-top-color: rgba(255,255,255,.28); justify-items: center; text-align: center; }
.preset-elegant .stats span { color: inherit; opacity: .78; }
.preset-elegant .hero__visual { display: none; }
.preset-elegant .btn--primary { --btn-bg: var(--surface); --btn-fg: var(--ink); }
.preset-elegant .btn--quiet { --btn-bg: transparent; --btn-fg: inherit; --btn-bd: rgba(255,255,255,.5); }
.preset-elegant .section__title { letter-spacing: -0.01em; }
.preset-elegant .service, .preset-elegant .advantage, .preset-elegant .doctor {
  border-color: transparent; box-shadow: var(--shadow-sm);
}
.preset-elegant [data-section="hero"]       { order: 1; }
.preset-elegant [data-section="advantages"] { order: 2; }
.preset-elegant [data-section="services"]   { order: 3; }
.preset-elegant [data-section="team"]       { order: 4; }
.preset-elegant [data-section="gallery"]    { order: 5; }
.preset-elegant [data-section="contacts"]   { order: 6; }
.preset-elegant [data-section="lead"]       { order: 7; }

/* -- bold: typographic hero, oversized type, heavy rules ----------------- */
/*    FROZEN — untested on a real lead; do not select without a review. */
.preset-bold .hero__inner { grid-template-columns: 1fr; }
.preset-bold .hero__title { font-size: clamp(40px, 11vw, 108px); letter-spacing: -0.045em; line-height: .96; }
.preset-bold .hero__visual { display: none; }
.preset-bold .hero__subtitle { font-size: clamp(18px, 2.6vw, 23px); max-width: 44ch; }
.preset-bold .section__title { font-size: clamp(32px, 6.4vw, 60px); letter-spacing: -0.035em; }
.preset-bold .service, .preset-bold .advantage, .preset-bold .doctor,
.preset-bold .contacts__list, .preset-bold .leadcard { border-width: 2px; border-color: var(--ink); box-shadow: none; }
.preset-bold .service:hover { box-shadow: 5px 5px 0 var(--accent); }
.preset-bold .btn { border-width: 2px; }
.preset-bold [data-section="hero"]       { order: 1; }
.preset-bold [data-section="advantages"] { order: 2; }
.preset-bold [data-section="services"]   { order: 3; }
.preset-bold [data-section="gallery"]    { order: 4; }
.preset-bold [data-section="team"]       { order: 5; }
.preset-bold [data-section="contacts"]   { order: 6; }
.preset-bold [data-section="lead"]       { order: 7; }

/* -- minimal: compact typographic hero, rules instead of cards ----------- */
/*    FROZEN — untested on a real lead; do not select without a review. */
.preset-minimal { --tint: #F7F6F4; }
.preset-minimal .hero { padding-block: clamp(52px, 8vw, 96px); }
.preset-minimal .hero__inner { grid-template-columns: 1fr; max-width: 800px; }
.preset-minimal .hero__visual { display: none; }
.preset-minimal .service, .preset-minimal .advantage, .preset-minimal .doctor {
  background: transparent; border: 0; border-top: 1px solid var(--line);
  border-radius: 0; padding-inline: 0; box-shadow: none;
}
.preset-minimal .service:hover { transform: none; box-shadow: none; }
.preset-minimal .advantage__icon { background: transparent; padding: 0; width: auto; height: auto; margin-bottom: 14px; }
.preset-minimal .contacts__list { background: transparent; border: 0; padding-inline: 0; }
.preset-minimal .leadcard { box-shadow: none; }
.preset-minimal [data-section="hero"]       { order: 1; }
.preset-minimal [data-section="services"]   { order: 2; }
.preset-minimal [data-section="contacts"]   { order: 3; }
.preset-minimal [data-section="advantages"] { order: 4; }
.preset-minimal [data-section="team"]       { order: 5; }
.preset-minimal [data-section="gallery"]    { order: 6; }
.preset-minimal [data-section="lead"]       { order: 7; }

/* =============================================================================
   Chat widget
   ========================================================================== */
.chat-fab {
  position: fixed; right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 60; width: 58px; height: 58px; border-radius: var(--radius-pill);
  border: 0; background: var(--grad); color: var(--accent-ink);
  display: grid; place-items: center; cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: transform .2s var(--ease), opacity .2s var(--ease);
}

.chat-fab svg { width: 26px; height: 26px; }
.chat-fab__close { display: none; }
.chat-fab.is-open .chat-fab__open { display: none; }
.chat-fab.is-open .chat-fab__close { display: block; }

.chat-panel {
  position: fixed; z-index: 61;
  right: max(16px, env(safe-area-inset-right));
  bottom: calc(max(16px, env(safe-area-inset-bottom)) + 70px);
  left: max(16px, env(safe-area-inset-left));
  max-width: 396px; margin-left: auto;
  height: min(600px, calc(100dvh - 130px));
  display: none; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
  opacity: 0; transform: translateY(10px) scale(.985);
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.chat-panel.is-mounted { display: flex; }
.chat-panel.is-open { opacity: 1; transform: none; }

.chat-head {
  display: flex; align-items: center; gap: 12px; padding: 16px 18px;
  background: var(--grad); color: var(--accent-ink); flex: none;
}
.chat-head__title { font-family: var(--font-display); font-weight: 600; font-size: 16px; }
.chat-head__sub { font-size: 12.5px; opacity: .8; }

.chat-log {
  flex: 1; overflow-y: auto; overscroll-behavior: contain;
  padding: 18px; display: flex; flex-direction: column; gap: 10px;
  background: var(--bg);
}
.chat-msg {
  max-width: 84%; padding: 11px 15px; border-radius: 16px;
  font-size: 15.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
}
.chat-msg--bot  { background: var(--surface); border: 1px solid var(--line); border-bottom-left-radius: 5px; align-self: flex-start; }
.chat-msg--user { background: var(--grad); color: var(--accent-ink); border-bottom-right-radius: 5px; align-self: flex-end; }
.chat-msg--note { align-self: center; text-align: center; max-width: 100%; background: var(--accent-08); color: var(--ink-2); font-size: 13.5px; border-radius: var(--radius-sm); }
.chat-msg--note a { color: var(--accent-text); text-decoration: underline; }

.chat-typing { display: inline-flex; gap: 4px; align-items: center; padding: 14px 16px; }
.chat-typing i { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); animation: chatBlink 1.2s infinite; }
.chat-typing i:nth-child(2) { animation-delay: .18s; }
.chat-typing i:nth-child(3) { animation-delay: .36s; }
@keyframes chatBlink { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

.chat-form { display: flex; gap: 8px; padding: 12px; border-top: 1px solid var(--line); flex: none; background: var(--surface); }
.chat-form input {
  flex: 1; min-width: 0; padding: 12px 15px; font-size: 16px;
  background: var(--bg); border: 1px solid var(--line-2); border-radius: var(--radius-pill);
}
.chat-form input:focus { outline: none; border-color: var(--accent-line); }
.chat-form button {
  flex: none; width: 46px; height: 46px; border-radius: var(--radius-pill); border: 0;
  background: var(--grad); color: var(--accent-ink); display: grid; place-items: center; cursor: pointer;
}
.chat-form button:disabled { opacity: .45; cursor: not-allowed; }
.chat-form button svg { width: 19px; height: 19px; }

/* =============================================================================
   Motion — native CSS only, no library, no JS.

   ONE moment, and it is the hero's load. The hero's own elements arrive in a
   short stagger via @starting-style + transition: 420 ms each, last delay
   160 ms, 580 ms in total. Nothing else on the page animates on entry.

   Scroll-driven reveals were removed on 05 Sep 2026 and must not come back.
   They hid every section below the fold from every screenshot this repo took —
   a fullPage capture does not scroll, so the operator's "look at the
   screenshots" gate was looking at a blank page. They also run backwards when
   the visitor scrolls up, and on a phone opened from WhatsApp they read as lag.
   Everything below the hero is painted at load, no exceptions.

   A browser without @starting-style simply shows the finished page.
   prefers-reduced-motion removes the stagger and changes nothing else.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  .hero__badge,
  .hero__title,
  .hero__subtitle,
  .hero__actions,
  .stats,
  .hero__visual {
    transition: opacity .42s var(--ease), transform .42s var(--ease);
  }
  @starting-style {
    .hero__badge,
    .hero__title,
    .hero__subtitle,
    .hero__actions,
    .stats,
    .hero__visual { opacity: 0; transform: translateY(var(--rise, 14px)); }
  }
  .hero__badge    { transition-delay: .02s; }
  .hero__title    { transition-delay: .10s; --rise: 20px; }
  .hero__visual   { transition-delay: .16s; --rise: 26px; }
  .hero__subtitle { transition-delay: .10s; }
  .hero__actions  { transition-delay: .14s; }
  .stats          { transition-delay: .16s; }
}

/* Hover transitions answer the pointer and stay. Entry transforms do not. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* =============================================================================
   Imagery
   Every stock photo carries a visible "your photo goes here" badge. See
   docs/imagery-policy.md — a demo must never let a reader assume a stock image
   shows this business.
   ========================================================================== */
.media {
  position: relative; overflow: hidden;
  border-radius: var(--radius); background: var(--tint);
}
.media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Sits over the image, always legible, never decorative enough to be missed. */
.media__badge {
  position: absolute; left: 12px; bottom: 12px; z-index: 2;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px 7px 10px; border-radius: var(--radius-pill);
  background: rgba(12, 10, 8, 0.72);
  backdrop-filter: blur(8px) saturate(1.4);
  color: #fff; font-size: 12.5px; font-weight: 600; line-height: 1.25;
  letter-spacing: -0.005em; max-width: calc(100% - 24px);
  box-shadow: 0 1px 3px rgba(0,0,0,.28);
}
.media__badge::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: #FFC24A;
}

/* ---- Hero image --------------------------------------------------------- */
.hero__visual.media { background: var(--tint); }
.hero__visual.media::after { content: none; }
.hero__visual.media img { aspect-ratio: 4 / 3; }
@media (min-width: 900px) {
  .preset-clinical .hero__visual.media img { aspect-ratio: auto; height: 100%; min-height: 460px; }
}

/* ---- Gallery / cases ---------------------------------------------------- */
.gallery { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); }
.gallery li { background: none; aspect-ratio: auto; border-radius: 0; overflow: visible; }
.case { display: flex; flex-direction: column; gap: 14px; }
.case .media { aspect-ratio: 4 / 3; }
.case__title { font-size: 17px; font-family: var(--font-display); font-weight: 600; }
.case__text { font-size: 15px; color: var(--ink-2); line-height: 1.5; margin-top: 5px; }

/* ---- Directions --------------------------------------------------------- */
.directions { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(262px, 1fr)); }
.direction {
  padding: 26px 24px 28px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.direction:hover { border-color: var(--line-2); box-shadow: var(--shadow-sm); }
.direction__icon {
  width: 42px; height: 42px; border-radius: var(--radius-sm);
  display: grid; place-items: center; margin-bottom: 18px;
  background: var(--accent-08); color: var(--accent-line);
}
.direction__icon svg { width: 21px; height: 21px; }
.direction__body { min-width: 0; }
.direction h3 { font-size: 18px; margin-bottom: 8px; }
.direction p { color: var(--ink-2); font-size: 15px; line-height: 1.55; }

/* ---- Training ----------------------------------------------------------- */
.training {
  padding: clamp(30px, 5vw, 56px);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.training__text { color: var(--ink-2); margin-top: 18px; max-width: 60ch; font-size: 17px; }
.training__list {
  display: flex; flex-wrap: wrap; gap: 9px; margin-top: 26px;
}
.training__list li {
  padding: 8px 15px; border-radius: var(--radius-pill);
  background: var(--accent-08); color: var(--accent-text);
  font-size: 14.5px; font-weight: 600;
}

/* ---- Branches — one block per address, for a multi-branch lead -----------
   Deliberately the same `.contacts__list` rows as the single-branch shape, so a
   lead's theme styles both with one rule and a network page does not need its
   own contacts styling. What is added is the name, the district line and the
   branch's own rating.
   ------------------------------------------------------------------------ */
.branches { display: grid; gap: 14px; }
@media (min-width: 820px) { .branches { grid-template-columns: 1fr 1fr; gap: 18px; } }
.branch { display: grid; align-content: start; gap: 10px; }
.branch__name { font-size: clamp(19px, 2.2vw, 23px); letter-spacing: -0.015em; }
.branch__note { color: var(--ink-3); font-size: 14.5px; line-height: 1.45; margin-top: -4px; }
.branch__rating { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.branch__rating b { font-size: clamp(21px, 2.4vw, 26px); letter-spacing: -0.02em; }
.branch__rating span { color: var(--ink-3); font-size: 14.5px; }
/* A count that belongs to this address's own directory card, next to the other
   one. Two branches of one business do not always publish the same price list —
   see demos/dental-time, where they publish 42 rows and 38. */
.branch__prices { color: var(--ink-3); font-size: 14.5px; margin-top: -4px; }
/*
 * The ways to write to this address. A run of links rather than more definition
 * rows: five labelled rows of one word each turns a contact block into a form.
 * `min-height` carries the 48px target on the link itself (§4 Mobile), the same
 * way `.hero__branch-phone` does.
 */
.branch__links { list-style: none; margin: 2px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0 20px; }
.branch__link {
  font-size: 15px; text-decoration: underline; text-underline-offset: 3px;
  min-height: 48px; display: inline-flex; align-items: center;
}
.branch__map { text-decoration: underline; text-underline-offset: 3px; }

/* A service one branch has and another does not. It is information, so it is
   set as information and never as a badge. */
.direction__where { color: var(--ink-3); font-size: 13.5px; margin-top: 6px; }

/* ---- Reviews / aggregate rating ----------------------------------------- */
.rating {
  display: grid; gap: 22px; align-items: center;
  padding: clamp(28px, 4vw, 46px);
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
.rating__score { display: flex; align-items: baseline; gap: 14px; }
.rating__value {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(46px, 9vw, 78px); line-height: 1; letter-spacing: -0.03em;
}
.rating__stars { display: flex; gap: 3px; color: var(--accent-line); }
.rating__stars svg { width: 21px; height: 21px; }
.rating__count { color: var(--ink-2); font-size: 17px; }
.rating__note { color: var(--ink-3); font-size: 14.5px; max-width: 52ch; }
@media (min-width: 700px) {
  .rating { grid-template-columns: auto 1fr auto; gap: 34px; }
}

/* ---- Select control ----------------------------------------------------- */
select.field__input {
  appearance: none; cursor: pointer; padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235D554E' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 15px center; background-size: 18px;
}

/* Presets that hide the hero visual must not hide a real photograph. */
.preset-elegant .hero__visual.media,
.preset-bold .hero__visual.media,
.preset-minimal .hero__visual.media { display: block; }
.preset-elegant .hero__inner:has(.media) { max-width: var(--shell); text-align: left; justify-items: stretch; }
.preset-elegant .hero__inner:has(.media) .hero__actions { justify-content: flex-start; }
.preset-elegant .hero__inner:has(.media) .stats { justify-items: start; text-align: left; }
@media (min-width: 900px) {
  .preset-elegant .hero__inner:has(.media),
  .preset-bold .hero__inner:has(.media),
  .preset-minimal .hero__inner:has(.media) { grid-template-columns: 1.05fr 0.95fr; }
  .preset-minimal .hero__inner:has(.media) { max-width: var(--shell); }
}
.preset-elegant .media__badge { background: rgba(12, 10, 8, 0.78); }

/* =============================================================================
   Abstract tiles — stand in for photography where a photo would be read as a
   claim about real clinical work. Brand colours only, no imagery.
   ========================================================================== */
.media--tile { position: relative; aspect-ratio: 4 / 3; background: var(--accent-100); }
.media--tile::before {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(120% 100% at 12% 8%, var(--accent-200), transparent 58%),
    radial-gradient(90% 80% at 92% 96%, var(--accent-400), transparent 62%),
    linear-gradient(150deg, var(--accent-50), var(--accent-300));
}
.media--tile::after {
  content: ''; position: absolute; inset: 0; opacity: .5;
  background: repeating-linear-gradient(58deg,
    transparent 0 22px, color-mix(in srgb, var(--accent-600) 14%, transparent) 22px 23px);
}
.tile-b::before { background:
  radial-gradient(110% 95% at 88% 10%, var(--accent-200), transparent 56%),
  radial-gradient(95% 85% at 8% 92%, var(--accent-500), transparent 60%),
  linear-gradient(28deg, var(--accent-100), var(--accent-300)); }
.tile-b::after { background: repeating-linear-gradient(-52deg,
  transparent 0 26px, color-mix(in srgb, var(--accent-700) 12%, transparent) 26px 27px); }

/* =============================================================================
   Price estimator
   ========================================================================== */
.estimator { display: grid; gap: 18px; align-items: start; }
@media (min-width: 860px) { .estimator { grid-template-columns: 1.55fr 1fr; gap: 28px; } }

.estimator__list { display: grid; gap: 2px; }
/*
 * `nowrap`, not `wrap`. With wrap, a row whose name is longer than the line
 * pushed its price onto a line of its own, right-aligned under an empty half
 * — «Глубокий кариес под микроскопом» then «49 000 ₸» alone below it, at 390.
 * The label already carries `min-width: 0`, so with nowrap the NAME wraps
 * inside its own column and the price stays on the first line where it can be
 * compared with the row above. Seen in a screenshot; no computed check can
 * tell this apart from a correct two-line row.
 */
.estimator__row {
  display: flex; align-items: center; gap: 16px; flex-wrap: nowrap;
  padding: 15px 18px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid transparent;
  transition: background-color .16s var(--ease), border-color .16s var(--ease);
}
.estimator__row.is-on { border-color: var(--accent-line); background: var(--accent-08); }
.estimator__row label {
  display: flex; align-items: center; gap: 13px; flex: 1 1 auto; cursor: pointer;
  min-width: 0; min-height: 48px;   /* the label is the real tap target */
}
.estimator__box {
  appearance: none; width: 24px; height: 24px; flex: none; cursor: pointer;
  border: 1.5px solid var(--line-2); border-radius: 7px; background: var(--bg);
  display: grid; place-items: center;
}
.estimator__box:checked { background: var(--accent-700); border-color: var(--accent-700); }
.estimator__box:checked::after {
  content: ''; width: 11px; height: 6px; margin-top: -2px;
  border-left: 2px solid #fff; border-bottom: 2px solid #fff; transform: rotate(-45deg);
}
.estimator__name { font-size: 16px; overflow-wrap: anywhere; }
.estimator__price { margin-left: auto; font-weight: 600; white-space: nowrap; }
.estimator__price--ask { color: var(--ink-3); font-weight: 500; font-size: 14.5px; }

.estimator__total {
  position: sticky; top: 90px; display: grid; gap: 12px;
  padding: 26px 24px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line);
}
.estimator__label { font-size: 13px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); }
.estimator__sum {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(34px, 6vw, 48px); line-height: 1; letter-spacing: -0.03em;
}
/*
 * AFTER `.estimator__sum`, not before it. Both selectors are one class, so
 * source order decided, and the empty state was drawn at 48px display weight —
 * «Отметьте услуги» reading as a heading over the CTA instead of as the
 * placeholder it is. The rule was there, was correct, and was losing.
 */
.estimator__sum--empty {
  font-size: clamp(17px, 2.4vw, 20px); font-weight: 500;
  color: var(--ink-3); letter-spacing: 0;
}
.estimator__unpriced { color: var(--accent-800); }

/* =============================================================================
   Appointment slots
   ========================================================================== */
.field__label--sub { margin-top: 16px; }
.slots { display: flex; flex-wrap: wrap; gap: 8px; }
.slot {
  padding: 11px 17px; min-height: 46px; cursor: pointer;
  border: 1px solid var(--line-2); border-radius: var(--radius-pill);
  background: var(--bg); font-size: 15.5px; font-weight: 600;
}
.slot.is-on { background: var(--accent-700); border-color: var(--accent-700); color: #fff; }
input[type="date"].field__input { cursor: pointer; }

/* =============================================================================
   Mobile action bar — primary actions inside thumb reach
   ========================================================================== */
.actionbar {
  position: fixed; z-index: 55; left: 0; right: 0; bottom: 0;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(14px) saturate(1.5);
  border-top: 1px solid var(--line);
}
.actionbar__item {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 4px; min-height: 52px; border-radius: var(--radius-sm);
  font-size: 12px; font-weight: 600; color: var(--ink-2);
}
/*
 * A glyph placed beside text by setIconText() has no intrinsic size: the SVG
 * has a viewBox and no width, so it fills whatever box it is in. On the booking
 * button that came out 271px square and on the privacy note 468px — on every
 * lead with a form, shipped since the tone rewrite. Size them where they are
 * inserted, and lay the containers out so the glyph sits beside the text.
 */
[data-slot="leadSubmit"], [data-slot="leadPrivacy"] {
  display: inline-flex; align-items: center; gap: 9px;
}
[data-slot="leadPrivacy"] { display: flex; align-items: flex-start; }
[data-slot="leadSubmit"] svg,
[data-slot="leadPrivacy"] svg {
  width: 18px; height: 18px; flex: none; stroke: currentColor; fill: none;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
[data-slot="leadPrivacy"] svg { margin-top: 2px; }

.actionbar__item svg { width: 21px; height: 21px; stroke: currentColor; fill: none;
  /* 1.6 everywhere the set is drawn at 21-22px — see the ICONS block in app.js. */
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.actionbar__item:active { background: var(--accent-08); color: var(--accent-text); }
@media (min-width: 820px) { .actionbar { display: none; } }
/* Keep the chat button clear of the bar on phones. */
@media (max-width: 819px) {
  body:has(.actionbar:not([hidden])) .chat-fab { bottom: calc(78px + env(safe-area-inset-bottom)); }
  body:has(.actionbar:not([hidden])) .chat-panel { bottom: calc(140px + env(safe-area-inset-bottom)); }
  body:has(.actionbar:not([hidden])) .site-footer { padding-bottom: 90px; }
}
