/* HYPERFIC — component styles
   Точная фиксация текущего дизайна. Desktop-first, mobile-переопределения ниже.
   Breakpoint: --hf-bp = 900px (desktop ≥ 900, mobile < 900).
   Hero-блок: см. секцию «HERO» — там же место под SVG-морф. */

/* ============================================================
   BASE
   ============================================================ */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--hf-coal-canvas);
}

body {
  font-family: var(--hf-font-ui);
  color: var(--hf-ivory);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--hf-steel); text-decoration: none; }
a:hover { color: var(--hf-ivory); }

/* ============================================================
   SCREEN / ФОН
   Порядок слоёв: gradient → peripheral stitches → grain → контент
   ============================================================ */
.hf-screen {
  position: relative;
  overflow: hidden;
  min-height: 100vh;
  min-height: 100dvh; /* tracks the real visible viewport on iOS/Telegram, no jump on toolbar collapse */
  background: var(--hf-coal);
  color: var(--hf-ivory);
}

/* В продакшне — просто фон страницы. В артборд-режиме фиксируется 1440×900. */
.hf-screen--artboard {
  width: 1440px;
  height: 900px;
  min-height: 0;
  border: var(--hf-border-artboard);
}

.hf-bg,
.hf-grain,
.hf-stitch-field {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.hf-bg { background: var(--hf-bg-gradient); }

.hf-grain {
  opacity: 0.55;
  background-image: var(--hf-grain-desktop);
}

/* Разрежённые стежковые × по периферии. Статичные, разной заметности. */
.hf-stitch-field {
  font-family: var(--hf-font-stitch);
  color: var(--hf-steel-stitch);
}
.hf-stitch-field > span {
  position: absolute;
  line-height: 1;
}

/* ============================================================
   HEADER
   ============================================================ */
/* Soft legibility wash behind HYPERFIC/DRAMIONE now that the header is a
   fixed layer a scrolling result can pass under (see the header-unify
   commit) — content fades out before it reaches the wordmark instead of
   reading through it. Not a panel: no solid fill, no blur, no
   border/shadow, and no change to .hf-header's own geometry/padding —
   this is a separate, non-interactive sibling painted immediately before
   .hf-header in the markup, so it always sits behind the header's own
   text (same z-index, DOM order decides paint order) while still staying
   above .hf-layout's scrolling content (z-index 1) and the decorative
   bg/grain/stitch layers (z-index auto). --hf-coal-top is the exact top
   stop of --hf-bg-gradient — using it (not --hf-coal) is what makes the
   fade's own top edge invisible against the screen's real background
   instead of reading as a step. Height is intentionally taller than the
   header's own rendered height (99px desktop / 46px mobile) so the fade
   has room to actually reach transparent before the header's content,
   rather than cutting off right at its edge. Only rendered when
   state.view === "result" (see app.js) — home's hero and the short,
   never-scrolling empty state don't have the under-header overlap
   problem this solves, so they get no fade at all. */
.hf-header-fade {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 2;
  height: 160px;
  pointer-events: none;
  background: linear-gradient(to bottom, var(--hf-coal-top) 0%, transparent 100%);
  /* Desktop-only in practice (see .is-panel-open below), but harmless to
     declare unconditionally — same duration/easing as the header's own
     padding-right and .hf-layout__panel's flex-basis, so the fade's
     right edge tracks the drawer opening/closing in lockstep instead of
     snapping to its final width. */
  transition: right 360ms var(--hf-ease-sheet);
}
/* Keeps the fade clear of the desktop drawer column, geometrically and
   visually — end at the drawer's own left edge (524px, the same width
   as .hf-drawer/.hf-layout__panel's open flex-basis) instead of
   spanning the full screen width and dimming the panel's opaque
   background from above. Desktop only: on mobile the drawer is a
   position:fixed bottom sheet at z-index:4, already above the fade's
   z-index:2 with its own opaque background, so it already fully covers
   the fade beneath it with no extra rule needed. */
@media (min-width: 900px) {
  .hf-header-fade.is-panel-open { right: 524px; }
}

.hf-header {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 36px 44px;
  /* Full-width transparent shell — must not steal clicks from whatever
     sits underneath it (e.g. the drawer's ✕, top-right of the panel,
     was unreachable by real pointer events because this box, despite
     being invisible there, was still on top and intercepting hits). */
  pointer-events: none;
  /* Desktop-only: when the filter panel is open, DRAMIONE (the header's
     own right-aligned flex child, see .hf-stage__fandom below) must stay
     clear of the drawer's 524px column instead of sitting on top of it —
     see .hf-header.is-panel-open in the LAYOUT section. Transition lives
     here (not just on the modifier) so both the open *and* close edges
     animate; scoped to desktop only in that same rule, so this base
     transition is harmless (never triggered) on mobile. */
  transition: padding-right 360ms var(--hf-ease-sheet);
}
.hf-header > * {
  pointer-events: auto;
}

.hf-wordmark {
  display: flex;
  align-items: center;
  /* ×1.6 of the original 17px — was reading as a small service label next
     to DRAMIONE rather than the brand mark. Header padding/position is
     untouched; this only makes the flex item itself taller, and
     align-items:center on .hf-header keeps it vertically centered against
     DRAMIONE, which stays at its own unchanged size. */
  height: 27px;
}
.hf-wordmark:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 4px;
  border-radius: 2px;
}
/* Final MVP wordmark asset (hyperfic-logo.svg) — colors/geometry/the
   stitch-I are baked into the file itself, never overridden here. Only
   height is set; width is intentionally omitted so the browser scales
   the image proportionally from its own viewBox aspect ratio instead of
   stretching either axis. */
.hf-wordmark__logo {
  display: block;
  height: 27px;
  width: auto;
}

/* Static label, not a control — MVP has nothing for it to open. */
.hf-fandom {
  display: flex;
  align-items: center;
  font-size: 12px;
  letter-spacing: 0.18em;
  color: var(--hf-ivory-55);
}
/* Very deliberately the only default-state hover in the app that isn't
   plain ivory — a barely-there hint of the hidden magic/Dramione layer. */
.hf-fandom:hover { color: var(--accent-magic); }

/* DRAMIONE (.hf-fandom.hf-stage__fandom) is now a plain flex child of
   .hf-header — justify-content:space-between puts it at the header's
   right edge, .hf-wordmark at the left. align-self:flex-start overrides
   .hf-header's own align-items:center: without it, the row centers
   DRAMIONE's own shorter line-box against .hf-wordmark's explicit 27px
   height and it lands ~6px lower than before — flex-start instead pins
   it to the row's own top (== .hf-header's padding-top), which is
   exactly where the old hardcoded `top: 36px` used to put it, so the
   pixel position is unchanged even though the mechanism is now flex, not
   absolute. The .hf-stage__fandom class name is kept in the markup
   purely to keep this diff minimal, even though it no longer lives in
   .hf-stage. Living in the header (which never scrolls) rather than
   .hf-stage (which scrolls its own overflow) is what makes it move as
   one unit with HYPERFIC instead of scrolling away independently — see
   .hf-header.is-panel-open below for how it still stays clear of the
   drawer on desktop. */
.hf-stage__fandom {
  align-self: flex-start;
}

/* ============================================================
   LAYOUT — desktop: two-column flex row (stage | filter panel).
   The panel column animates its flex-basis (0 ↔ 524px) instead of
   translateX; the stage is flex:1, so it re-centers on its own as
   the panel grows/shrinks — no JS-computed offsets needed.
   Mobile disables this entirely (see media query): stage goes back
   to absolute inset:0 and the drawer becomes a fixed bottom sheet.
   ============================================================ */
.hf-layout {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
}

/* Mirrors .hf-layout__panel's flex-basis (0 ↔ 524px) as a right inset on
   the header instead — desktop only (the mobile drawer is a fixed-overlay
   bottom sheet, see the @media block, so it never claims horizontal
   space from the header there). Keeps DRAMIONE clear of the open drawer
   the same way it always was when it lived inside .hf-stage (whose own
   width used to shrink for the same reason) — just expressed on the
   header now that DRAMIONE lives there instead. */
@media (min-width: 900px) {
  .hf-header.is-panel-open { padding-right: 568px; }
}

/* ============================================================
   CENTER STAGE — главная функция всегда в центре (своей колонки)
   ============================================================ */
/* flex-direction:column + margin:auto on the actual content (below),
   rather than align-items/justify-content:center on this container —
   centering via align-items/justify-content on an oversized flex item
   overflows the container equally top AND bottom, and the top half of
   that overflow is unreachable/clipped once overflow-y:auto is added
   (the classic flexbox-centering-plus-scroll bug). Auto margins don't
   have that problem: they centre only while there's room, and collapse
   to 0 the moment content is taller than the box, so a long result
   simply starts at the top and everything below it stays reachable by
   scrolling this container. */
.hf-stage {
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
  overflow-x: hidden;
  /* Top/bottom padding is symmetric on purpose: with the margin:auto
     centering below, equal top/bottom padding shrinks the centering
     canvas equally on both sides, so it does NOT shift where short
     content ends up centered (verified: offset-from-viewport-top stays
     identical regardless of the padding value, only symmetric ones
     preserve that). Its real job is the overflow case — it's the "safe
     top padding" a long result scrolls up against instead of running
     under the fixed header, and matching bottom space so the last
     action isn't flush against the viewport edge either. */
  padding: 72px 24px;
}
.hf-stage > .hf-home,
.hf-stage > .hf-result {
  margin-top: auto;
  margin-bottom: auto;
}

/* Filter panel column — width is the only thing that animates. */
.hf-layout__panel {
  flex: 0 0 0px;
  overflow: hidden;
  display: flex;
  justify-content: flex-end;
  transition: flex-basis 360ms var(--hf-ease-sheet);
}
.hf-layout.is-panel-open .hf-layout__panel {
  flex-basis: 524px;
}

/* ============================================================
   HERO  ← место cross-stitch объекта и будущей morph-анимации
   ------------------------------------------------------------
   .hf-hero__art — фиксированная по высоте область (190px desktop /
   140px mobile). Высоту НЕ менять: она держит вертикальный ритм,
   иначе контент ниже поедет. Внутрь монтируется inline <svg> с
   отдельными <path class="stitch"> — по одному на каждый крестик.
   Сейчас область пустая (ассет будет поставлен отдельно).
   ============================================================ */
.hf-home {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 760px;
  max-width: 100%;
}

.hf-hero__art {
  height: 190px;                /* ← зарезервированная высота hero-объекта */
  display: flex;
  align-items: center;
  justify-content: center;
}
.hf-hero__art > svg,
.hf-hero__art > img {
  display: block;
  height: 200px;                /* ассет крупнее области и центрируется по ней */
  width: auto;
  max-width: 100%;
}

.hf-h1 {
  margin: 22px 0 0;
  font-family: var(--hf-font-display);
  font-weight: 700;
  font-size: 66px;
  line-height: 1;
  letter-spacing: -0.035em;
  text-align: center;
  color: var(--hf-ivory-strong);
}

.hf-sub {
  margin: 26px 0 0;
  max-width: 350px;
  font-size: 19px;
  line-height: 1.6;
  color: var(--hf-ivory-50);
  text-align: center;
}

/* ============================================================
   BUTTONS
   Primary — спокойный gunmetal, pill. Металл статичен.
   Один короткий highlight на hover будет добавлен позже в коде:
   точка расширения — .hf-btn::after (сейчас выключена).
   ============================================================ */
.hf-btn {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  height: 62px;
  padding: 0 28px;
  border: var(--hf-border-btn);
  border-radius: var(--hf-radius-pill);
  background: var(--hf-btn-surface);
  box-shadow: var(--hf-shadow-cta-desktop);
  font-family: inherit;
  font-size: 17px;
  font-weight: 500;
  color: var(--text-primary);
  cursor: pointer;
  transition: border-color 160ms ease;
}
.hf-btn__arrow { color: var(--text-primary); }
.hf-btn:hover  { border-color: rgba(201,204,209,0.34); }
.hf-btn:active { transform: translateY(1px); }
/* Outline auto-follows the pill's own border-radius — no separate
   radius needed here. Same steel outline token as the rest of the app's
   focus states (.hf-textlink, .hf-result__warnings-toggle, etc). */
.hf-btn:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
}
.hf-btn:disabled,
.hf-btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: default;
  transform: none;
  border-color: rgba(201,204,209,0.12);
}
/* Зарезервировано под one-shot metallic highlight (не включать сейчас) */
.hf-btn::after { content: none; }

.hf-btn--hero   { margin-top: 34px; width: 322px; padding: 0; justify-content: center; }
.hf-btn--hero .hf-btn__arrow {
  position: absolute;
  right: 24px;
  top: 50%;
  transform: translateY(-50%);
}
.hf-btn--read   { width: 212px; height: 58px; font-size: 16px; box-shadow: none; }
.hf-btn--sticky { width: 100%; padding: 0; box-shadow: none; border: var(--hf-border-btn-cta); }

/* "Слушать" — secondary to "Читать": same pill/height/font family, but an
   outline instead of the primary's filled gunmetal surface, and dimmer
   ivory-76 text (same resting color as .hf-action's secondary actions)
   so the reading CTA stays the visually dominant one. Width sizes to
   content rather than the primary's fixed 212px — "Слушать" is a short
   label and forcing it to match would just add empty pill on both sides. */
.hf-btn--listen {
  width: auto;
  height: 58px;
  padding: 0 24px;
  gap: 9px;
  font-size: 15px;
  color: var(--hf-ivory-76);
  background: transparent;
  border: var(--hf-border-btn-cta);
  box-shadow: none;
}
.hf-btn--listen:hover { color: var(--text-primary); border-color: rgba(201,204,209,0.34); }
.hf-btn__play { flex-shrink: 0; }

/* Secondary — текстовое действие, не конкурирует с primary.
   No underline in any state; text is the same brightness as primary CTA
   text, just smaller and unweighted so it stays clearly secondary. */
.hf-textlink {
  margin-top: 28px;
  padding: 0;
  background: none;
  border: 0;
  font-family: inherit;
  font-size: 15px;
  font-weight: 400;
  color: var(--hf-ivory-strong);
  text-decoration: none;
  cursor: pointer;
}
.hf-textlink:hover {
  opacity: 0.8;
}
.hf-textlink:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 4px;
  border-radius: 2px;
}
.hf-textlink--star {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

/* Hierarchy here comes from component form, not color: a large filled pill
   (Читать) reads as primary, lightweight text (Давай другой / Изменить
   подбор) reads as secondary — both stay in the same ivory family as every
   other clickable action in the app. Steel is for metadata/tags/inactive
   controls/decoration, never for actionable text. Hover is a restrained
   brightness step within ivory, not a family change. Padding + matching
   negative margin enlarges the actual clickable box without changing the
   button's visual footprint or nudging its neighbor in the flex row. */
.hf-action {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: 0;
  padding: 8px 6px;
  margin: -8px -6px;
  font-family: inherit;
  font-size: 15px;
  color: var(--hf-ivory-76);
  cursor: pointer;
  transition: color 140ms ease;
}
.hf-action:hover { color: var(--text-primary); }
.hf-action .hf-glyph { flex-shrink: 0; }
.hf-action:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ============================================================
   DRAWER (desktop) — persistent sidebar column, not an overlay.
   No scrim: the stage stays fully visible and interactive next to it.
   ============================================================ */
.hf-scrim {
  display: none;
}

.hf-drawer {
  position: relative;
  flex: 0 0 524px;
  width: 524px;
  display: flex;
  flex-direction: column;
  background: var(--hf-graphite-panel);
  border-left: var(--hf-border-panel);
  box-shadow: var(--hf-shadow-drawer);
}
.hf-drawer__top {
  display: flex;
  justify-content: flex-end;
  padding: 26px 34px 0;
}
.hf-drawer__close {
  background: none; border: 0; padding: 0;
  font-size: 19px;
  line-height: 1;
  color: var(--text-meta);
  cursor: pointer;
}
.hf-drawer__close:hover { color: var(--text-primary); }
.hf-drawer__close:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 4px;
}

.hf-drawer__scroll {
  flex: 1;
  overflow-y: auto;
  padding: 16px 46px 26px;
}
.hf-drawer__foot {
  padding: 18px 46px 30px;
  border-top: var(--hf-border-hairline);
  background: var(--hf-graphite-panel);
}

.hf-h3 {
  margin: 0;
  font-family: var(--hf-font-display);
  font-weight: 700;
  font-size: 36px;
  line-height: 1.12;
  letter-spacing: -0.03em;
  color: var(--text-primary);
}
.hf-drawer__sub {
  margin: 0;
  font-size: 16px;
  line-height: 1.55;
  color: var(--text-muted);
}

.hf-drawer__subrow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-top: 14px;
}
.hf-reset-link {
  flex-shrink: 0;
  margin-top: 2px;
  background: none;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: 13px;
  color: var(--text-meta);
  text-decoration: underline;
  text-decoration-color: rgba(163, 171, 179, 0.3);
  cursor: pointer;
  animation: hfFade 200ms ease both;
}
.hf-reset-link:hover { color: var(--stitch-bright); }
.hf-reset-link:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ============================================================
   FILTER GROUPS
   ============================================================ */
.hf-group { margin-top: 30px; }
.hf-group__head {
  display: flex;
  align-items: center;
  gap: 11px;
}
.hf-group__title {
  font-size: 17px;
  font-weight: 500;
  color: var(--text-primary);
}

/* Level-1 (micro) easter egg — reserved-height slot so it never shifts
   layout when it appears/disappears. */
/* Hyperfic's one shared "reaction" voice — micro quips (next to a filter
   question), combo quips (above the sticky CTA) and the result screen's
   match-reason are the same character speaking, not three separate content
   types: same family/style/weight/color, close size/line-height. A reader
   should recognize the voice by typography, not just by the green. Layout
   (position, max-width, animation, text-align) stays per-instance below —
   only the voice itself is shared. Any future reactive text should join
   this selector list rather than inventing its own treatment. */
.hf-micro-quip,
.hf-combo-quip,
.hf-result__why {
  font-family: var(--hf-font-ui);
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--accent-magic);
}

.hf-micro-quip-slot {
  height: 20px;
  margin-top: 8px;
}
.hf-micro-quip {
  display: inline-block;
  font-size: 13px;
  animation: hfMicroQuipIn 260ms ease both;
}

/* Level-2 (combo) easter egg — above the sticky CTA, replaces itself. */
.hf-combo-quip {
  margin: 0 0 10px;
  font-size: 13.5px;
  text-align: center;
  animation: hfFade 260ms ease both;
}

/* ---------- STITCH SLIDER ----------
   Track height is the real clickable/drag hit area (≥40px) — the 1px rail
   and small × ticks are purely visual and stay centered inside it. Ticks
   and labels share one repeat(N, 1fr) grid (template set inline per
   instance, see stitchSlider.js) so a tick and its label always land in
   the same column, no manual per-item offsets needed. */
.hf-stitch-slider { margin-top: 22px; }
.hf-stitch-slider__track {
  position: relative;
  height: 40px;
}
.hf-stitch-slider__rail {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 2px;
  background: var(--stitch-default);
  opacity: 0.3;
  transform: translateY(-50%);
}
/* The "already stitched" portion — soft ivory (text-body), ends exactly at
   the selected tick's own column center (same math the thumb/ticks use).
   Width is 0 in the neutral state, so the whole rail reads as uniformly
   cold/muted with nothing to suggest a value has been picked. Softer than
   full-strength ivory on purpose — this is an ordinary control, not a
   headline — the contrast with the steel remainder still carries the read. */
.hf-stitch-slider__rail-fill {
  position: absolute;
  left: 0; top: 50%;
  height: 2px;
  width: 0%;
  background: var(--text-body);
  transform: translateY(-50%);
  transition: width 180ms ease;
}
.hf-stitch-slider__ticks {
  position: absolute;
  inset: 0;
  display: grid;
  align-items: center;
}
.hf-stitch-slider__tick {
  justify-self: center;
  width: 12px;
  height: 12px;
  transform: scale(1);
  transition: transform 180ms ease;
  font-family: var(--hf-font-stitch);
}
.hf-stitch-slider__tick::before {
  content: "×";
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
  color: var(--stitch-default);
  opacity: 0.4;
  transition: color 180ms ease, opacity 180ms ease;
}
/* Stops already passed on the way to the current value sit on the ivory
   portion of the rail — a real color shift (steel → ivory), matching the
   rail-fill's direction but at full strength, since a small glyph needs
   more punch than a line to read clearly. Never accent-magic green: this
   is an ordinary UI state. */
.hf-stitch-slider__tick.is-filled::before {
  color: var(--text-primary);
  opacity: 1;
}
.hf-stitch-slider__tick:hover,
.hf-stitch-slider__tick.is-near {
  transform: scale(1.25);
}
/* ~19px footprint (3×3 stitch-star grid, cell size set in stitchSlider.js) —
   clearly larger than any ordinary 12px tick, so the current stop never
   gets lost among the others. */
.hf-stitch-slider__thumb {
  position: absolute;
  top: 50%;
  width: 19px;
  height: 19px;
  margin-left: -9.5px;
  transform: translateY(-50%);
  left: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  transition: left 180ms ease;
}
.hf-stitch-slider__thumb .hf-glyph { pointer-events: none; }
.hf-stitch-slider__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  z-index: 2;
}
.hf-stitch-slider__input:focus-visible ~ .hf-stitch-slider__thumb {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 999px;
}
.hf-stitch-slider__labels {
  display: grid;
  /* Tighter than before on purpose — rail + labels should read as one
     control, not a divider with a caption floating under it. */
  margin-top: 7px;
}
.hf-stitch-slider__label {
  background: none;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: 12px;
  line-height: 1.3;
  text-align: center;
  /* Inactive options are not disabled — text-meta reads at ~5:1 against
     the panel background, well clear of the ~4.5:1 floor. Selected still
     reads unambiguously brighter via .is-active → text-primary (ivory). */
  color: var(--text-meta);
  cursor: pointer;
  transition: color 140ms ease;
}
.hf-stitch-slider__label:hover { color: var(--stitch-bright); }
.hf-stitch-slider__label.is-active { color: var(--text-primary); }
.hf-stitch-slider__label:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------- CHIPS (pill) ---------- */
.hf-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}
.hf-chip {
  font-family: inherit;
  font-size: 14px;
  padding: 10px 14px;
  border-radius: var(--hf-radius-pill);
  background: var(--hf-chip-surface-off);
  border: var(--hf-border-soft);
  color: var(--text-meta);
  cursor: pointer;
  transition: border-color 140ms ease, color 140ms ease, background 140ms ease;
}
.hf-chip:hover {
  border-color: rgba(163,171,179,0.4);
  color: var(--stitch-bright);
}
.hf-chip:active { transform: translateY(1px); }
/* Outline auto-follows the pill's own border-radius. */
.hf-chip:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 2px;
}
.hf-chip[aria-pressed="true"],
.hf-chip[aria-checked="true"] {
  /* Border + text carry the selected state; the fill stays close to the
     unselected surface so a row of several selected chips (e.g. multi-pick
     Draco traits) never reads as one heavy gray block. */
  background: rgba(241, 231, 216, 0.045);
  border: var(--hf-border-chip-on);
  color: var(--text-primary);
}
.hf-chip__x {
  display: inline-block;
  margin-right: 6px;
  color: var(--hf-steel);
  opacity: 0.8;
}
.hf-chip:disabled,
.hf-chip[aria-disabled="true"] {
  opacity: 0.4;
  cursor: default;
  transform: none;
}

/* ---------- Tropes "Ещё +N" ---------- */
.hf-tropes-more {
  display: block;
  margin-top: 12px;
  background: none;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: 13px;
  color: var(--text-meta);
  text-decoration: underline;
  text-decoration-color: rgba(163, 171, 179, 0.3);
  cursor: pointer;
}
.hf-tropes-more:hover { color: var(--stitch-bright); }
.hf-tropes-more:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------- «Все параметры +» ---------- */
.hf-more {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  margin-top: 34px;
  padding: 20px 0 0;
  background: none;
  border: 0;
  border-top: var(--hf-border-hairline);
  font-family: inherit;
  cursor: pointer;
}
.hf-more__label { font-size: 16px; color: var(--text-meta); }
.hf-more__sign  { font-size: 20px; line-height: 1; color: var(--text-meta); }
.hf-more:hover .hf-more__label { color: var(--stitch-bright); }
.hf-more:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 4px;
}

.hf-extra {
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  animation: hfRise 320ms ease both;
}
.hf-extra__title {
  font-size: 11px;
  letter-spacing: 0.2em;
  color: var(--text-muted);
}
.hf-extra .hf-chips { margin-top: 14px; }
/* Tropes/Ending/Status inside "Для фанатов" reuse .hf-group for the
   title+glyph treatment, but .hf-extra's own flex gap already spaces them —
   an extra 30px group margin on top would double it up. */
.hf-extra .hf-group { margin-top: 0; }
/* Now the last item inside .hf-extra's own flex column — its flex `gap`
   already spaces it from ИСКЛЮЧИТЬ above, same as every other advanced
   filter block, so the checkbox's own margin-top would double it up. */
.hf-extra .hf-checkbox { margin-top: 0; }

/* ---------- Audio-only checkbox ----------
   Light, single-row constraint — deliberately not a .hf-group (no glyph,
   no title row, no section treatment) and not another chip (a real
   checkbox reads as "an extra always-on constraint", not "one more
   pickable value" among the trope/ending/status chips above it). No new
   color/radius/spacing token: box and checked-state reuse the exact chip
   tokens (--hf-border-soft/--hf-chip-surface-off off-state,
   --hf-border-chip-on + the same 0.045 ivory fill checked), so the
   checked box reads exactly as calm as a selected chip, no new accent. */
.hf-checkbox {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 20px;
  padding: 0;
  background: none;
  border: 0;
  font-family: inherit;
  font-size: 14px;
  color: var(--text-meta);
  cursor: pointer;
}
.hf-checkbox:hover { color: var(--stitch-bright); }
.hf-checkbox:hover .hf-checkbox__box { border-color: rgba(163,171,179,0.4); }
.hf-checkbox:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 4px;
}
.hf-checkbox__box {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: var(--hf-radius-thumb);
  border: var(--hf-border-soft);
  background: var(--hf-chip-surface-off);
  color: transparent;
  transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
}
.hf-checkbox[aria-checked="true"] .hf-checkbox__box {
  border: var(--hf-border-chip-on);
  background: rgba(241, 231, 216, 0.045);
  color: var(--text-primary);
}
.hf-checkbox[aria-checked="true"] .hf-checkbox__label { color: var(--text-primary); }

/* ---------- Zero-match helper under the (disabled) sticky CTA ---------- */
.hf-drawer__hint {
  margin: 10px 0 0;
  font-size: 13px;
  line-height: 1.4;
  text-align: center;
  color: var(--text-muted);
}

/* ============================================================
   RESULT
   ============================================================ */
.hf-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 980px;
  max-width: 100%;
}
/* Стежковая линия-«нить» над результатом */
.hf-result__rule {
  height: 36px;
  display: flex;
  align-items: center;
}
.hf-result__meta {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 11px;
  letter-spacing: 0.24em;
  color: var(--text-muted);
  animation: hfFade 340ms ease both;
}
.hf-result__titlewrap {
  min-height: 96px;
  margin-top: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Название фанфика — same display family as home's H1 and the drawer's H3
   (var(--hf-font-display)), so the app reads as one typographic voice: the
   thing HYPERFIC says (headings) and the thing it found (the title) share
   a family, just at different sizes/weights for hierarchy. Golos Text
   stays the body/UI voice everywhere else.
   font-size/line-height below are the pre-JS default (== the largest tier)
   — fitResultTitle() in resultView.js measures actual rendered line count
   and overrides both via inline style, stepping down through a fixed set
   of tiers until the title fits within ~2 lines (or hits the smallest
   tier, which is allowed to run longer rather than shrink indefinitely).
   It's never sized off raw character count — a short title in a narrow
   viewport and a long one in a wide container need different answers, and
   only measuring the actual layout gets that right. Tiers are tuned for
   Unbounded's own (wider-than-Golos-Text) glyph metrics. */
.hf-result__title {
  font-family: var(--hf-font-display);
  font-weight: 600;
  font-size: 72px;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-align: center;
  color: var(--text-primary);
  padding: 0 4px;
  max-width: 780px;
  /* Never break inside a word — wrap by whole word only, however long the
     title is. */
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
}
.hf-result__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  animation: hfRise 420ms ease both;
}
/* One calm centered metadata line — "Original title · author" (or just
   author when there's no original title) — rather than a decorative dash
   pairing two differently-styled elements. Same size/tracking/color for
   both halves so the author never reads louder than the title next to it.
   max-width keeps a long original title wrapping onto a second line by
   whole words, same as before. */
.hf-result__byline {
  margin-top: 6px;
  max-width: 420px;
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: 0.04em;
  color: var(--text-meta);
  text-align: center;
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
}
.hf-result__desc {
  margin: 22px 0 0;
  max-width: 560px;
  font-size: 17px;
  line-height: 1.7;
  color: var(--text-body);
  text-align: center;
  text-wrap: pretty;
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
}
.hf-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 20px;
  max-width: 640px;
}
.hf-tag {
  font-size: 13px;
  padding: 7px 12px;
  border-radius: var(--hf-radius-pill);
  background: var(--hf-chip-surface-off);
  border: var(--border-neutral);
  color: var(--text-meta);
}

/* ---------- Content warnings disclosure ----------
   Service information, not a fact-card — no surface, no pill, no border,
   no glow, and deliberately never accent-magic/red/ivory: warnings stay in
   the cold-steel "interface" register, same as any other metadata. Renders
   nothing at all when a fic has no warnings (see resultView.js). */
.hf-result__warnings {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.hf-result__warnings-toggle {
  background: none;
  border: 0;
  /* Padding + matching negative margin keeps the visible row small while
     giving the button a real ~44px tap target — same trick as .hf-action.
     line-height is pinned so the target height is predictable at both the
     13px desktop and 12px mobile sizes. */
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 15px 10px;
  margin: -15px -10px;
  font-family: inherit;
  font-weight: 400;
  font-size: 13px;
  line-height: 1.2;
  letter-spacing: 0.01em;
  color: var(--text-meta);
  cursor: pointer;
  transition: color 140ms ease;
}
.hf-result__warnings-toggle:hover { color: var(--stitch-bright); }
.hf-result__warnings-toggle:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: 3px;
  border-radius: 4px;
}
.hf-result__warnings-label { display: inline-block; }
/* Drawn, not Unicode, so it stays crisp and centers exactly on the text
   baseline; rotates in place instead of swapping glyphs. */
.hf-result__warnings-chevron {
  display: block;
  flex: none;
  width: 11px;
  height: 11px;
  color: inherit;
  transform: rotate(0deg);
  transition: transform 190ms ease;
}
.hf-result__warnings-chevron.is-open { transform: rotate(180deg); }
/* Height/opacity transition via the CSS grid-rows trick (0fr → 1fr) — no
   JS height measurement needed to animate to the content's intrinsic
   height. */
.hf-result__warnings-panel {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  width: 100%;
  max-width: 680px;
  transition: grid-template-rows 200ms ease, opacity 200ms ease;
}
.hf-result__warnings-panel.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
}
.hf-result__warnings-list {
  margin: 0;
  overflow: hidden;
  min-height: 0;
  padding-top: 6px;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.45;
  text-align: center;
  /* Same muted-steel token as everything else in this tier — the extra-dim
     one-off rgba this used before read as disabled/inactive rather than
     "quiet metadata", which fought against actually wanting the content
     read. */
  color: var(--text-muted);
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
}
/* tags/metadata (info about the work) vs. the green Hyperfic reaction
   (Hyperfic's own voice) need to read as two different tiers, not two
   adjacent captions — extra breathing room only kicks in when a warnings
   block actually sits between them, collapsed or expanded alike. */
.hf-result__warnings + .hf-result__why { margin-top: 30px; }

/* ---------- "Почему тебе" ----------
   A single small editorial remark, not a section — no label, no card, no
   line, no border. Centered like a caption, wrapping as whole words same
   as everything else. Voice (family/style/weight/color) comes from the
   shared .hf-micro-quip/.hf-combo-quip/.hf-result__why rule above — this
   is the same character speaking as the filter-drawer quips, just laid
   out centered instead of inline. Only size/line-height/layout live here. */
.hf-result__why {
  margin: 14px 0 0;
  max-width: 480px;
  text-align: center;
  font-size: 14px;
  line-height: 1.45;
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
  opacity: 0;
  transform: translateY(3px);
  animation: hfWhyIn 260ms ease-out both;
  animation-delay: 80ms;
}
@keyframes hfWhyIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
/* Reaction → CTA needs more air than any other gap on the result screen —
   it's the last beat before the primary action, not another content line.
   Margin-top on the CTA (not a fixed-height wrapper around the reaction)
   means this is measured from wherever the text actually ends, one or two
   lines alike. Base .hf-result__cta margin-top below covers the no-reaction
   case unchanged. */
.hf-result__why + .hf-result__cta { margin-top: 52px; }

.hf-result__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 36px;
}
/* Wraps the "Читать" button + its source popover. inline-flex shrinks the
   wrapper to the button's own width (previously it was width:100%, which
   made the button hug the wrapper's left edge instead of sitting centered
   under the rest of the result content) — the popover's left:0 then lines
   up with the button itself, not the wrapper. */
.hf-result__read {
  position: relative;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  /* Only ever visible when a second (audio) button is actually present —
     a lone "Читать" button has nothing to gap against. */
  gap: 14px;
}
.hf-result__actions {
  display: flex;
  /* +12px over the intended 26px visual gap — each .hf-action's own -6px
     horizontal margin (enlarged hit area) eats into it from both sides. */
  gap: 38px;
  margin-top: 26px;
}

/* Поповер выбора источника (Ficbook / AO3) */
.hf-sources {
  position: absolute;
  top: 66px; left: 0;
  width: 212px;
  overflow: hidden;
  background: var(--hf-graphite-menu);
  border: var(--hf-border-line);
  border-radius: var(--hf-radius-menu);
  box-shadow: var(--hf-shadow-menu);
  animation: hfFade 200ms ease both;
  z-index: 5;
}
.hf-sources__item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  background: none;
  border: 0;
  font-family: inherit;
  font-size: 14px;
  color: var(--hf-ivory-76);
  cursor: pointer;
}
.hf-sources__item:hover {
  background: var(--hf-graphite-hover);
  color: var(--hf-ivory);
}
.hf-sources__item:focus-visible {
  outline: 1px solid var(--hf-steel-70);
  outline-offset: -2px;
}

/* ============================================================
   STITCH GLYPHS (иконки фильтров, × в вордмарке, линия-нить)
   Рендерятся сеткой из символов × — см. StitchGlyph в Hyperfic.jsx
   ============================================================ */
.hf-glyph {
  display: grid;
  font-family: var(--hf-font-stitch);
  line-height: 1;
}
.hf-glyph > i {
  font-style: normal;
  text-align: center;
}

/* ============================================================
   ANIMATIONS — только входы состояний. Никаких loop-анимаций.
   ============================================================ */
@keyframes hfRise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
@keyframes hfFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes hfSheet { from { transform: translateY(24px); opacity: 0.6; } to { transform: none; opacity: 1; } }
@keyframes hfMicroQuipIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .hf-result__body, .hf-result__meta, .hf-extra, .hf-sheet, .hf-sources { animation: none !important; }
  /* .hf-result__why's base rule sets opacity:0/translateY(3px) as the
     animation's starting point, not just the keyframe's "from" — disabling
     the animation alone would leave it stuck invisible, so the end state is
     restored explicitly. Content is unchanged, it just appears immediately
     with everything else instead of fading in afterward. The stars' own
     resting opacity (set per-cell in resultView.js, quieter than the full-
     opacity text) is untouched — it was never part of the animation. */
  .hf-result__why {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .hf-result__warnings-panel,
  .hf-result__warnings-panel.is-open,
  .hf-result__warnings-chevron {
    transition: none !important;
  }
  .hf-result__warnings-panel.is-open {
    grid-template-rows: 1fr;
    opacity: 1;
  }
}

/* Переключатели desktop/mobile-разметки (верхняя строка шторки) */
.hf-only-mobile { display: none; }

/* ============================================================
   MOBILE  (< 900px) — home остаётся центрированным,
   фильтры открываются снизу как full-height sheet
   ============================================================ */
@media (max-width: 899px) {
  .hf-only-desktop { display: none; }
  .hf-only-mobile { display: block; }

  .hf-grain { opacity: 0.5; background-image: var(--hf-grain-mobile); }

  .hf-header-fade { height: 100px; }
  .hf-header { padding: 24px 24px 0; }
  /* Close to the same ×1.6 factor as desktop (14px → 22px) — verified at
     320px width there's still a comfortable gap before DRAMIONE, so no
     need to undershoot for safety. */
  .hf-wordmark { height: 22px; }
  .hf-wordmark__logo { height: 22px; }
  .hf-fandom { font-size: 11px; letter-spacing: 0.16em; }

  /* Two-column desktop layout is disabled entirely: stage fills the
     screen again and the panel reverts to a fixed bottom-sheet overlay. */
  .hf-layout { position: static; display: block; }
  .hf-stage { position: absolute; inset: 0; flex: none; padding: 56px 28px; }
  .hf-layout__panel { display: contents; }
  .hf-home { width: 100%; }

  .hf-hero__art { height: 140px; }             /* ← mobile-высота hero-области */
  .hf-hero__art > svg,
  .hf-hero__art > img { height: 164px; object-fit: contain; }

  .hf-h1 { margin-top: 26px; font-size: 38px; line-height: 1.08; letter-spacing: -0.03em; text-wrap: balance; }
  .hf-sub { margin-top: 18px; max-width: 250px; font-size: 15px; color: var(--hf-ivory-48); }

  .hf-btn { height: 56px; font-size: 16px; box-shadow: var(--hf-shadow-cta-mobile); }
  .hf-btn--hero { margin-top: 36px; width: 100%; max-width: 302px; }
  .hf-btn--read { width: 100%; height: 54px; box-shadow: none; }
  .hf-result__read { display: flex; width: 100%; gap: 10px; }
  /* Two-up: "Читать" and "Слушать" split the row evenly instead of the
     lone-button 100% width above. Tighter padding/font than desktop's
     .hf-btn--listen so both labels + the play icon stay comfortably clear
     of each other down to real small-phone widths (this card is already
     capped at max-width:302px below). */
  .hf-result__read.has-audio .hf-btn--read,
  .hf-result__read.has-audio .hf-btn--listen {
    width: auto;
    flex: 1 1 0;
    min-width: 0;
    padding: 0 14px;
    font-size: 14px;
  }
  .hf-btn--listen { height: 54px; }
  .hf-textlink { margin-top: 24px; font-size: 14px; }

  /* Bottom sheet вместо drawer */
  .hf-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 3;
    background: var(--hf-scrim-sheet);
  }
  .hf-drawer {
    position: fixed;
    top: auto; left: 0; right: 0; bottom: 0;
    z-index: 4;
    width: auto;
    flex: none;
    /* Capped, not stretched: sizes to content up to this ceiling, so a
       short filter set doesn't force a tall sheet with empty scroll
       space. Two declarations, same progressive-enhancement pattern as
       .hf-screen's min-height — the vh/no-safe-area line is the fallback
       for engines without dvh/env() support, the second overrides it
       where available. Anchored to the real visual viewport (fixed, not
       .hf-screen's dvh-based box) and shrunk by both safe-area insets so
       the sheet's own top/handle/close/title can never be pushed past
       the top of the visible area regardless of a browser's own chrome
       height (see hyperfic.css .hf-scrim comment for the underlying bug
       this and the scrim's position:fixed both address). */
    max-height: min(788px, calc(100vh - 24px));
    max-height: min(788px, calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 24px));
    border-left: 0;
    border-top: var(--hf-border-panel);
    border-radius: var(--hf-radius-sheet) var(--hf-radius-sheet) 0 0;
    box-shadow: var(--hf-shadow-sheet);
    animation: hfSheet 320ms var(--hf-ease-sheet) both;
  }
  /* На мобильном верхняя строка шторки: Закрыть слева, ручка по центру.
     "Сбросить" переехал в hf-drawer__subrow — общий с desktop. */
  .hf-drawer__top {
    position: relative;
    justify-content: flex-start;
    align-items: center;
    padding: 16px 22px 0;
  }
  .hf-sheet__handle {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 38px; height: 3px;
    border-radius: var(--hf-radius-thumb);
    background: rgba(241,231,216,0.16);
  }
  .hf-sheet__text {
    background: none; border: 0; padding: 0;
    font-family: inherit;
    font-size: 14px;
    color: var(--text-meta);
    cursor: pointer;
  }
  .hf-sheet__text--muted { color: var(--text-muted); }
  .hf-sheet__text:hover { color: var(--text-primary); }
  .hf-sheet__text:focus-visible {
    outline: 1px solid var(--hf-steel-70);
    outline-offset: 3px;
    border-radius: 2px;
  }

  .hf-drawer__scroll { padding: 24px 22px 20px; }
  .hf-drawer__foot { padding: 14px 22px max(26px, calc(env(safe-area-inset-bottom) + 16px)); }

  .hf-h3 { font-size: 26px; line-height: 1.15; letter-spacing: -0.025em; }
  .hf-drawer__sub { margin-top: 0; font-size: 14px; }
  .hf-drawer__subrow { margin-top: 12px; }
  .hf-reset-link { font-size: 12px; }

  .hf-group { margin-top: 24px; }
  .hf-group__head { gap: 10px; }
  .hf-group__title { font-size: 15px; }
  .hf-micro-quip-slot { height: 18px; margin-top: 6px; }
  .hf-micro-quip { font-size: 12px; }

  .hf-stitch-slider { margin-top: 18px; }
  /* Track height stays ≥40px on mobile too — it's the real touch target;
     only the visual thumb/label/tick presentation changes below. */
  .hf-stitch-slider__labels { margin-top: 9px; }
  /* Slightly larger + higher-contrast than the desktop scale so it doesn't
     read as a shrunk-down desktop control; selected still reads clearly
     brighter via .is-active → var(--text-primary), untouched here. */
  /* Color now inherits the base rule's text-meta — already at the fixed
     contrast target, no separate mobile boost needed anymore. */
  .hf-stitch-slider__label { font-size: 13px; line-height: 1.3; }
  /* No extra mobile scale-up needed anymore — the base ~19px star already
     reads clearly at mobile viewing distance on its own. */

  .hf-chips { gap: 7px; margin-top: 14px; }
  .hf-chip { font-size: 13px; padding: 9px 13px; }

  .hf-tropes-more { font-size: 12px; }

  .hf-more { margin-top: 26px; padding-top: 18px; }
  .hf-more__label { font-size: 15px; }
  .hf-more__sign { font-size: 18px; }
  .hf-extra { margin-top: 18px; gap: 18px; }
  .hf-extra .hf-chips { margin-top: 12px; }

  .hf-drawer__hint { font-size: 12px; }
  .hf-combo-quip { font-size: 13px; }

  .hf-result { width: 100%; }
  .hf-result__rule { height: 26px; }
  .hf-result__meta { margin-top: 16px; font-size: 10px; letter-spacing: 0.2em; }
  .hf-result__titlewrap { margin-top: 12px; }
  .hf-result__title { font-size: 38px; line-height: 1.1; }
  .hf-result__byline { margin-top: 4px; max-width: 280px; font-size: 12px; letter-spacing: 0.03em; }
  .hf-result__desc { margin-top: 20px; font-size: 15px; }
  .hf-tags { gap: 6px; margin-top: 20px; }
  .hf-tag { font-size: 12px; padding: 6px 11px; }
  .hf-result__warnings { margin-top: 16px; }
  .hf-result__warnings-toggle { font-size: 12px; gap: 6px; }
  .hf-result__warnings-chevron { width: 10px; height: 10px; }
  .hf-result__warnings-panel { max-width: 300px; }
  .hf-result__warnings-list { font-size: 12px; line-height: 1.45; }
  .hf-result__warnings + .hf-result__why { margin-top: 26px; }
  .hf-result__why { margin-top: 12px; max-width: 300px; font-size: 13px; line-height: 1.4; }
  .hf-result__why + .hf-result__cta { margin-top: 42px; }
  .hf-result__cta { width: 100%; max-width: 302px; margin-top: 28px; }
  .hf-result__actions { gap: 34px; margin-top: 24px; }
  .hf-action { font-size: 14px; gap: 7px; }

  /* Источники под кнопкой, а не поповером */
  .hf-sources {
    position: static;
    width: auto;
    margin-top: 8px;
    box-shadow: none;
  }
  .hf-sources__item { padding: 13px 15px; }
}

/* Real small-phone fallback: if "Читать" + "Слушать" side by side would get
   too cramped, stack them instead of shrinking text further. 359px picked
   so the two-up row above still applies at the common 375-390px phone
   widths (where it has plenty of room) and only the genuinely narrow tier
   (≤iPhone SE-class, 320px) drops to one column. */
@media (max-width: 359px) {
  .hf-result__read.has-audio {
    flex-direction: column;
    width: 100%;
  }
  .hf-result__read.has-audio .hf-btn--read,
  .hf-result__read.has-audio .hf-btn--listen {
    width: 100%;
    flex: none;
  }
}
