/* ==========================================================================
   Vadim Gorbunov — Photographer Portfolio
   Design system: editorial fashion photography — full-bleed photo,
   integrated typography, minimal floating nav
   Palette: warm dark + burnt-terracotta (reference-based)

   Applied rules from the Claude DESIGN.md:
   · Single serif weight for headlines — never bold, never light
   · Body line-height 1.60 — literary pacing, never below 1.40
   · Headings 1.05–1.14 — serifs need breathing room, not compression
   · Every neutral carries the warm undertone — no cool grays
   · Ring shadows (0 0 0 1px) instead of heavy drop shadows
   · Radius scale 8 / 12 / 16 / 24 / 32 — nothing sharp
   · Sans for UI / labels / body, serif for headlines
   ========================================================================== */

:root {
  /* ── Warm near-black surfaces ──
     The reference is dark, but the dark is warm: it leans brown, not blue
     or green. The whole site reads as one warm material. */
  --bg: #0d0c0a;            /* deepest warm black */
  --bg-soft: #141210;       /* lifted surface for alternating sections */
  --surface: #1c1916;       /* card surface */
  --surface-2: #2a2620;     /* elevated containers, borders */

  /* ── Warm neutrals — every gray has a brown undertone ── */
  --text: #ede5d6;          /* warm ivory */
  --text-soft: #b3a999;     /* body / secondary on dark */
  /* Was #7d7468 — 4.25:1 on the page background, under the 4.5 AA floor,
     which matters most for the small metadata this token is used for. */
  --text-mute: #847b6f;     /* tertiary, metadata — 4.7:1 */
  --line: #2a2620;          /* Border Dark */
  --line-soft: rgba(237, 229, 214, 0.07);
  --line-strong: rgba(237, 229, 214, 0.14);

  /* ── Burnt-terracotta accents (from the reference's headline color) ──
     accent:       warm sienna — text/links on dark surfaces
     accent-solid: deeper terracotta — primary CTA fills
     accent-deep:  brick — pressed/active fills, deep borders */
  --accent: #c2643f;
  --accent-solid: #9c4a2c;
  --accent-deep: #5d2916;
  --accent-dim: rgba(194, 100, 63, 0.14);
  --accent-line: rgba(194, 100, 63, 0.30);

  /* ── Glass (warm-tinted, Claude ring-shadow depth) ── */
  --glass-bg: rgba(237, 229, 214, 0.045);
  --glass-bg-strong: rgba(237, 229, 214, 0.075);
  --glass-border: rgba(237, 229, 214, 0.10);
  --glass-shadow:
    inset 0 1px 0 rgba(237, 229, 214, 0.06),
    0 0 0 1px rgba(237, 229, 214, 0.05),
    0 20px 60px rgba(0, 0, 0, 0.5);
  --ring: 0 0 0 1px rgba(237, 229, 214, 0.09);
  --whisper: 0 4px 24px rgba(0, 0, 0, 0.28);
  --float: 0 24px 64px rgba(0, 0, 0, 0.55);

  /* ── Radius scale (Claude) ── */
  --r-sm: 8px;    /* small buttons, inline containers */
  --r-md: 12px;   /* primary buttons, inputs, nav */
  --r-lg: 16px;   /* cards, featured containers */
  --r-xl: 24px;   /* tags, highlighted containers */
  --r-2xl: 32px;  /* hero containers, large cards, media */

  /* Legacy aliases — admin.css predates the Claude scale and still
     references these. Mono maps to sans: Claude reserves mono for code. */
  --radius: var(--r-lg);
  --radius-lg: var(--r-2xl);
  --radius-pill: var(--r-xl);
  --font-mono: var(--font-sans);
  --shadow-card: var(--float);

  --container: 1280px;
  --easing: cubic-bezier(0.22, 1, 0.36, 1);
  /* Slower tail than --easing: reaches ~90% of the distance in the first third
     of the duration, then eases in for a long, quiet settle. Used for scroll
     entrances, where the "arrive and hold" feel matters most. */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);

  /* ── Typeface stack ──
     All-sans pairing. Bricolage Grotesque is a variable modern grotesque
     with an opsz axis (12 → 96): it tightens into a display cut at large
     sizes. DM Sans is a clean, neutral body and UI face. Both families
     support Cyrillic.
     No serifs anywhere on the site. */
  --font-display: 'Bricolage Grotesque', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-sans: 'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-serif: var(--font-display);  /* legacy alias — used by some selectors */

  /* ── Fluid type scale ──
     A tighter ratio than before (display no longer balloons to 118px while
     h4 stayed at 26px) so the jump between levels reads as deliberate. */
  --fs-display: clamp(46px, 7.4vw, 104px);
  --fs-h2: clamp(34px, 4.4vw, 58px);
  --fs-h3: clamp(26px, 2.6vw, 36px);
  --fs-h4: clamp(20px, 1.7vw, 24px);
  --fs-lead: clamp(17px, 1.35vw, 20px);
  --fs-body: 16.5px;
  --fs-ui: 15px;
  --fs-caption: 14px;
  --fs-label: 12px;
  --fs-overline: 11px;

  /* ── Editorial pacing ── */
  --section-y: clamp(88px, 9vw, 128px);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.60;              /* Claude: never below 1.40 */
  background: var(--bg);
  color: var(--text);
  overflow-x: hidden;
  font-feature-settings: 'ss01', 'cv11';
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; transition: opacity .3s var(--easing), color .3s var(--easing); }
a:hover { opacity: .85; }
button { background: none; border: none; color: inherit; cursor: pointer; font: inherit; }

::selection { background: var(--accent-solid); color: var(--text); }

.container {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 32px;
}

/* ───────────────────────── Glass surface ───────────────────────── */
.glass {
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  box-shadow: var(--glass-shadow);
}

/* ───────────────────────── Buttons ───────────────────────── */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 24px;
  border-radius: var(--r-md);       /* Claude: primary buttons 12px */
  font-size: var(--fs-ui);
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.4;
  transition: transform .35s var(--easing), background .3s var(--easing),
              color .3s var(--easing), box-shadow .3s var(--easing);
  white-space: nowrap;
}
.pill-solid {
  background: var(--accent-solid);
  color: var(--text);
  box-shadow: 0 0 0 0 var(--accent-solid), 0 0 0 1px var(--accent-solid);
}
.pill-solid:hover {
  background: var(--accent-deep);
  transform: translateY(-2px);
  opacity: 1;
  box-shadow: 0 0 0 1px var(--accent-line), 0 8px 24px rgba(194, 100, 63, 0.30);
}
.pill-ghost {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--line-strong);
  backdrop-filter: blur(10px);
}
.pill-ghost:hover {
  background: var(--glass-bg-strong);
  border-color: rgba(237, 229, 214, 0.24);
  opacity: 1;
}

.link-arrow {
  display: inline-flex;
  gap: 8px;
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1.3;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 4px;
  align-self: start;
  transition: color .3s var(--easing), border-color .3s var(--easing);
}
.link-arrow:hover { border-color: var(--accent); color: var(--accent); opacity: 1; }

/* ───────────────────────── Navigation — floating glass pill ───────────────────────── */
.nav {
  position: fixed;
  top: 18px;
  left: 0; right: 0;
  z-index: 50;
  padding: 0 10px;
  pointer-events: none;
}
.nav-inner {
  pointer-events: auto;
  /* Stretch to almost the full viewport — was var(--container) = 1280px, which
     made the plate visibly short on laptops ≥ 1440px wide. 1440px cap keeps
     4K monitors from getting a 3800px-wide bar. */
  max-width: 1440px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 10px 12px 10px 18px;
  border-radius: var(--r-2xl);
  transition: padding .35s var(--easing), background .35s var(--easing),
              box-shadow .35s var(--easing);
}
.nav-brand { display: flex; align-items: center; gap: 12px; isolation: isolate; }
.nav-logo {
  display: block;
  height: 52px;
  width: auto;
  margin: -2px 0;          /* optical alignment — descenders sit on the baseline of the pill */
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;     /* keep the whole pill area clickable, not just the glyph */
}
.nav-brand:hover .nav-logo { opacity: 0.92; }
.brand-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border-radius: var(--r-sm);
  background: var(--accent-solid);
  color: var(--text);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 19px;
  flex-shrink: 0;
}
.brand-name { display: flex; gap: 6px; line-height: 1.2; font-size: 15px; color: var(--text); }
.b-sans { font-weight: 400; }
.b-serif { font-family: var(--font-display); font-weight: 500; }
.brand-mark.big { width: 40px; height: 40px; font-size: 23px; }

.footer-logo {
  display: block;
  height: 44px;
  width: auto;
  opacity: 0.85;
  transition: opacity .25s var(--easing);
}
.footer-brand:hover .footer-logo { opacity: 1; }

.nav-links {
  display: flex; gap: 28px;
  list-style: none;
  margin-left: auto;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--text);
}
.nav-links a {
  position: relative;
  padding: 4px 0;
  opacity: 0.85;
  transition: opacity .25s var(--easing), color .25s var(--easing);
}
.nav-links a::after {
  content: ''; position: absolute; left: 0; bottom: 0;
  width: 0; height: 1px; background: var(--accent);
  transition: width .35s var(--easing);
}
.nav-links a:hover { opacity: 1; color: var(--accent); }
.nav-links a:hover::after { width: 100%; }

/* ── Active-section indicator (scroll spy) ──
   One shared pill that slides between links. Measuring the active link and
   moving this single element is both cheaper and smoother than transitioning
   a background on five separate links — and, more to the point, the pill can
   *travel*, which is the whole effect. Position comes from two custom
   properties written by JS; the scale comes from the same .is-scrolled class
   the links use, so the pill never drifts out of step with them.
   transform and width are lerped by motion.js on rAF (so a fast scroll never
   makes the pill bounce between neighbours — there is a dead-zone hysteresis
   at section edges). CSS only transitions opacity; doing both at once would
   double-animate and stutter whenever a scroll event lands mid-flight. */
.nav-links { position: relative; }

/* The pill is disabled — Vadim (2026-09-15) prefers an underline + bolder
   type for the active link. The element is still on the page (JS still
   writes --ind-x/--ind-w) so we don't have to touch motion.js, but it's
   invisible. */
.nav-indicator { display: none; }

.nav-links a { z-index: 1; }
/* Active link: full opacity, semibold (so it reads as the chosen one
   without changing the underline width and shifting layout), and the
   accent underline is already there via ::after. */
.nav-links a.is-active {
  opacity: 1;
  color: var(--text);
  font-weight: 500;
}
.nav-links a.is-active::after { width: 100%; }

/* Current section, spelled out next to the wordmark: "02 · Работы".
   Absent (and invisible) while the visitor is still in the hero. */
.nav-section {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity .35s var(--easing), transform .45s var(--ease-out-expo);
  pointer-events: none;
}
.nav-section.is-on { opacity: .95; transform: none; }

/* The nav CTA matches the plate's own rounding, not the global pill radius.
   As the plate condenses (32px → 24px) the CTA follows the same arc so the
   two halves read as one shape, not two shapes stuck together. The full
   math lives in #nav-cta below — scroll-driven via var(--nav-t). */

.nav-burger { display: none; flex-direction: column; gap: 4px; margin-left: auto; padding: 8px; }
.nav-burger span { width: 22px; height: 1.5px; background: var(--text); display: block; transition: transform .25s var(--easing), opacity .25s var(--easing); }
.nav-burger.open span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.nav-burger.open span:nth-child(2) { opacity: 0; }
.nav-burger.open span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

/* ───────────────────────── HERO — editorial composition ───────────────────────── */
/* The photo IS the hero. No card, no panel — typography is layered directly
   on the image with a soft bottom scrim for legibility. The composition
   borrows from editorial fashion magazines: massive name in the lower-left,
   thin editorial caption top-right, minimal scroll indicator on the edge. */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  overflow: hidden;
  isolation: isolate;
}
.hero-bg { position: absolute; inset: 0; z-index: -2; }
.hero-bg img, .hero-bg video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Bias the crop toward the upper half — that's where the subject's face
     lives in the current portrait. Centring would push the head down. */
  object-position: center 28%;
  filter: saturate(0.85) contrast(1.05);
  transform: scale(1.04);
  animation: hero-zoom 16s var(--easing) infinite alternate;
}
@keyframes hero-zoom {
  from { transform: scale(1.04); }
  to   { transform: scale(1.12); }
}
.hero-grain {
  position: absolute; inset: 0;
  background-image:
    radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,0.55) 100%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.06 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  opacity: 0.45;
  mix-blend-mode: overlay;
}
.hero-overlay {
  position: absolute; inset: 0; z-index: -1;
  /* Two-stop shade: transparent top so the photo breathes, dense dark
     bottom so the typography stays legible. No middle band — a mid-tone
     wedge would flatten the photo. */
  background: linear-gradient(180deg,
    rgba(13,12,10,0.25) 0%,
    rgba(13,12,10,0)    32%,
    rgba(13,12,10,0.05) 55%,
    rgba(13,12,10,0.78) 100%);
}

/* The content block floats in the lower-left, never centered. */
.hero-content {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 0 clamp(20px, 4vw, 48px) clamp(56px, 9vh, 96px);
  max-width: 1400px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
  z-index: 2;
}
.hero-content > * { margin: 0; }

.hero-kicker {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  line-height: 1.6;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 14px;
  width: max-content;
}
.hero-kicker::before {
  content: ''; display: inline-block;
  width: 28px; height: 1px;
  background: var(--accent);
}

.hero-title {
  font-family: var(--font-display);
  font-size: clamp(42px, 7.5vw, 100px);
  font-weight: 600;
  /* Each name on its own line; lines must clear each other even at
     100px uppercase Bricolage, where descenders and accents were
     grazing the surname. 1.04 gives a hair of breathing room without
     breaking the editorial stack. */
  line-height: 1.04;
  letter-spacing: -0.035em;
  margin: 4px 0 0;
  text-transform: uppercase;
  text-wrap: balance;
}
.hero-title .word { display: block; }   /* stack: ВАДИМ / ГОРБУНОВ */
.hero-title .word-1 {
  color: var(--text);
  font-weight: 500;
}
.hero-title .word-2 {
  color: var(--accent);            /* surname carries the warm accent */
  font-weight: 600;
}
/* Second line of the H1: carries «портретный фотограф в Нижнем Новгороде»
   for search engines while staying visually subordinate to the name —
   small, muted, tightly leaded so it reads as a caption, not a headline. */
/* Second line of the H1: «портретный фотограф в Нижнем Новгороде» — the
   only place on the page where the keywords sit inside the H1, so Yandex
   matches «фотограф Нижний Новгород» against the heading itself.

   Visually it the same as the visible tagline below it («Портретный
   фотограф»), which is what Vadim flagged on 2026-09-15. The tagline is
   content from the admin and must not be touched, so the cleanest fix is
   to keep the SEO line in the DOM but take it off screen — search engines
   read it, the eye doesn't. The visually-hidden pattern is the standard
   accessible way to do this; it is exposed to screen readers too. */
.hero-title-sub {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.hero-tagline {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-h4);
  line-height: 1.45;
  color: var(--text-soft);
  max-width: 44ch;
  margin-top: 4px;
}

.hero-tags {
  display: flex; gap: 8px; flex-wrap: wrap;
  list-style: none;
  margin-top: 4px;
}
.hero-tags li {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1.4;
  padding: 7px 14px;
  border-radius: 999px;
  background: rgba(13, 12, 10, 0.42);
  color: var(--text);
}

.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 14px; }

/* Right-edge scroll indicator — rotated vertical text + an animated line.
   Pulled inside the hero's right padding so the "S" of SCROLL isn't clipped
   by the hero's overflow:hidden. */
.hero-scroll {
  position: absolute;
  right: clamp(24px, 4vw, 44px);
  bottom: 56px;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--text);
  z-index: 2;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  text-decoration: none;
}
.hero-scroll-line {
  display: block;
  width: 1px; height: 48px;
  background: linear-gradient(180deg, var(--text), transparent);
  transform-origin: top center;
  animation: scroll-bob 2.4s ease-in-out infinite;
}
@keyframes scroll-bob {
  0%,100% { transform: scaleY(0.4); opacity: .55; }
  50%     { transform: scaleY(1);   opacity: 1; }
}

/* ───────────────────────── Let's Connect — Streetwear Editorial Carousel ───────────────────────── */
/* Horizontal auto-scrolling marquee inspired by editorial streetwear brand websites (Patta).
   Cards bend in an upward arc (convex curve) where the center card sits highest and largest,
   while outer cards tilt outwards along the arc and drop down. Includes hover pause,
   drag scrubbing, seamless 3x loop, and email signup form below. */
.connect-section {
  position: relative;
  padding: clamp(54px, 6.5vw, 88px) 0 clamp(44px, 5.5vw, 76px);
  background: radial-gradient(ellipse 70% 40% at 50% 50%, rgba(194, 100, 63, 0.045), transparent 75%), var(--bg);
  overflow: hidden;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}

.connect-header {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: clamp(34px, 4.5vw, 54px);
}

.connect-eyebrow {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: clamp(11px, 0.85vw, 12px);
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}

.connect-title {
  font-family: var(--font-display);
  font-size: clamp(38px, 5.4vw, 68px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: var(--text);
  margin: 0;
  text-wrap: balance;
}

/* Carousel stage — provides 3D perspective and edge gradients */
.connect-stage {
  position: relative;
  width: 100%;
  height: clamp(380px, 44vw, 520px);
  perspective: 2400px;
  perspective-origin: 50% 50%;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
  touch-action: pan-y;
}

.connect-stage.is-dragging {
  cursor: grabbing;
}

/* Edge fade gradients so cards gracefully dissolve into the dark canvas */
.connect-stage::before,
.connect-stage::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(70px, 12vw, 160px);
  z-index: 20;
  pointer-events: none;
}
.connect-stage::before {
  left: 0;
  background: linear-gradient(90deg, var(--bg) 0%, transparent 100%);
}
.connect-stage::after {
  right: 0;
  background: linear-gradient(-90deg, var(--bg) 0%, transparent 100%);
}

.connect-track {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}

/* Card geometry: portrait cards, 5 visible across desktop in an upward arc */
.connect-card {
  position: absolute;
  top: clamp(24px, 3.5vw, 44px);
  left: 0;
  width: clamp(220px, 18.5vw, 275px);
  aspect-ratio: 3 / 4;
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.46), 0 0 0 1px var(--line-soft);
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  will-change: transform;
  transform-origin: center center;
  transition: box-shadow 0.35s var(--easing);
  -webkit-tap-highlight-color: transparent;
}

@media (max-width: 1024px) {
  .connect-card {
    width: clamp(180px, 22vw, 220px);
  }
}

@media (max-width: 640px) {
  .connect-stage {
    height: clamp(310px, 78vw, 380px);
  }
  .connect-card {
    width: clamp(150px, 38vw, 185px);
    top: 18px;
    border-radius: var(--r-lg);
  }
}

.connect-card:hover {
  box-shadow: 0 26px 60px rgba(0, 0, 0, 0.65), 0 0 0 1px var(--accent-line);
}

.connect-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  overflow: hidden;
}

.connect-card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  pointer-events: none;
  transition: transform 0.65s var(--easing);
}

.connect-card:hover img {
  transform: scale(1.05);
}

/* Subtle streetwear/editorial overlay badge & caption */
.connect-card-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 16px 18px 20px;
  background: linear-gradient(180deg, rgba(13, 12, 10, 0.42) 0%, transparent 42%, rgba(13, 12, 10, 0.88) 100%);
  pointer-events: none;
  transition: background 0.35s var(--easing);
}

.connect-card:hover .connect-card-overlay {
  background: linear-gradient(180deg, rgba(13, 12, 10, 0.3) 0%, transparent 35%, rgba(13, 12, 10, 0.82) 100%);
}

.connect-card-tag {
  align-self: flex-start;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(13, 12, 10, 0.62);
  padding: 4px 10px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/* On small screens the card is only ~150 px wide, so "Студийная Съемка"
   wraps to two lines and the pill eats the top third of the frame —
   exactly where a portrait's face sits. Two fixes for phones:
     1. the tag drops to the bottom of the card, above the title, so the
        photograph itself is never covered;
     2. it shrinks (9px / tighter tracking / tighter padding) so the longest
        category still fits on a single line. */
@media (max-width: 640px) {
  .connect-card-overlay {
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 12px 14px;
    background: linear-gradient(180deg, rgba(13, 12, 10, 0.20) 0%, transparent 30%, rgba(13, 12, 10, 0.88) 100%);
  }
  .connect-card-tag {
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.10em;
    padding: 2px 8px;
  }
}

.connect-card-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.connect-card-title {
  font-family: var(--font-display);
  font-size: clamp(15px, 1.2vw, 19px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.22;
  letter-spacing: -0.01em;
}

.connect-card-year {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  color: var(--text-mute);
  letter-spacing: 0.08em;
}

/* ── End of Carousel Section ── */
@media (prefers-reduced-motion: reduce) {
  .connect-card,
  .connect-card img {
    transition: none !important;
  }
}

/* ───────────────────────── Section heads ───────────────────────── */
.section-head {
  display: flex; flex-direction: column; gap: 14px;
  margin-bottom: 56px;
  max-width: 720px;
}
.section-num {
  font-family: var(--font-sans);
  font-size: var(--fs-overline);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.6;
  color: var(--accent);
  /* Drifts a little slower than the page, as if pinned to the background
     behind the title. Composes with the reveal's own transform — `translate`
     is applied before `transform`, so both work. */
  translate: 0 var(--py, 0px);
}

/* ── Reading progress ──
   A hairline at the very top of the viewport. Appears once you are past the
   hero, because there is nothing to measure while you are still in it. */
.scroll-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 60;
  pointer-events: none;
}
.scroll-progress span {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
  opacity: var(--progress-o, 0);
  transition: opacity .3s var(--easing);
}
.section-title {
  font-family: var(--font-serif);   /* Claude: headlines are serif, period */
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: 1.10;
  letter-spacing: -0.022em;
  text-wrap: balance;
  display: flex; flex-wrap: wrap; gap: 18px; align-items: baseline;
}
.section-title .t-sans {
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: 0.5em;
  letter-spacing: 0.02em;
  color: var(--text-mute);
}
.section-title .t-serif { font-family: var(--font-serif); font-weight: 500; }
.section-sub {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-lead);
  line-height: 1.55;
  color: var(--text-soft);
  max-width: 54ch;
  text-wrap: pretty;              /* no orphaned last word */
}

/* The works carousel centres its header, so its subtitle needs the same
   treatment as the parent — .section-sub alone would sit left-aligned. */
.connect-sub { text-align: center; margin-top: 14px; }

/* ───────────────────────── About ───────────────────────── */
.about { padding: var(--section-y) 0; }
/* 1 : 1.55 gives the bio column enough room for a comfortable reading
   line without forcing the photo to grow tall and dominate.
   align-items: start puts the top of the name on the same line as the
   top of the photo — that's the whole point of the two columns. */
.about-grid {
  display: grid;
  grid-template-columns: 1fr 1.55fr;
  gap: 88px;
  align-items: start;
  margin-top: 56px;
}
.about-col-right {
  display: flex;
  flex-direction: column;
  gap: 26px;
}
.about-photo {
  position: relative;
  aspect-ratio: 4/5;
  overflow: hidden;
  border-radius: var(--r-xl);
}
.about-photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: grayscale(0.15) contrast(1.04);
}
.about-photo-caption {
  position: absolute; bottom: 18px; left: 18px;
  padding: 8px 15px;
  background: rgba(10, 16, 14, 0.55);
  border-radius: 999px;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--text);
  border: 1px solid var(--glass-border);
}
.about-text { display: flex; flex-direction: column; gap: 26px; }
.about-name {
  display: flex; flex-direction: column; gap: 2px;
  font-size: var(--fs-h2);
  line-height: 1.12;
  letter-spacing: -0.016em;
}
.about-name .t-sans { font-weight: 300; color: var(--text-soft); }
.about-name .t-serif { font-family: var(--font-serif); font-weight: 500; }
.about-bio {
  display: flex;
  flex-direction: column;
  /* Generous gap: with 7 paragraphs and only one font-size step between
     body and lead, a tight rhythm reads as a wall. 28px is the smallest
     that lets the eye distinguish "thought" from "thought". */
  gap: 28px;
  max-width: 56ch;
}
.about-bio p {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1.72;
  color: var(--text-soft);
  text-wrap: pretty;
}
/* Opening line — the one sentence the visitor actually reads. */
.about-bio p.bio-lead {
  font-size: var(--fs-lead);
  line-height: 1.5;
  letter-spacing: -0.008em;
  color: var(--text);
}
/* **accent** in the admin text. Not bold — bold shouts; colour is enough. */
.about-bio .bio-accent {
  font-style: normal;
  color: var(--accent);
}
/* Closing line. Serif italic matches the rest of the site's voice and gives
   the bio somewhere to land instead of trailing off. */
.about-bio p.bio-sign {
  /* Larger top margin than a normal gap — the closing line should feel
     like a separate beat, not just the next paragraph. */
  margin-top: 12px;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-lead);
  line-height: 1.45;
  color: var(--text);
}
.about-bio p.bio-sign .bio-accent {
  font-style: inherit;
}
.about-stats {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 24px; list-style: none;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}
.about-stats li { display: flex; flex-direction: column; gap: 4px; }
.about-stats li strong {
  font-family: var(--font-serif);
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.01em;
  /* Digits keep the same advance width while the counter runs, so the "+"
     does not twitch on every frame. */
  font-variant-numeric: tabular-nums lining-nums;
}
.about-stats li span {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--text-mute);
}

/* ───────────────────────── Portfolio magazine grid ───────────────────────── */
/* 12-column grid, 50px row unit, 20px gap.
   Spans are computed so each class lands on its intended aspect ratio:
     tall 4×9 → 0.70   big 4×7 → 0.91   square 4×6 → 1.07
     mini 3×4 → 1.21   wide 6×5 → 1.97                                    */
.portfolio { padding: var(--section-y) 0; }
.grid-magazine {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 50px;
  gap: 20px;
  grid-auto-flow: dense;
}
.gm-card {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  cursor: pointer;
  transition: transform .5s var(--easing), box-shadow .5s var(--easing);
  isolation: isolate;
}
.gm-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 0 0 1px var(--line-strong), var(--float);
}
.gm-card img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Crop from the TOP: when a frame shrinks, the loss must come from the
     bottom of the portrait. Centred cropping decapitates the subject. */
  object-position: top center;
  /* `translate` and `scale` are separate properties, which is the whole trick
     here: the scroll-linked offset must follow the scrollbar with NO
     transition, while the hover zoom needs a slow one. Sharing a single
     `transform` would force one behaviour onto both. */
  translate: 0 var(--py, 0px);
  /* 1.08 is travel budget, not a zoom: 4% of the height is free above and
     below, so the image can drift inside its frame without ever showing an
     edge. Hover builds on top of that rather than starting from 1. */
  scale: var(--img-s, 1.08);
  transition: scale 1.2s var(--easing), filter .5s var(--easing);
  will-change: translate;
}
.gm-card:hover img { --img-s: 1.13; filter: saturate(1.08); }
.gm-cap {
  position: absolute; inset: auto 0 0 0;
  padding: 20px;
  background: linear-gradient(180deg, transparent 0%, rgba(13,12,10,0.9) 78%);
  display: flex; justify-content: space-between; align-items: end; gap: 12px;
  pointer-events: none;
}
.gm-cap .meta { display: flex; flex-direction: column; gap: 2px; }
.gm-cap .cat {
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  line-height: 1.6;
  color: var(--accent);
}
.gm-cap .ttl {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1.25;
  color: var(--text);
}
.gm-cap .yr {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.07em;
  line-height: 1.6;
  color: var(--text-soft);
}

.gm-card.span-tall   { grid-column: span 4; grid-row: span 9; }
.gm-card.span-big    { grid-column: span 4; grid-row: span 7; }
.gm-card.span-square { grid-column: span 4; grid-row: span 6; }
.gm-card.span-mini   { grid-column: span 3; grid-row: span 4; }
.gm-card.span-wide   { grid-column: span 6; grid-row: span 5; }

/* Same chip family as the nav pills and "Акция" — full pill, no border, soft
   volumetric feel from inset highlights alone. The square-ish r-xl version
   of this badge was the heaviest single glass element on the portfolio:
   a 1px accent border on a small element always reads as decoration on top
   of decoration. */
.gm-card .badge {
  position: absolute; top: 14px; left: 14px;
  padding: 6px 13px;
  border-radius: 999px;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  line-height: 1.6;
  color: var(--accent);
}

.portfolio-more {
  display: flex; justify-content: center;
  margin-top: 40px;
}
.gm-card.is-extra { display: none; }   /* hidden until "Загрузить ещё" */
.gm-card.is-revealing { animation: rise .8s var(--easing) both; }

/* ───────────────────────── Services ───────────────────────── */
.services { padding: var(--section-y) 0; }
.services-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.svc {
  position: relative;
  padding: 34px 30px 30px;
  border-radius: var(--r-lg);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(14px);
  display: flex; flex-direction: column; gap: 16px;
  min-height: 300px;
  transition: transform .4s var(--easing), background .3s var(--easing), box-shadow .3s var(--easing);
}
.svc:hover {
  transform: translateY(-5px);
  background: var(--glass-bg-strong);
  box-shadow: 0 0 0 1px var(--line-strong), var(--whisper);
}
.svc .svc-title {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.22;
  letter-spacing: -0.01em;
  color: var(--accent);          /* the accent lives in the title — no icons */
  text-wrap: balance;
}
.svc .svc-desc {
  color: var(--text-soft);
  font-size: var(--fs-body);
  line-height: 1.60;
  text-wrap: pretty;
}
/* Description is now a div that holds paragraphs and (optionally) a list.
   Keep paragraphs gap-loose so the list sits inside the same breathing
   rhythm as before, and never collapse the surrounding cards. */
.svc .svc-desc > * + * { margin-top: 10px; }
.svc .svc-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.svc .svc-list li {
  position: relative;
  padding-left: 18px;
  margin: 4px 0;
  text-wrap: pretty;
}
.svc .svc-list li::before {
  content: '—';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--text-mute);
}
.svc .svc-desc-foot {
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: var(--text-mute);
  border-top: 1px dashed var(--line-soft);
  padding-top: 8px;
  margin-top: 12px;
}
.svc .svc-price {
  margin-top: auto;
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}
.svc.is-on-sale .svc-price { border-top-color: var(--accent-line); }

/* Optional "Акция" badge in the top-right corner of a sale card.

   Solid fill, deliberately. The card title is accent-coloured too, so an
   outlined badge in that same hue did not read as a marker at all — it read
   as part of the title, and at 10px it dissolved into it. Filled with the
   accent and set in dark type it becomes the only solid element in an
   otherwise translucent card, so the eye lands on it first. Contrast of dark
   on accent is 4.7:1 — passes AA. */
.svc .svc-badge {
  position: absolute;
  top: 18px; right: 18px;
  padding: 5px 11px;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #12100d;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  box-shadow: 0 2px 12px rgba(194, 100, 63, 0.28);
  pointer-events: none;
}

/* The badge is taken out of flow, so the title runs underneath it. Reserve
   the room — on sale cards only, a plain card keeps its full width. */
.svc.is-on-sale .svc-title { padding-right: 78px; }
.svc.has-countdown.is-on-sale .svc-title { padding-right: 168px; }

/* Countdown meta inside the badge: "Акция · осталось 12 дн".
   `.svc-badge-meta` runs at slightly smaller size and lighter weight so the
   "осталось" feels like a footnote, not a sibling to the headline. */
.svc-badge-sep {
  display: inline-block;
  margin: 0 6px;
  opacity: 0.45;
  font-weight: 400;
}
.svc-badge-meta {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  opacity: 0.85;
  display: inline-block;
}
.svc-badge-days {
  display: inline-block;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: 0;
  margin: 0 1px;
}
/* Tiny tick pulse on the number whenever the JS rewrites it — keeps the
   countdown feeling alive without being noisy. */
@keyframes svc-badge-days-tick {
  0%   { transform: translateY(0)    scale(1);    }
  40%  { transform: translateY(-1px) scale(1.10); }
  100% { transform: translateY(0)    scale(1);    }
}
.svc-badge-days--tick {
  animation: svc-badge-days-tick 360ms ease-out;
  transform-origin: center;
}
@media (prefers-reduced-motion: reduce) {
  .svc-badge-days--tick { animation: none; }
}

/* On narrow screens the countdown meta is the first thing to drop — keep
   just the word "Акция" so the badge still fits beside the title. */
@media (max-width: 520px) {
  .svc.has-countdown .svc-badge-meta,
  .svc.has-countdown .svc-badge-sep { display: none; }
  .svc.has-countdown.is-on-sale .svc-title { padding-right: 78px; }
}
.svc .svc-price-old {
  font-size: 13px;
  font-weight: 400;
  color: var(--text-mute);
  text-decoration: line-through;
  text-decoration-color: var(--text-mute);
  text-decoration-thickness: 1px;
  margin-bottom: 4px;
  letter-spacing: 0;
}
.svc .svc-price-new.is-sale {
  color: var(--accent);
  font-weight: 600;
  font-size: 18px;
}

.services-cta {
  margin-top: 36px;
  padding: 30px 34px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
  border-radius: var(--r-lg);
}
.services-cta p {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-h4);
  line-height: 1.4;
  color: var(--text);
  max-width: 48ch;
}
.services-cta .services-cta-text { display: flex; flex-direction: column; gap: 2px; }
/* Fine print under the CTA — deliberately quiet: small, muted, but the two
   contact links stay underlined so they read as clickable. */
.services-cta .services-note {
  margin-top: 12px;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 13px;
  line-height: 1.55;
  letter-spacing: 0;
  color: var(--text-mute);
  max-width: 56ch;
}
.services-cta .services-note[hidden] { display: none; }
.services-cta .services-note a {
  color: var(--text-soft);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  text-decoration-color: var(--line-strong);
  transition: color .25s var(--easing), text-decoration-color .25s var(--easing);
}
.services-cta .services-note a:hover,
.services-cta .services-note a:focus-visible {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* ───────────────────────── Testimonials ───────────────────────── */
.testimonials {
  padding: var(--section-y) 0;
  background: var(--bg-soft);
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}
.t-head { flex-direction: row; align-items: flex-end; justify-content: space-between; }
.t-head-text { display: flex; flex-direction: column; gap: 14px; }
.t-arrows { display: flex; gap: 8px; }
.t-arrow {
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
  font-family: var(--font-sans);
  font-size: 24px; line-height: 1;
  color: var(--text-soft);
  background: transparent;
  transition: background .25s var(--easing), color .25s var(--easing), border-color .25s var(--easing);
}
.t-arrow:hover:not(:disabled) { background: var(--glass-bg-strong); color: var(--text); border-color: var(--accent-line); }
.t-arrow:active:not(:disabled) { transform: scale(0.94); }
.t-arrow:disabled { opacity: 0.3; cursor: not-allowed; }

/* The viewport clips the track; the track itself is a flex row that
   slides horizontally. Each card reserves its space via flex-basis
   (the gap is subtracted so the math works out for any visible count). */
.t-viewport { overflow: hidden; padding-bottom: 6px; }
.t-track {
  display: flex;
  gap: 22px;
  transition: transform .6s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
.t-track .t-card {
  flex: 0 0 calc((100% - 22px) / 2);  /* 2 visible on desktop */
  min-width: 0;
}

.t-card {
  padding: 38px 34px 32px;
  border-radius: var(--r-lg);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(14px);
  display: flex; flex-direction: column; gap: 14px;
  position: relative;
}
/* The quote mark is a real element, not a ::before pinned to the card edge —
   that put it flush against the top border. It now sits inside the padding
   with a trimmed line box, so there is air above and below the glyph. */
.t-card .t-quote {
  display: block;
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: 68px;
  line-height: 1;
  height: 30px;                  /* trim the empty space under the glyph */
  color: var(--accent);
  opacity: 0.55;
  user-select: none;
}
.t-card .t-text {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.62;
  letter-spacing: -0.003em;
  color: var(--text);
  text-wrap: pretty;
}
.t-card .t-meta {
  display: flex; align-items: center; gap: 14px;
  margin-top: auto; padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}
.t-card .t-ava {
  flex: 0 0 auto;
  width: 46px; height: 46px;
  border-radius: 50%;
  overflow: hidden;
  display: grid; place-items: center;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--line-soft);
}
.t-card .t-ava img { width: 100%; height: 100%; object-fit: cover; display: block; }
.t-card .t-ava-mono {
  font-family: var(--font-serif);
  font-weight: 500; font-size: 19px; line-height: 1;
  color: var(--accent);
}
.t-card .t-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.t-card .t-name { font-size: var(--fs-body); font-weight: 500; line-height: 1.5; }
.t-card .t-role {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  line-height: 1.5;
  color: var(--text-mute);
}

/* ───────────────────────── Contact + calendar ───────────────────────── */
.contact { padding: var(--section-y) 0; }
.contact-grid { display: grid; grid-template-columns: 1fr 0.95fr; gap: 56px; align-items: start; }
.contact-lead {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-lead);
  line-height: 1.55;
  color: var(--text-soft);
  margin: 0 0 26px;
  max-width: 40ch;
}
.contact-list { list-style: none; display: flex; flex-direction: column; border-top: 1px solid var(--line-soft); }
.contact-list li { border-bottom: 1px solid var(--line-soft); }

/* Вся строка — одна большая кнопка. Цель кликать должна быть крупной:
   на телефоне попадать пальцем в тонкую ссылку справа неудобно. */
.contact-list .ch {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 17px 0;
  color: var(--text);
  text-decoration: none;
  transition: color .25s var(--easing);
}
.contact-list .ch:hover { color: var(--accent); }
.contact-list .ch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--r-sm);
}

/* Название канала и сам адрес — на одной строке, адрес уезжает
   вправо. На узком экране они встают друг под другом. */
.ch-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
}
.ch-lab {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase;
  line-height: 1.5;
  color: var(--text-mute);
  transition: color .25s var(--easing);
}
.contact-list .ch:hover .ch-lab { color: var(--accent); }

.ch-val {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1.3;
  text-align: right;
  word-break: break-word;
}

/* Мелкая строка «отвечаю быстрее всего» — объясняет выбор,
   не превращая список в сухую таблицу. */
.ch-hint {
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-mute);
  opacity: .85;
}
.calendar { padding: 26px 24px 22px; border-radius: var(--r-2xl); }
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.cal-title {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1.3;
}
.cal-nav {
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
  font-size: 22px; color: var(--text-soft);
  transition: background .3s var(--easing), box-shadow .3s var(--easing);
}
.cal-nav:hover { background: var(--glass-bg-strong); color: var(--text); box-shadow: var(--ring); }

.cal-weekdays {
  display: grid; grid-template-columns: repeat(7, 1fr);
  text-align: center;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  line-height: 1.6;
  color: var(--text-mute);
  padding: 6px 0;
}
.cal-weekdays .wknd { color: var(--accent); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.cal-day {
  aspect-ratio: 1;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  font-weight: 400;
  color: var(--text);
  border-radius: var(--r-sm);
  position: relative;
  transition: background .2s var(--easing), color .2s var(--easing), box-shadow .2s var(--easing);
}
.cal-day.is-free {
  background: var(--accent-dim);
  box-shadow: 0 0 0 1px var(--accent-line);
  color: var(--accent);
  cursor: pointer;
}
.cal-day.is-free:hover { background: var(--accent); color: var(--text); }
.cal-day.is-booked { background: transparent; color: var(--text-mute); text-decoration: line-through; cursor: not-allowed; }
.cal-day.is-past { color: rgba(237, 229, 214, 0.2); cursor: not-allowed; }
.cal-day.is-today { box-shadow: 0 0 0 1px rgba(237, 229, 214, 0.34); }
.cal-day.is-empty { background: transparent; cursor: default; }

.cal-legend {
  margin-top: 16px;
  display: flex; gap: 16px; flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase;
  line-height: 1.6;
  color: var(--text-mute);
}
.cal-legend .lg {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 3px; margin-right: 6px; vertical-align: middle;
}
.lg-free { background: var(--accent-dim); box-shadow: 0 0 0 1px var(--accent-line); }
.lg-booked { background: rgba(237, 229, 214, 0.06); box-shadow: 0 0 0 1px var(--line-soft); }
.lg-past { background: transparent; box-shadow: 0 0 0 1px rgba(237, 229, 214, 0.1); }

.cal-hint {
  margin-top: 14px;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-caption);
  line-height: 1.55;
  color: var(--text-soft);
}

/* ───────────────────────── Footer ───────────────────────── */
.footer { padding: 44px 0 56px; border-top: 1px solid var(--line-soft); background: var(--bg); }
.footer-inner { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.footer-brand { display: flex; align-items: center; gap: 12px; font-size: 16px; }
.footer-meta {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase;
  line-height: 1.6;
  color: var(--text-mute);
}
.footer-dev {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase;
  line-height: 1.6;
  color: var(--text-mute);
  border-bottom: 1px solid transparent;
  transition: color .25s var(--easing), border-color .25s var(--easing);
}
.footer-dev:hover { color: var(--accent); border-bottom-color: var(--accent-line); opacity: 1; }

/* ───────────────────────── Reveal ───────────────────────── */
/* The house entrance: content lifts into place rather than just fading.
   Two details do most of the work —
     · a hair of scale (0.985 → 1) so it settles instead of snapping;
     · expo-out easing, which spends most of its time in the last 20% of the
       movement. That "arrive and hold" quality is what makes Apple's motion
       feel calm rather than busy.
   Transform/opacity only — both run on the compositor, no layout cost. */
.reveal {
  opacity: 0;
  transform: translate3d(0, 30px, 0) scale(0.985);
  transition: opacity .85s var(--ease-out-expo), transform 1.15s var(--ease-out-expo);
}
.reveal.is-visible { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }

/* The class is attached by JS after the markup has already painted, so without
   this the element would spend 0.85s fading *out* — visible content dimming
   away for no reason. Applied for one frame only, just long enough to commit
   the hidden state before the transition is allowed to run. */
.reveal.reveal--no-anim { transition: none !important; }

/* Elements that should hold their scale (images, cards with borders) can opt
   out of the scale part and only lift. */
.reveal.reveal--lift { transform: translate3d(0, 26px, 0); }
.reveal.reveal--lift.is-visible { transform: translate3d(0, 0, 0); }

/* ── Parallax layers ──
   Driven from JS by a CSS variable so the scroll handler only ever writes one
   property. The extra inset gives the layer room to travel without exposing
   an edge; `.hero` clips with overflow:hidden. */
.hero-bg {
  inset: -8% 0;
  will-change: transform;
  transform: translate3d(0, var(--hero-py, 0px), 0);
}
.hero-content {
  will-change: opacity, transform;
  transform: translate3d(0, var(--hero-cy, 0px), 0);
  opacity: var(--hero-co, 1);
}
/* The scroll cue is part of the hero's exit, not the page's entrance. */
.hero-scroll { opacity: var(--hero-so, 1); transition: opacity .2s linear; }

/* About portrait drifts a little slower than the page. */
.about-photo img {
  will-change: transform;
  transform: translate3d(0, var(--photo-py, 0px), 0) scale(1.06);
}

/* ==========================================================================
   Micro-animations
   Small, quick, and always subordinate to the content. Nothing loops except
   the two ambient loops already present (hero zoom, film strip). Everything
   collapses to nothing under prefers-reduced-motion — see the rule at the end.
   ========================================================================== */

@keyframes rise {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}
@keyframes menu-drop {
  from { opacity: 0; transform: translateY(-10px) scaleY(0.97); }
  to   { opacity: 1; transform: none; }
}

/* ── Hero: staggered entrance ── */
.hero-kicker,
.hero-title .word,
.hero-tagline,
.hero-tags,
.hero-actions,
.hero-scroll { animation: rise .9s var(--easing) both; }
.hero-kicker         { animation-delay: .06s; }
.hero-title .word-1  { animation-delay: .16s; }
.hero-title .word-2  { animation-delay: .26s; }
.hero-tagline        { animation-delay: .38s; }
.hero-tags           { animation-delay: .46s; }
.hero-actions        { animation-delay: .54s; }
.hero-scroll         { animation-delay: .70s; }

/* ── Nav: condenses once you leave the top. The glass pill gets a touch
     stronger so links stay readable over light sections. ── */
/* (Background and box-shadow for the plate live in the Liquid Glass block
   further down — keeping them in one place avoids two systems fighting.) */

/* ── Nav "condense" motion ──
   The bar lives in two states: open (at the top, generous spacing) and
   compact (everywhere else, tighter). Both directions transition through
   CSS, with a stagger that lets each element settle in its own time —
   that's what makes it read as "assembly / disassembly" instead of a
   single blink. The stagger lives on a CSS custom property so the link's
   existing hover transition (opacity/color) stays instant, while the
   scroll-triggered transform picks up the delay. */
.nav-inner {
  /* The full state is what you see at the top of the page; the "compact"
     state is at scrollY ≥ 220px. Every value below interpolates linearly
     between the two, driven by --nav-t (set on the .nav element by the
     scroll-driven nav-condense animation further down). No CSS transitions
     on these props — the compositor owns the interpolation. */
  padding-block: calc(12px - 5px * var(--nav-t, 0));
  padding-inline: calc(20px - 4px * var(--nav-t, 0));
  gap: calc(28px - 12px * var(--nav-t, 0));
  border-radius: calc(32px - 8px * var(--nav-t, 0));
  /* Base layer is the dense tint; the at-rest tint rides on a ::before
     pseudo and fades out as --nav-t → 1. Two layers instead of color-mix
     so the interpolation is rock-solid — color-mix() interpolation under
     scroll-driven animations is still flaky across browsers. */
  background: var(--liquid-tint);
  position: relative;     /* anchor the ::before overlay */
  /* The plate is wider to start with (see .nav base styles, max-width 1440px)
     so the condense has visible travel — the bar literally stretches out
     to "open" and pulls back in to "compact". */
  transition:
    box-shadow 0.4s cubic-bezier(.16,1,.3,1);
}
.nav-inner::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--liquid-tint-dense);
  /* Fade the dense tint in as we condense. opacity is animatable across
     every browser, so this is the reliable leg of the condense. */
  opacity: var(--nav-t, 0);
  pointer-events: none;
}

/* Stagger the appearance of children so the condense reads as an
   assembly, not a single blink. Each link's transform lags a little, and
   the CTA lands last — your eye finishes on the button. The delays are
   tiny so this doesn't slow down a quick scroll, only adds rhythm. */
.nav-brand img {
  transform: scale(calc(1 - 0.1 * var(--nav-t, 0)));
  transform-origin: left center;
  will-change: transform;
}

.nav-links a {
  /* Hover props keep their short, instant feel. The scroll-driven scale
     has no transition — scroll-driven owns it. */
  transition: opacity .25s var(--easing), color .25s var(--easing);
  transform: scale(calc(1 - 0.06 * var(--nav-t, 0)));
  transform-origin: center;
  transition-delay: calc(20ms * var(--nav-t, 0));
}
.nav-links li:nth-child(1) { transition-delay: calc(20ms * var(--nav-t, 0)); }
.nav-links li:nth-child(2) { transition-delay: calc(35ms * var(--nav-t, 0)); }
.nav-links li:nth-child(3) { transition-delay: calc(50ms * var(--nav-t, 0)); }
.nav-links li:nth-child(4) { transition-delay: calc(65ms * var(--nav-t, 0)); }
.nav-links li:nth-child(5) { transition-delay: calc(80ms * var(--nav-t, 0)); }

#nav-cta {
  /* The CTA is shorter than the plate (47px vs 73px at rest), which means
     the same absolute radius reads as a much more pronounced rounding
     on the button — an "oval" that visually breaks away from the plate.
     The fix is to dial the radius down so the *relative* curvature
     matches: ~21px on the button gives the same R/h ratio as the plate's
     32px does on the plate. It still tracks --nav-t so it condenses
     in lockstep with the bar — just along a tighter scale. */
  border-radius: calc(21px - 4px * var(--nav-t, 0));
  /* The CTA "lands" last, so the eye finishes on it. */
  transition:
    background 0.3s var(--easing),
    color 0.3s var(--easing);
  transform: scale(calc(1 - 0.06 * var(--nav-t, 0)));
  transform-origin: center;
  transition-delay: calc(140ms * var(--nav-t, 0));
}

/* Drive --nav-t from scroll position. Range 0 → 220px covers the hero
   fade-out, after that the bar is fully compact. Both directions
   (scrolling down to compact, scrolling back to top to "re-assemble")
   come from the same animation — that's the part Vadim was asking
   about. The composited animation is run on the .nav element so the
   value inherits down to everything that uses it. */
.nav {
  --nav-t: 0;
  animation: nav-condense linear both;
  animation-timeline: scroll(root);
  animation-range: 0 220px;
}

/* ── Anchor targets clear the fixed nav ── */
section[id], header[id] { scroll-margin-top: 96px; }

/* ── Press feedback ── */
.pill:active { transform: scale(0.975); }
.gm-card:active { transform: translateY(-2px) scale(0.995); }

/* ── Link with arrow: the arrow nudges forward ── */
.link-arrow { transition: color .3s var(--easing), border-color .3s var(--easing); }
.link-arrow:hover { color: var(--accent); border-color: var(--accent-line); opacity: 1; }
.link-arrow .la-arrow {
  display: inline-block;
  transition: transform .35s var(--easing);
}
.link-arrow:hover .la-arrow { transform: translateX(6px); }

/* ── Service card: a hairline of accent draws across the top ── */
.svc::before {
  content: '';
  position: absolute; top: 0; left: 30px; right: 30px; height: 1px;
  background: linear-gradient(90deg, var(--accent), transparent 85%);
  transform: scaleX(0); transform-origin: left;
  opacity: .85;
  transition: transform .55s var(--easing);
}
.svc:hover::before { transform: scaleX(1); }

/* ── Portfolio cover: caption lifts a touch on hover ── */
.gm-cap .meta { transition: transform .45s var(--easing); }
.gm-card:hover .gm-cap .meta { transform: translateY(-3px); }

/* ── Calendar: free days respond to the pointer ── */
.cal-day.is-free { transition: background .2s var(--easing), color .2s var(--easing),
                               box-shadow .2s var(--easing), transform .2s var(--easing); }
.cal-day.is-free:hover { transform: scale(1.13); }
.cal-day.is-free:active { transform: scale(0.94); }

/* ── Testimonial avatar: settles in on hover ── */
.t-card .t-ava { transition: box-shadow .35s var(--easing), transform .35s var(--easing); }
.t-card:hover .t-ava { transform: scale(1.06); box-shadow: 0 0 0 1px var(--accent-line); }

/* ── Keyboard focus is always visible ── */
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.empty {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1.5;
  color: var(--text-soft);
  grid-column: 1 / -1;
  padding: 40px 0;
}
.empty a { color: var(--accent); }

/* ───────────────────────── Responsive ───────────────────────── */
@media (max-width: 1100px) {
  .about-grid { grid-template-columns: 1fr; gap: 40px; }
  .grid-magazine { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 44px; gap: 16px; }
  .gm-card.span-tall   { grid-column: span 6; grid-row: span 9; }
  .gm-card.span-big    { grid-column: span 6; grid-row: span 7; }
  .gm-card.span-square { grid-column: span 3; grid-row: span 6; }
  .gm-card.span-mini   { grid-column: span 3; grid-row: span 4; }
  .gm-card.span-wide   { grid-column: span 6; grid-row: span 5; }
  .services-grid, .t-grid { grid-template-columns: 1fr 1fr; }
  .contact-grid { grid-template-columns: 1fr; }
}

@media (max-width: 768px) {
  .container { padding: 0 20px; }
  .nav { padding: 18px 16px; }
  .nav-inner { gap: 8px; }
  .nav-links, .nav-cta { display: none; }
  .nav-section { display: none; }
  .nav-burger { display: flex; }
  .nav-links.open {
    display: flex;
    position: absolute; top: calc(100% + 8px); left: 0; right: 0;
    flex-direction: column; gap: 4px; padding: 14px;
    background: rgba(20, 18, 16, 0.94);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-lg);
    backdrop-filter: blur(28px) saturate(150%);
    -webkit-backdrop-filter: blur(28px) saturate(150%);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), var(--ring);
    margin-left: 0;
    animation: menu-drop .32s var(--easing) both;
  }
  .nav-links.open li a {
    display: block;
    padding: 12px 14px;
    border-radius: var(--r-sm);
    font-size: 16px;
    color: var(--text);
    transition: background .25s var(--easing), color .25s var(--easing);
  }
  .nav-links.open li a:hover,
  .nav-links.open li a:active { background: var(--glass-bg-strong); color: var(--accent); }
  .nav-links.open li a::after { display: none; }
  /* On mobile, the title sits slightly higher to keep the name out of
     the bottom gesture zone and the photo's face visible. */
  .hero-content { padding: 0 18px clamp(40px, 7vh, 64px); gap: 16px; }
  .hero-title { font-size: clamp(36px, 11vw, 64px); line-height: 1.04; letter-spacing: -0.03em; }
  .hero-actions { gap: 10px; }
  .hero-scroll { right: 12px; bottom: 32px; font-size: 9px; }
  .hero-scroll-line { height: 32px; }
  .about, .portfolio, .services, .testimonials, .contact { padding: 76px 0; }
  .grid-magazine { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 40px; gap: 14px; }
  .gm-card.span-tall   { grid-column: span 2; grid-row: span 7; }
  .gm-card.span-big    { grid-column: span 2; grid-row: span 6; }
  .gm-card.span-square { grid-column: span 1; grid-row: span 5; }
  .gm-card.span-mini   { grid-column: span 1; grid-row: span 4; }
  .gm-card.span-wide   { grid-column: span 2; grid-row: span 4; }
  .services-grid, .t-grid { grid-template-columns: 1fr; }
  .t-head { flex-direction: column; align-items: flex-start; gap: 22px; }
  .t-track .t-card { flex: 0 0 100%; }   /* 1 visible on mobile */
  .svc { min-height: 0; padding: 28px; }
  /* Coverflow on phones: same wide fan as desktop (each step ~one card
     width apart) so the arc fills the viewport edge-to-edge. Card is
     smaller and the stage is tighter so the row actually fills the frame
     instead of floating in a tall dark band. */
  .coverflow-card { width: clamp(140px, 44vw, 190px); }
  .coverflow-stage { height: clamp(280px, 50vh, 360px); }
  .coverflow-card-inner {
    transform:
      translateX(calc(var(--pos, 0) * 62%))
      translateZ(calc((var(--pos, 0) * var(--pos, 0)) * -55px))
      rotateY(calc(var(--pos, 0) * -12deg))
      scale(calc(1 - abs(var(--pos, 0)) * 0.05));
  }
  .coverflow-hint { display: none; }
  .about-stats { grid-template-columns: 1fr; gap: 14px; padding-top: 22px; }
  .services-cta { flex-direction: column; align-items: flex-start; }
  /* На узком экране «название | адрес» в одну строку не влезает —
     ставим друг под другом, адрес выравниваем по левому краю. */
  .ch-top { flex-direction: column; align-items: flex-start; gap: 3px; }
  .ch-val { text-align: left; }
}

/* Honour the OS setting. Duration alone is not enough — the two ambient
   loops (hero zoom, film strip) must also stop looping, or a 0.001ms
   animation with infinite iterations just flickers. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }
  /* Scroll entrances must not depend on JS at all under reduced motion:
     kill the hidden start state outright so nothing can get stuck invisible
     if the observer never fires. Parallax offsets are dropped too. */
  .reveal,
  .reveal.reveal--lift {
    opacity: 1 !important;
    transform: none !important;
  }
  .hero-bg,
  .hero-content,
  .about-photo img {
    transform: none !important;
  }
  /* Parallax on photos and section numbers lives in `translate`, not
     `transform`, so it needs its own opt-out. */
  .gm-card img,
  .b-card-media img,
  .section-num {
    translate: none !important;
  }
  .scroll-progress { display: none !important; }
  .hero-content,
  .hero-scroll {
    opacity: 1 !important;
  }
}
/* ── Inner pages: reserve room for client-rendered content ────────────────
   #project-root, #post-root and .blog-page are filled from an API after the
   first paint. With nothing reserved the footer started near the top of the
   page and then jumped down when the content arrived — one jump worth 0.16
   CLS on a project page. Holding a viewport's worth of height keeps the
   footer below the fold, and a move the visitor never saw is not a shift.

   `main.blog-page` rather than `.blog-page`: blog.css already sets a smaller
   min-height on that class and loads later, so it would win on equal
   specificity. */
#project-root,
#post-root,
main.blog-page {
  min-height: 100vh;
}

/* ══════════════════════════════════════════════════════════════════════════
   Liquid glass — warm variant
   ══════════════════════════════════════════════════════════════════════════
   Apple's liquid glass is blue-cooled. On this palette that reads as a
   foreign object, so the tint here is ivory and the rim light is warm.

   Four things stacked: a blurred, slightly saturated backdrop; a warm tint;
   a specular hairline along the top edge; and a darker, thicker swell at the
   bottom so the slab reads as having depth rather than being a flat
   translucent rectangle.

   Kept in tokens so the whole set can be dialled back on weaker hardware
   from one place. Note there is no ::before lens — the depth swells live in
   the same box-shadow list, which avoids painting a layer over the content.
   ────────────────────────────────────────────────────────────────────────── */
:root {
  --liquid-blur: 20px;
  /* At rest the plate is barely-there so the hero photo shows through.
     Dense mode (scrolled, or any nested glass) dials tint up for legibility. */
  --liquid-tint: rgba(237, 229, 214, 0.030);
  --liquid-tint-dense: rgba(237, 229, 214, 0.078);
  /* Outer shadow is intentionally weak. On a dark backdrop a strong drop
     reads as a hard halo around the plate — the same "rim" problem in a
     softer suit. We want the plate to feel like it is hovering, not stamped. */
  --liquid-drop: 0 10px 28px rgba(0, 0, 0, 0.28);
}

/* The nav plate is glass from the very first frame — at hero it is just thin
   enough that the photo shows through, and it condenses (tint + saturation)
   as the page scrolls. No "no-glass → glass" jump: the family is the same
   the whole way down, only the density changes.

   No rim. On the dark backdrop of this site a 1px outline reads as "card has
   a border" rather than "this is glass" — it stops the eye from reading
   the lens-blob as a reflection. The combination of a bright top hairline,
   two soft lens-blobs and an outer drop is enough to say "glass"; the rim
   was decoration on top of decoration. */
.nav-inner {
  /* Tint + backdrop-filter + shadows — the "liquid glass" recipe. The
     background colour itself is now driven by the scroll-driven
     --nav-t (see "Nav condense" block above) which tints the plate
     denser on scroll. */
  -webkit-backdrop-filter: blur(var(--liquid-blur)) saturate(150%) brightness(1.05);
  backdrop-filter: blur(var(--liquid-blur)) saturate(150%) brightness(1.05);
  box-shadow:
    /* Soft top wash — wide, gentle. This is what tells the eye "light is
       hitting the top of this glass". */
    inset 0 18px 30px -10px rgba(255, 250, 240, 0.20),
    /* Soft bottom depth — wider and softer than the top so the card reads
       as resting on the page, not stamped onto it. */
    inset 0 -22px 34px -8px rgba(13, 12, 10, 0.32),
    var(--liquid-drop);
}
/* (The .nav.is-scrolled class toggle that used to switch background and
   backdrop-filter density is gone — the scroll-driven animation owns it.) */

.liquid {
  background: var(--liquid-tint);
  -webkit-backdrop-filter: blur(var(--liquid-blur)) saturate(150%) brightness(1.05);
  backdrop-filter: blur(var(--liquid-blur)) saturate(150%) brightness(1.05);
  box-shadow:
    inset 0 10px 18px -8px rgba(255, 250, 240, 0.38),
    inset 0 -10px 18px -6px rgba(13, 12, 10, 0.55),
    var(--liquid-drop);
}

/* Small elements do not need a 20px blur — and every extra backdrop-filter
   costs a full-screen pass on the GPU. */
.liquid--chip,
.nav-indicator,
.nav-section {
  --liquid-blur: 12px;
}
.liquid--chip,
.nav-section {
  background: var(--liquid-tint);
  -webkit-backdrop-filter: blur(var(--liquid-blur)) saturate(150%) brightness(1.05);
  backdrop-filter: blur(var(--liquid-blur)) saturate(150%) brightness(1.05);
  box-shadow:
    inset 0 8px 14px -8px rgba(255, 250, 240, 0.32),
    inset 0 -8px 14px -6px rgba(13, 12, 10, 0.42);
}

/* The active-section pill. Modeled on Apple's segmented-control "selected"
   capsule (the Inbox pill in the reference): denser glass on top of the
   plate — more opaque, brighter top reflection, no outer drop shadow
   (nested glass should not cast its own), no rim. */
.nav-indicator {
  background: rgba(237, 229, 214, 0.12);
  -webkit-backdrop-filter: blur(var(--liquid-blur)) saturate(160%) brightness(1.08);
  backdrop-filter: blur(var(--liquid-blur)) saturate(160%) brightness(1.08);
  box-shadow:
    inset 0 6px 12px -8px rgba(255, 250, 240, 0.40),
    inset 0 -5px 10px -6px rgba(13, 12, 10, 0.42);
}

/* "02 · Работы" next to the wordmark — a chip, but only once it is on. */
.nav-section {
  padding: 4px 10px;
  border-radius: 999px;
}

/* ── "Акция" badge: glass shell over a solid accent core ──────────────────
   Deliberately NOT translucent. Two tasks ago this badge was made solid
   because the outlined version dissolved into the accent-coloured title.
   Turning it back into glass would undo that, so it keeps the accent fill
   and only borrows the inner lens — contrast stays at 4.7:1.
   No rim, no 1px hairlines — minimalism rule. */
.svc .svc-badge {
  background: var(--accent);
  box-shadow:
    inset 0 6px 12px -8px rgba(255, 235, 220, 0.5),
    inset 0 -4px 10px -6px rgba(93, 41, 22, 0.45),
    0 4px 16px rgba(194, 100, 63, 0.32);
}

/* ── "Акция" badge: soft breathing glow + a highlight running round the rim

   Two quiet layers, both on the badge only — the goal is to catch the eye
   at the corner of vision, not to shout.

   1. Breathing glow (2.6s): the outer orange halo stretches a few px and
      fades back. The inset highlights never move, so the pill still reads
      as a solid object rather than a hologram.
   2. Rim sweep (4s): a thin warm highlight travels once around the
      border — the "animation along the edge" Vadim asked for. Built as a
      conic gradient masked down to a 1px ring, so it costs one
      pseudo-element and no extra markup. */

/* @property lets the conic-gradient angle be animated. Where it is not
   supported the gradient just holds still at 0deg — the badge keeps its
   static rim, nothing breaks. */
@property --svc-badge-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes svc-badge-sweep {
  to { --svc-badge-angle: 360deg; }
}

/* ── Nav condense ──
   A single 0..1 progress number that drives every nav-condense property
   through calc(). Driven by scroll() on the root timeline, so the
   compositor owns the interpolation — no JS, no class-toggle, and no
   CSS transition that could restart mid-scroll and stutter. Vadim's
   "микродвижение" complaint was exactly that: a class-toggle whose
   transition re-launched on every scroll event. With this we get one
   continuous animation tied to the scroll position. */
@property --nav-t {
  syntax: '<number>';
  initial-value: 0;
  inherits: true;
}

@keyframes nav-condense {
  to { --nav-t: 1; }
}

/* Stronger: the halo goes 0.18 → 0.75 alpha and 8px → 34px. The rim
   sweep was thinned down, so the breathing has to carry the "look here"
   job on its own — but it stays a halo, never a hard ring. */
@keyframes svc-badge-pulse {
  0%, 100% {
    box-shadow:
      inset 0 6px 12px -8px rgba(255, 235, 220, 0.5),
      inset 0 -4px 10px -6px rgba(93, 41, 22, 0.45),
      0 4px 8px rgba(194, 100, 63, 0.18);
  }
  50% {
    box-shadow:
      inset 0 6px 12px -8px rgba(255, 235, 220, 0.5),
      inset 0 -4px 10px -6px rgba(93, 41, 22, 0.45),
      0 8px 34px rgba(194, 100, 63, 0.75),
      0 0 0 1px rgba(255, 122, 77, 0.18);
  }
}

.svc.is-on-sale .svc-badge {
  animation: svc-badge-pulse 2.6s ease-in-out infinite;
}

/* The travelling rim. inset:-2.5px with 1.5px padding leaves a hairline
   ~1.5px halo — thin enough to read as a highlight rather than a border.
   The mask pair (content-box + full box, composited with exclude) punches
   out the middle so only that hairline shows. */
.svc.is-on-sale .svc-badge::after {
  content: '';
  position: absolute;
  inset: -2.5px;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(
    from var(--svc-badge-angle),
    rgba(255, 235, 220, 0)    0deg,
    rgba(255, 235, 220, 0)   80deg,
    rgba(255, 220, 190, 0.30) 140deg,
    rgba(255, 248, 234, 1.00) 168deg,
    rgba(255, 220, 190, 0.30) 196deg,
    rgba(255, 235, 220, 0)   256deg,
    rgba(255, 235, 220, 0)   360deg
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: svc-badge-sweep 3.4s linear infinite;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .svc.is-on-sale .svc-badge,
  .svc.is-on-sale .svc-badge::after { animation: none; }
}

/* ── Featured tag on a portfolio frame ─────────────────────────────────── */
.gm-card .badge {
  background: rgba(13, 12, 10, 0.42);
  box-shadow:
    inset 0 6px 12px -8px rgba(255, 250, 240, 0.28),
    inset 0 -5px 10px -6px rgba(13, 12, 10, 0.45);
}

/* Captions stay matte on purpose — glass over glass turns to milk. */
.gm-cap {
  box-shadow: inset 0 1px 0 rgba(237, 229, 214, 0.10);
}

/* Weaker GPUs: cut the blur radius rather than the effect. */
@media (max-width: 768px) {
  :root { --liquid-blur: 12px; }
  .liquid--chip, .nav-indicator, .nav-section { --liquid-blur: 8px; }
}

/* ─────────── FAQ ─────────── */
/* На <details>, а не на JS: работает без скриптов, с клавиатуры и со
   скринридером, а поисковик видит текст сразу. FAQPage-разметка обязана
   совпадать с видимым содержимым — сервер собирает и то, и другое
   из одного массива в db.json. */
.faq {
  padding: var(--section-y) 0;
  background: var(--bg-soft);
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}
.faq-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line-soft);
}
.faq-item {
  border-bottom: 1px solid var(--line-soft);
}
.faq-q {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 22px 4px;
  color: var(--text);
  font-size: 17px;
  line-height: 1.4;
  letter-spacing: -0.01em;
  transition: color 0.25s var(--easing);
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:hover { color: var(--accent); }
.faq-q:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--r-sm);
}

/* Плюс, который превращается в минус. Две линии вместо иконки —
   не надо тащить спрайт ради одного элемента. */
.faq-mark {
  position: relative;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
}
.faq-mark::before,
.faq-mark::after {
  content: '';
  position: absolute;
  inset: 50% 0 auto;
  height: 1px;
  background: currentColor;
  transition: transform 0.3s var(--easing), opacity 0.3s var(--easing);
}
.faq-mark::after { transform: rotate(90deg); }
.faq-item[open] .faq-mark::after { transform: rotate(0deg); opacity: 0; }

.faq-a {
  padding: 0 4px 24px;
  max-width: 68ch;
}
.faq-a p {
  margin: 0;
  color: var(--text-soft);
  font-size: 16px;
  line-height: 1.65;
}

@media (max-width: 620px) {
  .faq-q { padding: 18px 2px; font-size: 16px; gap: 16px; }
  .faq-a { padding-bottom: 20px; }
  .faq-a p { font-size: 15px; }
}
