/* Rated Tattoo — Landing UI kit sections (editorial / single-screen redesign)
   Full-viewport scroll-snap panels, entrance reveals, photo marquee works
   showcase, film grain. Composes design-system primitives from the bundle. */

const NS = window.RatedTattooDesignSystem_04b525;
const { Button, StarRating, Input, Accordion } = NS;

const LOGO = "./assets/logos/rated-logo-white.png";
const MAXW = "1240px";

/* [путь, название, стиль, пропорция кадра ш/в]
   Пропорция снята с исходников — плитки в ленте строятся по ней, чтобы
   работы не обрезались под общий бокс. */
const WORKS = [
  ["./assets/img/work-wolf.jpg", "Реалистичный волк", "реализм", 0.706],
  ["./assets/img/work-angel-sleeve.jpg", "Ангел · рукав", "реализм", 0.869],
  ["./assets/img/work-geisha.jpg", "Гейша", "ориентал", 0.644],
  ["./assets/img/work-mandala.jpg", "Геометрический орнамент", "орнамент", 0.664],
  ["./assets/img/work-oni-mask.jpg", "Маска они", "ориентал", 0.547],
  ["./assets/img/work-gaze.jpg", "Взгляд", "реализм", 0.989],
  ["./assets/img/work-warrior.jpg", "Северный воин", "реализм", 0.750],
  ["./assets/img/work-rat.jpg", "Уголовное дело", "нео-традишн", 0.750],
  ["./assets/img/work-catrina.jpg", "Катрина", "реализм", 0.750],
  ["./assets/img/work-bear-realism.webp", "Медведь — угроза", "реализм", 0.750],
  ["./assets/img/work-hourglass.jpg", "Песочные часы", "трэш-полька", 0.562],
  ["./assets/img/work-samurai.jpg", "Самурай", "ориентал", 0.799],
  ["./assets/img/work-girl-rose.jpg", "Девушка с розой", "реализм", 0.750],
  ["./assets/img/work-sphinx.webp", "Сфинкс", "графика", 0.715],
  ["./assets/img/work-robber.jpg", "Налётчик", "чикано", 0.592],
  ["./assets/img/work-sphynx-cat.jpg", "Кот сфинкс", "реализм", 0.664],
  ["./assets/img/work-tiger-sleeve.jpg", "Тигриный рукав", "реализм", 0.712],
  ["./assets/img/work-clown.jpg", "Клоун", "хоррор", 0.924],
  ["./assets/img/work-chief.jpg", "Вождь", "реализм", 0.776],
  ["./assets/img/work-bear-graphic.jpg", "Графический медведь", "графика", 0.722],
  ["./assets/img/work-portrait-peony.jpg", "Портрет с пионами", "реализм", 0.562],
  ["./assets/img/work-eagle-clock.jpg", "Орёл и время", "реализм", 0.750],
  ["./assets/img/work-eye.jpg", "Реалистичный глаз", "реализм", 0.750],
  ["./assets/img/work-lips.webp", "Перманентный татуаж", "татуаж", 1.810],
  ["./assets/img/work-samurai-armor.webp", "Самурай в доспехах", "реализм", 0.8],
  ["./assets/img/work-chief-feathers.webp", "Вождь в уборе", "реализм", 0.776],
  ["./assets/img/work-peonies-hip.webp", "Пионы", "графика", 0.671],
  ["./assets/img/work-dynamite.webp", "Динамит", "нео-традишн", 0.692],
  ["./assets/img/work-mandala-sternum.webp", "Мандала под грудью", "орнамент", 1.385],
  ["./assets/img/work-girl-city.webp", "Девушка с карпами", "реализм", 0.625],
  ["./assets/img/work-girl-revolver.webp", "Девушка с револьвером", "реализм", 0.694],
  ["./assets/img/work-samurai-sakura.webp", "Самурай и сакура", "ориентал", 0.664],
  ["./assets/img/work-antique-statue.webp", "Античная статуя", "реализм", 0.715],
  ["./assets/img/work-mountain-geometry.webp", "Гора и геометрия", "графика", 0.664],
  ["./assets/img/work-peony-pattern.webp", "Пионовый узор", "графика", 0.664],
  ["./assets/img/work-lizard.webp", "Ящерица", "графика", 0.696],
  ["./assets/img/work-elephant.webp", "Слон", "графика", 1.505],
  ["./assets/img/work-wolf-girl.webp", "Девушка-волчица", "реализм", 0.671],
  ["./assets/img/work-gorillas.webp", "Гориллы", "реализм", 0.97],
  ["./assets/img/work-girl-lilies.webp", "Девушка с лилиями", "реализм", 0.644],
  ["./assets/img/work-snake-color.webp", "Змея", "нео-традишн", 0.75],
  ["./assets/img/work-bear-roar.webp", "Медведь", "реализм", 0.75],
  ["./assets/img/work-jester.webp", "Смерть", "нео-традишн", 0.827],
  ["./assets/img/work-centurion.webp", "Центурион", "реализм", 0.601],
  ["./assets/img/work-anubis-graphic.webp", "Анубис", "графика", 0.75],
  ["./assets/img/work-scorpion.webp", "Скорпион", "графика", 0.75],
  ["./assets/img/work-girl-roses.webp", "Девушка в розах", "реализм", 0.75],
  ["./assets/img/work-tiger-lotus.webp", "Тигр и лотос", "ориентал", 0.681],
  ["./assets/img/work-anubis-mech.webp", "Анубис и механизм", "реализм", 0.684],
  ["./assets/img/work-harley.webp", "Харли Квинн", "нео-традишн", 0.75],
  ["./assets/img/work-hourglass-skull.webp", "Часы и череп", "нео-традишн", 0.671],
  ["./assets/img/work-fox-cat.webp", "Лиса и кошка", "нео-традишн", 0.692],
  ["./assets/img/work-bird-peach.webp", "Птица и персик", "нео-традишн", 0.545],
  ["./assets/img/work-matryoshka-gzhel.webp", "Матрёшка «Свои»", "нео-традишн", 0.818],
  ["./assets/img/work-deer-compass.webp", "Северный олень", "реализм", 0.75],
  ["./assets/img/work-portrait-rose.webp", "Портрет с розой", "реализм", 1.005],
  ["./assets/img/work-koi-set.webp", "Карпы кои", "ориентал", 1.691],
  ["./assets/img/work-girl-blue-hair.webp", "Девушка с синими волосами", "реализм", 0.691],
  ["./assets/img/work-grizzly.webp", "Волчий оскал", "реализм", 0.819],
  ["./assets/img/work-fire-sleeve.webp", "Феникс", "ориентал", 1.114],
  ["./assets/img/work-skulls-roses.webp", "Черепа и розы", "реализм", 1.108],
  ["./assets/img/work-warrior-wolf.webp", "Воин и волк", "реализм", 0.75],
  ["./assets/img/work-paws-lettering.webp", "Лапы и надпись", "графика", 1.333],
  ["./assets/img/work-koi-lotus.webp", "Кои и лотос", "ориентал", 0.722],
  ["./assets/img/work-crowned-skulls.webp", "Коронованные черепа", "реализм", 1.0],
  ["./assets/img/work-samurai-mask.webp", "Самурай в маске", "реализм", 0.705],
  ["./assets/img/work-feet-ornament.webp", "Орнамент на стопах", "орнамент", 1.0],
  ["./assets/img/work-dark-portrait.webp", "Тёмный демон", "реализм", 0.799],
  ["./assets/img/work-lion-egypt.webp", "Лев", "графика", 0.722],
  ["./assets/img/work-raccoon.webp", "Енот", "нео-традишн", 0.722],
  ["./assets/img/work-bear-shadow.webp", "Кинг Конг", "реализм", 0.585],
  ["./assets/img/work-via-the-end.webp", "Via the End", "биомеханика", 0.586],
  ["./assets/img/work-japan-landscape.webp", "Японский пейзаж", "ориентал", 0.505],
  ["./assets/img/work-roses-color.webp", "Розы", "реализм", 0.676],
  ["./assets/img/work-armor.webp", "Доспехи", "реализм", 0.739],
  ["./assets/img/work-lion-crown.webp", "Лев в короне", "реализм", 0.695],
  ["./assets/img/work-warrior-statue.webp", "Ассасин", "реализм", 0.783],
  ["./assets/img/work-geisha-smoke.webp", "Гейша в дыму", "реализм", 0.724],
  ["./assets/img/work-gunslinger.webp", "Стрелок", "реализм", 0.729],
  ["./assets/img/work-cat-geometry.webp", "Кот-геометрия", "графика", 0.722],
  ["./assets/img/work-roses-bird.webp", "Розы и птица", "реализм", 1.415],
  ["./assets/img/work-matryoshka-color.webp", "Матрёшка в цветах", "нео-традишн", 0.714],
  ["./assets/img/work-buddha.webp", "Будда", "ориентал", 0.707],
  ["./assets/img/work-portrait-flowers.webp", "Портрет в цветах", "реализм", 0.666],
  ["./assets/img/work-vampiress.webp", "Вампирша", "хоррор", 0.714],
  ["./assets/img/work-girl-roses-2.webp", "Девушка и розы", "реализм", 0.664],
  ["./assets/img/work-viking.webp", "Викинг", "реализм", 0.857],
  ["./assets/img/work-crying-girl.webp", "Плачущая", "реализм", 0.667],
  ["./assets/img/work-music-soul.webp", "Music of my soul", "трэш-полька", 1.154],
  ["./assets/img/work-snake-roses.webp", "Змея и розы", "графика", 0.669],
  ["./assets/img/work-capricorn.webp", "Козерог", "графика", 1.025],
  ["./assets/img/work-fox-trash.webp", "Лиса", "трэш-полька", 0.722],
  ["./assets/img/work-dreamcatcher.webp", "Ловец снов", "орнамент", 0.707],
  ["./assets/img/work-girl-ornament.webp", "Девушка в орнаменте", "реализм", 0.722],
  ["./assets/img/work-wolf-flag.webp", "Волк и флаг", "графика", 0.722],
  ["./assets/img/work-sculptor.webp", "Скульптор", "реализм", 0.722],
  ["./assets/img/work-mandala-underbust.webp", "Мандала", "орнамент", 1.385],
  ["./assets/img/work-lotus.webp", "Лотос", "графика", 0.722],
  ["./assets/img/work-biomech-sleeve.webp", "Биомеханика · рукав", "биомеханика", 0.75],
];

/* Стили в порядке первого появления — служат индексом ленты. */
const WORK_STYLES = WORKS.map((w) => w[2]).filter((s, i, a) => a.indexOf(s) === i);

/* Высота плитки одна на всю ленту; ширина — производная от пропорции.
   Крайние пропорции подрезаем, чтобы одна работа не заняла весь экран. */
const TILE_H = "clamp(320px, 38vw, 480px)";
const tileRatio = (r) => Math.min(1.35, Math.max(0.52, r));
/* Порог, за которым нажатие считается протяжкой, а не кликом по работе. */
const DRAG_SLOP = 8;

/* Слот-машина: клик по стилю разгоняет ленту, перестановка происходит на пике
   скорости — плитки в этот момент смазаны, и подмена содержимого не читается,
   — затем лента тормозит и встаёт первой работой стиля точно по центру.
   SPIN_DIST выведен из производной easeOutCubic в нуле (3·dist/dur): при таком
   пути торможение начинается ровно с SPIN_VMAX, без рывка на стыке фаз. */
const SPIN_UP = 0.24;      /* с — разгон */
const SPIN_DOWN = 1.1;     /* с — торможение */
const SPIN_VMAX = 5000;    /* px/s — пик, ~14 плиток в секунду */
const SPIN_DIST = SPIN_VMAX * SPIN_DOWN / 3;

/* ---------------------------------------------------------------- helpers */
function Kicker({ index, label, color }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: "14px", fontFamily: "var(--font-body)", fontSize: "12px", fontWeight: 700, letterSpacing: "0.22em", textTransform: "uppercase" }}>
      <span style={{ color: "var(--accent)" }}>{index}</span>
      <span style={{ width: "28px", height: "1px", background: color === "dark" ? "rgba(10,10,12,.25)" : "var(--border-hair)" }}></span>
      <span style={{ color: color === "dark" ? "rgba(10,10,12,.6)" : "var(--text-muted)" }}>{label}</span>
    </div>
  );
}

/* ----------------------------------------------------------------- Header */
function Header({ onBook }) {
  const [solid, setSolid] = React.useState(false);
  React.useEffect(() => {
    const sc = document.querySelector(".rt-scroll") || window;
    const onScroll = () => setSolid((sc.scrollTop || window.scrollY) > 40);
    sc.addEventListener("scroll", onScroll);
    return () => sc.removeEventListener("scroll", onScroll);
  }, []);
  const links = [["#about", "Мастер"], ["#works", "Работы"], ["#process", "Вживую"], ["#benefits", "Преимущества"], ["#faq", "Вопросы"]];
  return (
    <header style={{
      position: "fixed", top: 0, left: 0, right: 0, zIndex: 50,
      background: solid ? "rgba(10,10,12,0.82)" : "transparent",
      backdropFilter: solid ? "blur(14px)" : "none",
      borderBottom: solid ? "1px solid var(--border-hair)" : "1px solid transparent",
      transition: "background .35s ease, border-color .35s ease",
    }}>
      <div style={{ maxWidth: MAXW, margin: "0 auto", padding: "0 32px", height: "72px", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        <a href="#" style={{ display: "flex", alignItems: "center", gap: "12px" }}>
          <img src={LOGO} alt="Rated Tattoo" style={{ height: "38px", width: "auto" }} />
          <span style={{ fontFamily: "var(--font-logo)", color: "var(--bone)", fontSize: "20px", letterSpacing: "0.16em" }}>RATED</span>
        </a>
        <nav style={{ display: "flex", gap: "36px" }} className="rt-nav">
          {links.map(([href, label]) => (
            <a key={href} href={href} style={{ fontFamily: "var(--font-body)", fontSize: "12px", fontWeight: 600, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--text-body)", transition: "color .2s" }}
               onMouseEnter={(e) => (e.currentTarget.style.color = "var(--bone)")}
               onMouseLeave={(e) => (e.currentTarget.style.color = "var(--text-body)")}>{label}</a>
          ))}
        </nav>
        <div style={{ display: "flex", alignItems: "center", gap: "22px" }}>
          <a href="tel:+79689752099" className="rt-phone" style={{ fontFamily: "var(--font-body)", fontSize: "13px", fontWeight: 600, color: "var(--bone)", letterSpacing: "0.02em" }}>+7 (968) 975-20-99</a>
          <Button size="sm" variant="outline" onClick={onBook}>Записаться</Button>
        </div>
      </div>
    </header>
  );
}

/* ------------------------------------------------------------------- Hero */
function Hero({ onBook }) {
  return (
    <section id="hero" className="rt-snap" style={{ position: "relative", minHeight: "100vh", display: "flex", flexDirection: "column", overflow: "hidden" }}>
      <div style={{ position: "absolute", inset: 0, zIndex: 0 }}>
        <video autoPlay muted loop playsInline poster="./assets/img/work-wolf.jpg"
          style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: "center", filter: "grayscale(0.6) contrast(1.08) brightness(0.8)" }}>
          <source src="./assets/video/hero.mp4" type="video/mp4" />
        </video>
        <div style={{ position: "absolute", inset: 0, background: "var(--scrim-hero)" }}></div>
      </div>

      <div className="rt-edge-label" style={{ position: "absolute", left: "32px", top: "50%", transform: "rotate(180deg)", writingMode: "vertical-rl", zIndex: 2, fontFamily: "var(--font-body)", fontSize: "11px", letterSpacing: "0.3em", textTransform: "uppercase", color: "var(--text-muted)" }}>
        Rated Tattoo · Moscow · с 2015
      </div>

      <div className="rt-reveal rt-in" style={{ position: "relative", zIndex: 1, flex: "1 1 auto", width: "100%", maxWidth: MAXW, margin: "0 auto", padding: "150px 32px 60px", display: "flex", flexDirection: "column", justifyContent: "center" }}>
        <div style={{ marginBottom: "26px" }}><Kicker index="ТОП-10" label="Тату-мастеров Москвы 2023" /></div>
        <h1 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(46px, 7vw, 104px)", lineHeight: 0.9, letterSpacing: "-0.01em", margin: 0 }}>
          Элитные<br />татуировки<span style={{ color: "var(--accent)" }}>.</span>
        </h1>
        <div style={{ fontFamily: "var(--font-body)", fontSize: "14px", fontWeight: 600, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--text-muted)", marginTop: "20px" }}>
          — от мастера Тимура Тэда
        </div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: "36px", alignItems: "flex-end", justifyContent: "space-between", marginTop: "36px" }}>
          <p style={{ fontSize: "var(--fs-lead)", color: "var(--gray-200)", maxWidth: "42ch", lineHeight: 1.55, margin: 0 }}>
            Авторские татуировки в центре Москвы. Реализм, графика, орнаменты — каждая работа создаётся лично под клиента.
          </p>
          <div style={{ display: "flex", gap: "14px", flexWrap: "wrap" }}>
            <Button variant="primary" size="lg" onClick={onBook} iconRight="fas fa-arrow-right">Записаться</Button>
            <Button as="a" href="#works" variant="glass" size="lg">Работы</Button>
          </div>
        </div>
      </div>

      <div style={{ position: "relative", zIndex: 1, flex: "0 0 auto", borderTop: "1px solid var(--border-hair)", background: "rgba(10,10,12,0.4)", backdropFilter: "blur(8px)" }}>
        <div style={{ maxWidth: MAXW, margin: "0 auto", padding: "0 32px", display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }} className="rt-stats">
          {[["10+", "лет практики"], ["150+", "работ в портфолио"], ["150+", "отзывов · 5.0"], ["100%", "стерильность"]].map(([n, l], i) => (
            <div key={l} style={{ padding: "20px 24px", borderLeft: i === 0 ? "none" : "1px solid var(--border-hair)", display: "flex", flexDirection: "column", gap: "2px" }}>
              <span style={{ fontFamily: "var(--font-display)", fontSize: "30px", fontWeight: 600, color: "var(--bone)", lineHeight: 1 }}>{n}</span>
              <span style={{ fontSize: "11px", letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--text-muted)" }}>{l}</span>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------------ About */
function About() {
  const reasons = [
    ["Мировой уровень", "Участник международных тату-конвенций и лауреат премий. Постоянно совершенствую навыки."],
    ["Безопасность", "Только одноразовые материалы и стерилизация в автоклавах класса B."],
    ["Индивидуальный подход", "Каждый эскиз создаю лично, учитывая ваши пожелания и особенности тела."],
  ];
  return (
    <section id="about" className="rt-snap" style={{ position: "relative", background: "var(--bg-base)", minHeight: "100vh", display: "flex", alignItems: "center", padding: "100px 0", overflow: "hidden" }}>
      {/* atmospheric oversize wordmark */}
      <div aria-hidden="true" style={{ position: "absolute", right: "-2.5%", top: "6%", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: "clamp(140px, 23vw, 360px)", lineHeight: 0.8, color: "var(--bone)", opacity: 0.028, textTransform: "uppercase", letterSpacing: "-0.03em", pointerEvents: "none", userSelect: "none" }}>Тэд</div>

      <div className="rt-reveal rt-about-grid" style={{ position: "relative", maxWidth: MAXW, margin: "0 auto", padding: "0 32px", display: "grid", gridTemplateColumns: "0.94fr 1.06fr", gap: "72px", alignItems: "center", width: "100%" }}>
        {/* ----- portrait ----- */}
        <div className="rt-about-media" style={{ position: "relative" }}>
          <div className="rt-edge-label" style={{ position: "absolute", left: "-30px", top: "50%", transform: "translateY(-50%) rotate(180deg)", writingMode: "vertical-rl", zIndex: 2, fontFamily: "var(--font-body)", fontSize: "11px", letterSpacing: "0.32em", textTransform: "uppercase", color: "var(--text-faint)" }}>
            Tattoo master · с 2015
          </div>
          {/* Фон у фото вырезан (webp с альфой) — мастер стоит прямо на фоне
              секции, без прямоугольной подложки. Потому ни overflow, ни cover. */}
          <div style={{ position: "relative" }}>
            {/* Растворяем прямые кромки в фоне секции: низ (подлокотник) и левый
                край (коробка) упираются в край кадра прямым резом. Две маски,
                пересечение — гаснет там, где гасит любая. Контур головы и плеч
                уже вырезан в самом webp, его маски не трогают. */}
            <img src="./assets/img/about-master-work.webp" alt="Тимур Тэд за работой — Rated Tattoo" style={{ width: "100%", height: "auto", display: "block", filter: "grayscale(0.55) contrast(1.05) brightness(0.85)", WebkitMaskImage: "linear-gradient(to bottom, #000 78%, transparent 98%), linear-gradient(to right, transparent 1%, #000 13%)", maskImage: "linear-gradient(to bottom, #000 78%, transparent 98%), linear-gradient(to right, transparent 1%, #000 13%)", WebkitMaskComposite: "source-in", maskComposite: "intersect" }} />
          </div>
        </div>

        {/* ----- profile copy ----- */}
        <div>
          <Kicker index="01" label="О мастере" />
          <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(36px, 4.5vw, 60px)", lineHeight: 0.96, letterSpacing: "-0.01em", margin: "24px 0 0" }}>
            Эталон<br />качества<span style={{ color: "var(--accent)" }}>.</span>
          </h2>
          <p style={{ fontSize: "var(--fs-lead)", color: "var(--text-body)", lineHeight: 1.6, margin: "22px 0 0", maxWidth: "50ch" }}>
            Я создаю не просто татуировки, а произведения искусства, которые будут радовать вас всю жизнь. Реализм, графика и орнаменты — авторский подход к каждому эскизу.
          </p>
          <div style={{ display: "flex", margin: "32px 0 8px", borderTop: "1px solid var(--border-hair)", borderBottom: "1px solid var(--border-hair)" }}>
            {[["10+", "лет практики"], ["ТОП-10", "Москвы 2023"], ["∞", "гарантия"]].map(([n, l], i) => (
              <div key={l} style={{ flex: 1, padding: "18px 0", paddingLeft: i === 0 ? 0 : "24px", borderLeft: i === 0 ? "none" : "1px solid var(--border-hair)" }}>
                <div style={{ fontFamily: "var(--font-display)", fontSize: "29px", fontWeight: 600, color: "var(--bone)", lineHeight: 1 }}>{n}</div>
                <div style={{ fontFamily: "var(--font-body)", fontSize: "11px", letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--text-muted)", marginTop: "6px" }}>{l}</div>
              </div>
            ))}
          </div>
          <div>
            {reasons.map(([h, p], i) => (
              <div key={h} style={{ display: "grid", gridTemplateColumns: "42px 1fr", gap: "4px 18px", padding: "20px 0", borderBottom: "1px solid var(--border-hair)" }}>
                <span style={{ gridRow: "span 2", fontFamily: "var(--font-display)", color: "var(--accent)", fontSize: "18px", fontWeight: 600, paddingTop: "3px" }}>{String(i + 1).padStart(2, "0")}</span>
                <h4 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", letterSpacing: "0.02em", fontSize: "18px", fontWeight: 500, margin: 0 }}>{h}</h4>
                <p style={{ color: "var(--text-muted)", margin: "6px 0 0", lineHeight: 1.55, fontSize: "14px" }}>{p}</p>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------- Works (карусель) */
/* clone — второй проход ленты, нужный только для бесшовной прокрутки.
   Он скрыт от скринридера и выключен из таб-порядка, иначе каждая работа
   объявляется и обходится дважды. */
function WorkTile({ w, onOpen, onFocus, clone }) {
  const [hover, setHover] = React.useState(false);
  return (
    <button type="button" className="rt-work-tile"
      onClick={onOpen} onFocus={onFocus}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      aria-hidden={clone ? "true" : undefined} tabIndex={clone ? -1 : undefined}
      aria-label={w[1] + " — " + w[2] + ". Открыть крупно"}
      style={{ position: "relative", flexShrink: 0, height: TILE_H, width: "calc(" + TILE_H + " * " + tileRatio(w[3]) + ")", margin: "0 5px", overflow: "hidden", background: "var(--ink-800)", padding: 0, border: 0, display: "block", cursor: "pointer" }}>
      {/* src проставляет цикл ленты, когда плитка подходит к экрану — см. sweep().
          Штатный loading="lazy" здесь не работает: React вставляет плитки после
          отрисовки, и Chrome успевает счесть их близкими к экрану. */}
      <img data-src={w[0]} alt={w[1] + " — " + w[2]} draggable={false} style={{ width: "100%", height: "100%", objectFit: "cover", userSelect: "none", pointerEvents: "none", filter: "grayscale(0.5) contrast(1.06)", transform: hover ? "scale(1.06)" : "scale(1)", transition: "transform .7s var(--ease-out), filter .4s" }} />
      {/* Заливка держит читаемость подписи и на светлых кадрах — плашки под текстом нет. */}
      <div style={{ position: "absolute", inset: 0, background: "var(--scrim-hover)", opacity: hover ? 1 : 0.82, transition: "opacity .35s" }}></div>
      <div style={{ position: "absolute", left: "16px", right: "16px", bottom: "16px", textAlign: "left", transform: hover ? "translateY(0)" : "translateY(6px)", opacity: hover ? 1 : 0.9, transition: "all .35s" }}>
        <div style={{ fontFamily: "var(--font-body)", fontSize: "10px", fontWeight: 700, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--accent-soft)", marginBottom: "6px" }}>{w[2]}</div>
        <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", letterSpacing: "0.01em", fontSize: "18px", fontWeight: 500, margin: 0 }}>{w[1]}</h3>
      </div>
    </button>
  );
}

/* Просмотр работы крупно: Esc — закрыть, ←/→ — соседние работы. */
function WorkLightbox({ index, works, onClose, onStep }) {
  const closeRef = React.useRef(null);
  const open = index >= 0;

  React.useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  React.useEffect(() => {
    if (!open) return;
    if (closeRef.current) closeRef.current.focus();
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowRight") { e.preventDefault(); onStep(1); }
      else if (e.key === "ArrowLeft") { e.preventDefault(); onStep(-1); }
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, onClose, onStep]);

  if (!open) return null;
  const w = works[index];
  const nav = { width: "50px", height: "50px", display: "inline-flex", alignItems: "center", justifyContent: "center", background: "rgba(10,10,12,.55)", color: "var(--bone)", border: "1px solid var(--border-hair)", borderRadius: "var(--radius-sm)", cursor: "pointer", fontSize: "15px", flexShrink: 0 };

  return (
    <div role="dialog" aria-modal="true" aria-label={"Работа: " + w[1]} onClick={onClose}
      style={{ position: "fixed", inset: 0, zIndex: 100, background: "rgba(5,5,6,.92)", backdropFilter: "blur(6px)", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "18px", padding: "20px" }}>
      <button ref={closeRef} type="button" aria-label="Закрыть просмотр" onClick={onClose}
        style={{ position: "absolute", top: "20px", right: "20px", width: "50px", height: "50px", background: "transparent", color: "var(--bone)", border: "1px solid var(--border-hair)", borderRadius: "var(--radius-sm)", cursor: "pointer", fontSize: "16px" }}>
        <i className="fas fa-xmark" aria-hidden="true"></i>
      </button>

      <div onClick={(e) => e.stopPropagation()} style={{ display: "flex", alignItems: "center", gap: "clamp(10px, 3vw, 28px)", maxWidth: "100%" }}>
        <button type="button" aria-label="Предыдущая работа" onClick={() => onStep(-1)} style={nav} className="rt-lb-nav">
          <i className="fas fa-arrow-left" aria-hidden="true"></i>
        </button>
        <img src={w[0]} alt={w[1] + " — " + w[2]}
          style={{ maxHeight: "72vh", maxWidth: "min(1100px, 78vw)", objectFit: "contain", display: "block", border: "1px solid var(--border-hair)" }} />
        <button type="button" aria-label="Следующая работа" onClick={() => onStep(1)} style={nav} className="rt-lb-nav">
          <i className="fas fa-arrow-right" aria-hidden="true"></i>
        </button>
      </div>

      <div onClick={(e) => e.stopPropagation()} style={{ textAlign: "center" }}>
        <div style={{ fontFamily: "var(--font-body)", fontSize: "11px", fontWeight: 700, letterSpacing: "0.2em", textTransform: "uppercase", color: "var(--accent-soft)", marginBottom: "8px" }}>{w[2]}</div>
        <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(20px, 2.4vw, 28px)", fontWeight: 500, margin: 0, letterSpacing: "0.01em" }}>{w[1]}</h3>
        <div style={{ fontFamily: "var(--font-body)", fontSize: "12px", letterSpacing: "0.14em", color: "var(--text-muted)", marginTop: "10px" }}>
          {index + 1} / {works.length}
        </div>
      </div>
    </div>
  );
}

function Works({ onBook }) {
  /* Порядок ленты живёт в состоянии: клик по стилю не прячет работы, а
     переставляет их — работы выбранного стиля собираются подряд, и первая
     встаёт в середину кадра. Набор всегда полный, меняется только очередь. */
  const [order, setOrder] = React.useState(WORKS);
  const orderRef = React.useRef(order);
  orderRef.current = order;
  const items = [...order, ...order];
  const wrapRef = React.useRef(null);
  const trackRef = React.useRef(null);
  const [style, setStyle] = React.useState(WORK_STYLES[0]);
  const [shot, setShot] = React.useState(-1);   /* индекс работы в просмотре */
  /* Якорь на время перестановки: какую работу вывести в центр после неё. */
  const anchor = React.useRef(null);

  const S = React.useRef({
    offset: 0, setW: 1, paused: false, dragging: false,
    lastX: 0, lastT: 0, tween: 0, vel: 0, moved: 0, geo: [], mid: -1,
  });

  React.useEffect(() => {
    const track = trackRef.current;
    const wrap = wrapRef.current;
    /* Геометрия первого набора — по ней ищем работу в центре кадра. */
    const measure = () => {
      const s = S.current;
      s.setW = Math.max(1, track.scrollWidth / 2);
      s.geo = Array.prototype.slice.call(track.children, 0, orderRef.current.length)
        .map((el) => ({ left: el.offsetLeft, w: el.offsetWidth }));
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(track);

    /* Подгрузка кадров: src получают только плитки, подошедшие к экрану на
       PRELOAD пикселей. Позиции считаем из geo и offset, без чтения DOM —
       196 замеров на кадр стоили бы дороже самой ленты. Раз в 10 кадров хватает:
       при 34 px/s запас в экран шириной вырабатывается минуты за полторы. */
    const PRELOAD = 1200;
    let tick = 0;
    const sweep = () => {
      const s = S.current;
      if (!s.geo.length) return;
      const view = wrap.clientWidth;
      const kids = track.children;
      for (let k = 0; k < kids.length; k++) {
        const img = kids[k].firstElementChild;
        if (!img || !img.dataset.src) continue;
        const n = orderRef.current.length;
        const g = s.geo[k % n];
        if (!g) continue;
        const left = g.left + (k >= n ? s.setW : 0) - s.offset;
        if (left < view + PRELOAD && left + g.w > -PRELOAD) {
          img.src = img.dataset.src;
          delete img.dataset.src;
        }
      }
    };
    /* Кладём в S, чтобы перестановка могла подгрузить кадры синхронно: она
       меняет offset мгновенно, а цикл дошёл бы до sweep только через 10 кадров
       — и работы редких стилей, ни разу не подходившие к экрану, показались бы
       пустыми прямоугольниками. */
    S.current.sweep = sweep;

    const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
    let raf, last = performance.now();
    const loop = (now) => {
      const s = S.current;
      const dt = Math.min(0.05, (now - last) / 1000); last = now;

      if (s.dragging) {
        /* offset двигает обработчик move */
      } else if (s.spin) {
        const sp = s.spin;
        if (sp.phase === "up") {
          sp.t += dt;
          const u = Math.min(1, sp.t / SPIN_UP);
          s.offset += (sp.v0 + (SPIN_VMAX - sp.v0) * u * u) * dt;
          if (u >= 1) { sp.phase = "swap"; setOrder(buildOrder(sp.st)); }
        } else if (sp.phase === "swap") {
          /* Крутим на пике, пока React не закоммитит новый порядок. Тормозной
             путь выставит useLayoutEffect — там есть настоящая геометрия. */
          s.offset += SPIN_VMAX * dt;
        } else {
          sp.u = Math.min(1, sp.u + dt / SPIN_DOWN);
          const e = 1 - Math.pow(1 - sp.u, 3);
          s.offset = sp.start + sp.dist * e;
          if (sp.u >= 1) s.spin = null;
        }
      } else if (Math.abs(s.tween) > 0.5) {
        const step = s.tween * Math.min(1, dt * 6);
        s.offset += step; s.tween -= step; s.vel = 0;
      } else if (Math.abs(s.vel) > 12) {
        s.offset += s.vel * dt;              /* выбег после броска */
        s.vel *= Math.pow(0.06, dt);
      } else {
        s.tween = 0; s.vel = 0;
        if (!s.paused && !motion.matches) s.offset += 34 * dt;
      }

      s.offset = ((s.offset % s.setW) + s.setW) % s.setW;
      track.style.transform = "translate3d(" + (-s.offset) + "px,0,0)";

      /* Стиль работы, попавшей в центр кадра, ведёт индекс под лентой. */
      if (s.geo.length) {
        const focus = (s.offset + wrap.clientWidth / 2) % s.setW;
        let i = s.geo.length - 1;
        for (let k = 0; k < s.geo.length; k++) {
          if (focus >= s.geo[k].left && focus < s.geo[k].left + s.geo[k].w) { i = k; break; }
        }
        if (i !== s.mid) { s.mid = i; setStyle(orderRef.current[i][2]); }
      }
      if (tick++ % 10 === 0) sweep();
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, []);

  /* Горизонтальный трекпад листает ленту; вертикаль оставляем скроллу страницы. */
  React.useEffect(() => {
    const wrap = wrapRef.current;
    const onWheel = (e) => {
      if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
      e.preventDefault();
      const s = S.current;
      s.offset += e.deltaX; s.tween = 0; s.vel = 0;
    };
    wrap.addEventListener("wheel", onWheel, { passive: false });

    /* Контейнер с overflow:hidden всё равно прокручивается программно, и браузер
       делает это сам, подтягивая элемент под фокусом. Позицию ленты держит
       transform, поэтому чужую прокрутку возвращаем в ноль — иначе смещения
       складываются и плитка уезжает за кадр. */
    const unscroll = () => { if (wrap.scrollLeft !== 0) wrap.scrollLeft = 0; };
    wrap.addEventListener("scroll", unscroll);

    return () => { wrap.removeEventListener("wheel", onWheel); wrap.removeEventListener("scroll", unscroll); };
  }, []);

  React.useEffect(() => { S.current.paused = shot >= 0; }, [shot]);

  /* Подводит работу i к центру кадра кратчайшим путём по кольцу. */
  /* Показ работы, получившей фокус с клавиатуры. Целимся к левому краю, а не
     к центру: так offset остаётся в [0, setW), и видимой оказывается именно та
     плитка, на которой фокус, а не её двойник из второго прохода. */
  const glideIntoView = (i) => {
    const s = S.current;
    const g = s.geo[i];
    const wrap = wrapRef.current;
    if (!g || !wrap) return;
    const x = g.left - s.offset;
    if (x >= 0 && x + g.w <= wrap.clientWidth) return;   /* уже на виду */
    s.tween = Math.max(0, g.left - 24) - s.offset;
    s.vel = 0;
  };

  const nudge = (dir) => { const s = S.current; s.tween += dir * (wrapRef.current ? wrapRef.current.clientWidth : 600) * 0.6; s.vel = 0; };
  const step = (d) => setShot((i) => (i + d + order.length) % order.length);
  /* detail === 0 — нажатие с клавиатуры: там протяжки не было и порог не применим. */
  const open = (i, e) => { if ((e && e.detail === 0) || S.current.moved < DRAG_SLOP) setShot(i % order.length); };

  /* Клик по стилю: работы этого стиля собираются подряд, и первая из них встаёт
     в середину кадра. Ничего не скрывается — набор тот же, меняется очередь.

     Блок ставим не в самое начало ленты, а за несколькими работами — ровно
     столько, чтобы он начинался правее середины кадра. Иначе offset уходит в
     конец набора: визуально верно (лента бесшовная), но в центре оказывается
     плитка-двойник из второго прохода — а он скрыт от скринридера, и фокус с
     клавиатуры улетает к оригиналу через всю ленту. См. glideIntoView. */
  const buildOrder = (st) => {
    const s = S.current, wrap = wrapRef.current, cur = orderRef.current;
    const tagged = cur.filter((w) => w[2] === st);
    const others = cur.filter((w) => w[2] !== st);
    const half = wrap ? wrap.clientWidth / 2 : 0;
    let lead = 0;
    for (let acc = 0; lead < others.length && acc <= half; lead++) {
      const g = s.geo[cur.indexOf(others[lead])];
      acc += g ? g.w : 0;
    }
    anchor.current = tagged[0];
    return others.slice(0, lead).concat(tagged, others.slice(lead));
  };

  const sortByStyle = (st) => {
    const s = S.current;
    if (!wrapRef.current || !order.some((w) => w[2] === st)) return;
    /* При выключенной анимации крутить нечего — переставляем сразу. */
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      setOrder(buildOrder(st));
      return;
    }
    /* Повторный клик во время вращения не сбрасывает скорость к нулю. */
    s.spin = { phase: "up", t: 0, v0: s.spin ? SPIN_VMAX : 34, st };
    s.tween = 0; s.vel = 0;
  };

  /* Пересчёт после перестановки. Геометрию снимаем заново — ResizeObserver тут
     молчит: ширина ленты не изменилась, изменился только порядок. */
  React.useLayoutEffect(() => {
    const a = anchor.current;
    if (!a) return;
    anchor.current = null;
    const s = S.current, track = trackRef.current, wrap = wrapRef.current;
    if (!track || !wrap) return;
    s.setW = Math.max(1, track.scrollWidth / 2);
    s.geo = Array.prototype.slice.call(track.children, 0, order.length)
      .map((el) => ({ left: el.offsetLeft, w: el.offsetWidth }));
    const j = order.indexOf(a);
    if (j >= 0 && s.geo[j]) {
      /* Куда лента должна встать: якорная плитка центром в центр кадра.
         Плитки разной ширины, поэтому целимся центром, а не краем. */
      const final = s.geo[j].left + s.geo[j].w / 2 - wrap.clientWidth / 2;
      if (s.spin && s.spin.phase === "swap") {
        /* Отматываем назад на длину торможения и тормозим вперёд к final.
           Скачок назад невидим: лента сейчас идёт 5000 px/s и смазана. */
        s.spin.start = final - SPIN_DIST;
        s.spin.dist = SPIN_DIST;
        s.spin.u = 0;
        s.spin.phase = "down";
        s.offset = ((s.spin.start % s.setW) + s.setW) % s.setW;
      } else {
        s.offset = ((final % s.setW) + s.setW) % s.setW;
        s.tween = 0; s.vel = 0;
      }
      track.style.transform = "translate3d(" + (-s.offset) + "px,0,0)";
    }
    s.mid = -1;
    if (s.sweep) s.sweep();
  }, [order]);

  const down = (e) => {
    const s = S.current;
    s.dragging = true; s.lastX = e.clientX; s.lastT = performance.now();
    s.tween = 0; s.vel = 0; s.moved = 0; s.held = false;
    wrapRef.current.style.cursor = "grabbing";
  };
  const move = (e) => {
    const s = S.current;
    if (!s.dragging) return;
    const dx = e.clientX - s.lastX;
    const now = performance.now();
    const dt = Math.max(8, now - s.lastT) / 1000;
    s.offset -= dx; s.moved += Math.abs(dx);
    s.vel = s.vel * 0.7 + (-dx / dt) * 0.3;
    s.lastX = e.clientX; s.lastT = now;
    /* Захват включаем только после порога: иначе click уходит обёртке, а не плитке. */
    if (!s.held && s.moved >= DRAG_SLOP && wrapRef.current.setPointerCapture) {
      try { wrapRef.current.setPointerCapture(e.pointerId); s.held = true; } catch (_) {}
    }
  };
  const up = (e) => {
    const s = S.current;
    if (!s.dragging) return;
    s.dragging = false;
    wrapRef.current.style.cursor = "grab";
    if (s.held) { try { wrapRef.current.releasePointerCapture(e.pointerId); } catch (_) {} s.held = false; }
  };

  const ctrl = { width: "46px", height: "46px", display: "inline-flex", alignItems: "center", justifyContent: "center", background: "transparent", color: "var(--text-body)", border: "1px solid var(--border-hair)", borderRadius: "var(--radius-sm)", cursor: "pointer", fontSize: "14px", transition: "all .2s" };
  const ctrlOn = (e) => { e.currentTarget.style.background = "var(--accent)"; e.currentTarget.style.borderColor = "var(--accent)"; e.currentTarget.style.color = "var(--white)"; };
  const ctrlOff = (e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.borderColor = "var(--border-hair)"; e.currentTarget.style.color = "var(--text-body)"; };

  return (
    <section id="works" className="rt-snap" style={{ background: "var(--bg-surface)", minHeight: "100vh", display: "flex", flexDirection: "column", justifyContent: "center", padding: "110px 0", overflow: "hidden" }}>
      <div className="rt-reveal" style={{ maxWidth: MAXW, margin: "0 auto 40px", padding: "0 32px", width: "100%", display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: "20px" }}>
        <div>
          <Kicker index="02" label="Работы" />
          <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(32px, 4.5vw, 56px)", lineHeight: 1, letterSpacing: "-0.01em", margin: "20px 0 0" }}>Избранное</h2>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
          <button aria-label="Предыдущие работы" onClick={() => nudge(-1)} onMouseEnter={ctrlOn} onMouseLeave={ctrlOff} style={ctrl}><i className="fas fa-arrow-left" aria-hidden="true"></i></button>
          <button aria-label="Следующие работы" onClick={() => nudge(1)} onMouseEnter={ctrlOn} onMouseLeave={ctrlOff} style={ctrl}><i className="fas fa-arrow-right" aria-hidden="true"></i></button>
          <div style={{ width: "1px", height: "28px", background: "var(--border-hair)", margin: "0 6px" }}></div>
          <Button variant="ghost" onClick={onBook} iconRight="fas fa-arrow-right">Записаться на сеанс</Button>
        </div>
      </div>

      <div ref={wrapRef} role="group" aria-label="Лента работ"
        onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
        onMouseEnter={() => { S.current.paused = true; }} onMouseLeave={() => { S.current.paused = false; }}
        onFocusCapture={() => { S.current.paused = true; }} onBlurCapture={() => { S.current.paused = shot >= 0; }}
        style={{ overflow: "hidden", cursor: "grab", touchAction: "pan-y", userSelect: "none" }}>
        <div ref={trackRef} style={{ display: "flex", width: "max-content", willChange: "transform" }}>
          {/* Ключ по пути файла, а не по индексу: при перестановке React должен
              переносить готовые плитки, а не переписывать их содержимое на месте —
              иначе уже загруженные картинки сбрасываются и лента моргает. */}
          {items.map((w, i) => (
            <WorkTile key={w[0] + (i >= order.length ? "~2" : "~1")} w={w} clone={i >= order.length}
              onOpen={(e) => open(i, e)}
              onFocus={() => glideIntoView(i % order.length)} />
          ))}
        </div>
      </div>

      {/* Стили: подсвечен стиль работы в центре, клик — собрать этот стиль подряд. */}
      <div className="rt-reveal rt-work-styles" style={{ maxWidth: MAXW, margin: "28px auto 0", padding: "0 32px", width: "100%", display: "flex", flexWrap: "wrap", alignItems: "center", gap: "6px 22px" }}>
        {WORK_STYLES.map((s) => {
          const on = s === style;
          return (
            /* Подчёркивание живёт на внутреннем span, поэтому кнопке можно
               задать крупную область нажатия, не отрывая линию от текста. */
            /* Гасим на время просмотра: лайтбокс не запирает фокус, и Shift+Tab
               с кнопки закрытия уходит сюда. Перестановка сменила бы работу под
               открытым просмотром — индекс тот же, работа уже другая. */
            <button key={s} type="button" onClick={() => sortByStyle(s)} disabled={shot >= 0}
              aria-current={on ? "true" : undefined}
              style={{ background: "none", border: 0, padding: "13px 0", cursor: "pointer", fontFamily: "var(--font-body)", fontSize: "11px", fontWeight: 700, letterSpacing: "0.2em", textTransform: "uppercase", color: on ? "var(--bone)" : "var(--text-muted)", transition: "color .25s" }}>
              <span style={{ display: "inline-block", paddingBottom: "4px", borderBottom: "1px solid " + (on ? "var(--accent)" : "transparent"), transition: "border-color .25s" }}>{s}</span>
            </button>
          );
        })}
      </div>

      <WorkLightbox index={shot} works={order} onClose={() => setShot(-1)} onStep={step} />
    </section>
  );
}

/* ---------------------------------------------------------------- Процесс */
/* [id ролика в Cloud Video, название, категория, обложка, сек, ширина, высота] */
const CLIPS = [
  ["vplveewfxat7z3sqekdv", "Тимур за работой", "процесс", "./assets/img/video/v10.webp", 23, 528, 960],
  ["vplvrja4qp3ps3fonejk", "Змея", "процесс", "./assets/img/video/v01.webp", 22, 540, 960],
  ["vplvxwh3dnpgcnqcxzrj", "Медведь", "процесс", "./assets/img/video/v02.webp", 12, 540, 960],
  ["vplvp4tgzzga2un52nna", "Биомеханика", "процесс", "./assets/img/video/v03.webp", 28, 764, 960],
  ["vplvhiavnn74sa3ad2hz", "Портрет", "процесс", "./assets/img/video/v04.webp", 33, 768, 960],
  ["vplv63sw637q4xgtjwjc", "Гризли", "процесс", "./assets/img/video/v05.webp", 17, 540, 960],
  ["vplvn7i2a6l6b2y3xzeq", "Цветной рукав", "процесс", "./assets/img/video/v06.webp", 23, 576, 720],
  ["vplvxwfyyymbucdbuzn2", "It's alive !!!", "процесс", "./assets/img/video/v07.webp", 28, 576, 720],
  ["vplvmsh2zy23inmeyaxv", "Скорпион", "процесс", "./assets/img/video/v08.webp", 36, 576, 720],
  ["vplvovbcxpfwrrtqifjh", "Кинжал", "процесс", "./assets/img/video/v09.webp", 22, 576, 720],
  ["vplvql3ud6lpffedk4lj", "Персик", "процесс", "./assets/img/video/v11.webp", 32, 540, 960],
  ["vplvuapkkbbpheyzvrbw", "Клоун", "процесс", "./assets/img/video/v12.webp", 22, 576, 720],
  ["vplvxwcmg67sld53q7sz", "Взгляд", "процесс", "./assets/img/video/v13.webp", 17, 720, 720],
  ["vplvrmu6ndlyfzpuizmj", "Череп в шлеме", "процесс", "./assets/img/video/v14.webp", 29, 576, 720],
  ["vplvdepowfldj4muyxv4", "Lars", "процесс", "./assets/img/video/v15.webp", 37, 768, 960],
  ["vplvnmmmdtnjf32tzesl", "Череп", "процесс", "./assets/img/video/v16.webp", 22, 540, 960],
  ["vplv7dm2fazospgd5sjx", "Зверь", "процесс", "./assets/img/video/v17.webp", 16, 720, 720],
  ["vplvy4urfrwgh2sps5sl", "Triangle", "финал", "./assets/img/video/v18.webp", 12, 540, 960],
  ["vplv34slx5lve5p7j4tm", "Шаманка", "финал", "./assets/img/video/v19.webp", 14, 540, 960],
  ["vplv2hpr4stl3yklrnuw", "Сова", "процесс", "./assets/img/video/v20.webp", 13, 560, 960],
  ["vplvhsvigv4bgohwi45v", "Бабочки", "процесс", "./assets/img/video/v21.webp", 18, 540, 960],
  ["vplvgq57pvyskz2gs2bb", "Via the End", "процесс", "./assets/img/video/v22.webp", 13, 540, 960],
  ["vplvhsuvqkpjpl65bay7", "Харли — Джокер", "процесс", "./assets/img/video/v23.webp", 11, 540, 960],
  ["vplv4i6vmqnttvts7epq", "Волк и птица", "финал", "./assets/img/video/v24.webp", 29, 1707, 960],
];

/* Пропорция сцены. В передаче было 16:10, но под неё подходит один ролик из 24 —
   остальные вертикальные, их резало на 65%. 4:5 садится на основную массу, а что
   не совпало, вписывается целиком: рамку добирает размытая обложка того же кадра. */
const STAGE_AR = 0.8;
const STAGE_H = "clamp(340px, 60vh, 600px)";

/* Родной интерфейс плеера убран (hidden=*) — вся обвязка своя, как в макете.
   mute=1 обязателен: без него браузеры не дают автозапуск. */
const clipSrc = (id) =>
  "https://runtime.video.cloud.yandex.net/player/video/" + id +
  "?autoplay=1&mute=1&hidden=*&preload=false&background_color=0A0A0C";

const mmss = (s) => {
  s = Math.max(0, Math.floor(s || 0));
  return String(Math.floor(s / 60)).padStart(2, "0") + ":" + String(s % 60).padStart(2, "0");
};

/* Кадр вписывается в сцену целиком: считаем его коробку внутри 4:5. */
const clipBox = (w, h) => {
  const a = w / h;
  return a > STAGE_AR
    ? { width: "100%", height: (STAGE_AR / a * 100).toFixed(3) + "%" }
    : { width: (a / STAGE_AR * 100).toFixed(3) + "%", height: "100%" };
};

function Process() {
  const [active, setActive] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);
  const [progress, setProgress] = React.useState(0);
  const [time, setTime] = React.useState("00:00");
  const [live, setLive] = React.useState(false);   /* плеер создан только когда секция в кадре */
  const [fade, setFade] = React.useState(false);
  const frameRef = React.useRef(null);
  const stageRef = React.useRef(null);
  const listRef = React.useRef(null);
  const durRef = React.useRef(0);
  const activeRef = React.useRef(0);

  const cur = CLIPS[active];
  React.useEffect(() => { activeRef.current = active; }, [active]);

  const send = (method, params) => {
    const f = frameRef.current;
    if (f && f.contentWindow) f.contentWindow.postMessage(Object.assign({ method: method }, params || {}), "*");
  };

  /* Плеер весит около 2 МБ, поэтому создаём его только когда секция доехала до экрана. */
  React.useEffect(() => {
    const el = stageRef.current;
    if (!el) return;
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => {
        if (e.isIntersecting) setLive(true);
        else if (live) send("pause");
      });
    }, { threshold: 0.25 });
    io.observe(el);
    return () => io.disconnect();
  }, [live]);

  /* События плеера приходят через postMessage — строкой JSON, а не объектом.
     В примере из документации Яндекса разбора строки нет, и без него
     event всегда undefined, а вся обвязка стоит на нулях. */
  React.useEffect(() => {
    const onMsg = (e) => {
      let d = e.data;
      if (typeof d === "string") { try { d = JSON.parse(d); } catch (_) { return; } }
      if (!d || !d.event) return;
      if (d.duration) durRef.current = d.duration;
      if (d.event === "timeupdate" || d.event === "started" || d.event === "resumed" || d.event === "rewound") {
        setTime(mmss(d.time));
        if (durRef.current) setProgress(Math.min(1, (d.time || 0) / durRef.current));
        if (d.event !== "timeupdate") setPlaying(true);
      } else if (d.event === "paused") {
        setPlaying(false); setTime(mmss(d.time));
      } else if (d.event === "ended") {
        step((activeRef.current + 1) % CLIPS.length);
      }
    };
    window.addEventListener("message", onMsg);
    return () => window.removeEventListener("message", onMsg);
  }, []);

  /* Активный пункт подтягиваем внутри списка вручную: scrollIntoView утянул бы
     за собой и саму страницу. */
  React.useEffect(() => {
    const l = listRef.current;
    if (!l) return;
    const el = l.children[active];
    if (!el) return;
    const top = el.offsetTop - l.clientHeight / 2 + el.clientHeight / 2;
    l.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
  }, [active]);

  const step = (i) => {
    if (i === activeRef.current) return;
    setFade(true);
    setTimeout(() => {
      setActive(i); setProgress(0); setTime("00:00"); durRef.current = CLIPS[i][4];
      setFade(false);
      send("updateSource", { id: CLIPS[i][0] });
      send("play");
      setPlaying(true);
    }, 260);
  };

  const toggle = () => {
    if (playing) { send("pause"); setPlaying(false); }
    else { send("play"); setPlaying(true); }
  };

  const seek = (e) => {
    e.stopPropagation();
    const r = e.currentTarget.getBoundingClientRect();
    const frac = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
    const d = durRef.current || cur[4];
    send("seek", { time: frac * d });
    setProgress(frac);
  };

  const glass = { background: "rgba(10,10,12,.55)", backdropFilter: "blur(4px)" };

  return (
    <section id="process" className="rt-snap" style={{ background: "var(--bg-surface)", minHeight: "100vh", display: "flex", flexDirection: "column", justifyContent: "center", padding: "96px 0" }}>
      <div className="rt-reveal" style={{ maxWidth: MAXW, margin: "0 auto 34px", padding: "0 32px", width: "100%" }}>
        <Kicker index="03" label="Вживую" />
        <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(32px, 4vw, 52px)", lineHeight: 1, letterSpacing: "-0.01em", margin: "20px 0 0" }}>Ближе, чем фото</h2>
      </div>

      <div className="rt-reveal" style={{ maxWidth: "1040px", margin: "0 auto", padding: "0 32px", width: "100%" }}>
        <div className="rt-clip-grid" style={{ display: "grid", gridTemplateColumns: "auto 1fr", border: "1px solid var(--border-hair)", background: "var(--bg-base)" }}>

          {/* Сцена */}
          <div ref={stageRef} onClick={toggle} className="rt-clip-stage"
            style={{ position: "relative", height: STAGE_H, width: "calc(" + STAGE_H + " * " + STAGE_AR + ")", overflow: "hidden", background: "var(--ink-800)", cursor: "pointer" }}>

            {/* Размытая подложка — тот же кадр, чтобы поля не были пустыми */}
            <div style={{ position: "absolute", inset: 0, backgroundImage: "url(" + cur[3] + ")", backgroundSize: "cover", backgroundPosition: "center", filter: "blur(26px) brightness(0.45) saturate(0.7)", transform: "scale(1.15)" }}></div>

            <div style={{ position: "absolute", inset: 0, margin: "auto", opacity: fade ? 0 : 1, transition: "opacity .26s ease", ...clipBox(cur[5], cur[6]) }}>
              {live ? (
                <iframe ref={frameRef} title={cur[1]} src={clipSrc(cur[0])} frameBorder="0" scrolling="no"
                  allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
                  style={{ width: "100%", height: "100%", display: "block", border: 0, pointerEvents: "none" }}></iframe>
              ) : (
                <img src={cur[3]} alt={cur[1]} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
              )}
            </div>

            <div style={{ position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(10,10,12,.55), rgba(10,10,12,0) 45%)", pointerEvents: "none" }}></div>

            <div style={{ position: "absolute", top: "18px", left: "18px", display: "flex", alignItems: "center", gap: "9px", fontFamily: "var(--font-mono)", fontSize: "11px", letterSpacing: "0.14em", color: "var(--bone)", padding: "7px 12px", pointerEvents: "none", ...glass }}>
              <span className="rt-rec-dot" style={{ width: "7px", height: "7px", borderRadius: "50%", background: "var(--accent)" }}></span>
              REC {String(active + 1).padStart(2, "0")} · {time} / {mmss(cur[4])}
            </div>

            <button type="button" onClick={(e) => { e.stopPropagation(); toggle(); }}
              aria-label={playing ? "Пауза" : "Смотреть"}
              className="rt-clip-btn"
              style={{ position: "absolute", right: "18px", top: "18px", width: "38px", height: "38px", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--bone)", fontSize: "12px", border: 0, cursor: "pointer", ...glass }}>
              <i className={playing ? "fas fa-pause" : "fas fa-play"} aria-hidden="true"></i>
            </button>

            <div style={{ position: "absolute", left: "20px", right: "20px", bottom: "22px", pointerEvents: "none" }}>
              <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(20px, 2.2vw, 30px)", fontWeight: 500, margin: 0, letterSpacing: "0.01em" }}>{cur[1]}</h3>
            </div>

            <div onClick={seek} role="slider" aria-label="Перемотка" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(progress * 100)}
              style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: "14px", display: "flex", alignItems: "flex-end", cursor: "pointer" }}>
              <div style={{ width: "100%", height: "3px", background: "rgba(236,231,223,0.12)", pointerEvents: "none" }}>
                <div style={{ width: (progress * 100) + "%", height: "100%", background: "var(--accent)", transition: "width .2s linear" }}></div>
              </div>
            </div>
          </div>

          {/* Плейлист */}
          <div className="rt-clip-side" style={{ display: "flex", flexDirection: "column", borderLeft: "1px solid var(--border-hair)", height: STAGE_H, minWidth: 0 }}>
            <div style={{ flexShrink: 0, padding: "20px 24px 13px", display: "flex", alignItems: "center", gap: "12px", fontFamily: "var(--font-body)", fontSize: "11px", fontWeight: 700, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--text-muted)", borderBottom: "1px solid var(--border-hair)" }}>
              <span style={{ color: "var(--accent)" }}>▶</span> Видео из студии
              <span style={{ marginLeft: "auto", color: "var(--text-faint)", letterSpacing: "0.1em" }}>{CLIPS.length}</span>
            </div>
            {/* position: relative обязателен — от него берётся offsetTop пунктов,
                иначе центрирование считает от чужого предка и промахивается. */}
            <div ref={listRef} className="rt-clip-list" style={{ position: "relative", overflowY: "auto", flex: 1, minHeight: 0 }}>
              {CLIPS.map((v, i) => {
                const on = i === active;
                return (
                  <button key={v[0]} type="button" onClick={() => step(i)} aria-current={on ? "true" : undefined}
                    className="rt-clip-item"
                    style={{ position: "relative", width: "100%", display: "grid", gridTemplateColumns: "40px 1fr 24px", gap: "14px", alignItems: "center", textAlign: "left", padding: "14px 24px", background: on ? "rgba(236,231,223,0.04)" : "transparent", border: 0, borderBottom: "1px solid var(--border-hair)", borderLeft: on ? "2px solid var(--accent)" : "2px solid transparent", cursor: "pointer", transition: "background .25s, border-color .25s" }}>
                    {on ? <span style={{ position: "absolute", left: 0, bottom: "-1px", height: "1px", width: (progress * 100) + "%", background: "var(--accent)", transition: "width .2s linear" }}></span> : null}
                    <span style={{ fontFamily: "var(--font-display)", fontSize: "19px", fontWeight: 600, color: on ? "var(--accent)" : "var(--text-faint)", transition: "color .25s" }}>{String(i + 1).padStart(2, "0")}</span>
                    <span style={{ minWidth: 0 }}>
                      <span style={{ display: "block", fontFamily: "var(--font-display)", color: on ? "var(--bone)" : "var(--text-body)", textTransform: "uppercase", letterSpacing: "0.02em", fontSize: "16px", fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", transition: "color .25s" }}>{v[1]}</span>
                      <span style={{ display: "block", fontFamily: "var(--font-body)", color: "var(--text-faint)", fontSize: "11px", letterSpacing: "0.14em", textTransform: "uppercase", marginTop: "3px" }}>{mmss(v[4])}</span>
                    </span>
                    <span style={{ color: on ? "var(--accent)" : "var(--text-faint)", fontSize: "12px", transition: "color .25s" }}>
                      {on ? (
                        <span className="rt-eq" style={{ display: "inline-flex", alignItems: "flex-end", gap: "2px", height: "14px" }}>
                          <span style={{ height: "60%" }}></span><span style={{ height: "100%" }}></span><span style={{ height: "40%" }}></span>
                        </span>
                      ) : <i className="fas fa-play" aria-hidden="true"></i>}
                    </span>
                  </button>
                );
              })}
            </div>
          </div>

        </div>
      </div>
    </section>
  );
}

/* --------------------------------------------------------------- Services */
const SERVICES = [
  ["Авторские татуировки", "Уникальные художественные работы по вашему эскизу или моему дизайну.", "fas fa-pen-nib"],
  ["Коррекция и перекрытие", "Профессиональное исправление и перекрытие неудачных татуировок.", "fas fa-layer-group"],
  ["Тату на шрамах", "Специальные техники нанесения на рубцовую ткань.", "fas fa-shield-heart"],
  ["Перманентный татуаж", "Микроблейдинг, перманентный макияж бровей, губ и век.", "fas fa-feather"],
];

function Services({ onBook }) {
  const [hover, setHover] = React.useState(-1);
  return (
    <section id="services" className="rt-snap" style={{ background: "var(--bg-base)", minHeight: "100vh", display: "flex", alignItems: "center", padding: "110px 0" }}>
      <div className="rt-reveal" style={{ maxWidth: MAXW, margin: "0 auto", padding: "0 32px", width: "100%" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: "56px", flexWrap: "wrap", gap: "20px" }}>
          <div>
            <Kicker index="04" label="Услуги" />
            <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(32px, 4vw, 52px)", lineHeight: 1, letterSpacing: "-0.01em", margin: "20px 0 0" }}>Что я делаю</h2>
          </div>
          <Button variant="ghost" onClick={onBook} iconRight="fas fa-arrow-right">Рассчитать стоимость</Button>
        </div>
        <div style={{ borderTop: "1px solid var(--border-hair)" }}>
          {SERVICES.map(([title, body], i) => (
            <div key={title}
              onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}
              style={{ display: "grid", gridTemplateColumns: "70px 1.1fr 1.4fr 40px", gap: "28px", alignItems: "center", padding: "30px 16px", borderBottom: "1px solid var(--border-hair)", background: hover === i ? "rgba(236,231,223,0.03)" : "transparent", transition: "background .25s ease" }} className="rt-svc-row">
              <span style={{ fontFamily: "var(--font-display)", fontSize: "22px", fontWeight: 600, color: hover === i ? "var(--accent)" : "var(--text-faint)", transition: "color .25s" }}>{String(i + 1).padStart(2, "0")}</span>
              <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", letterSpacing: "0.01em", fontSize: "clamp(22px, 2.4vw, 30px)", fontWeight: 500, margin: 0 }}>{title}</h3>
              <p style={{ color: "var(--text-muted)", margin: 0, lineHeight: 1.6, fontSize: "15px" }} className="rt-svc-desc">{body}</p>
              <span style={{ justifySelf: "end", color: hover === i ? "var(--accent)" : "var(--text-faint)", fontSize: "18px", transform: hover === i ? "translateX(4px)" : "translateX(0)", transition: "transform .25s, color .25s" }}><i className="fas fa-arrow-right"></i></span>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* --------------------------------------------------------------- Benefits */
const BENEFITS = [
  ["fas fa-shield-halved", "100% стерильность", "Только одноразовые иглы и медицинская стерилизация в автоклаве."],
  ["fas fa-droplet", "Премиальные пигменты", "Eternal Ink, Dynamic Color и Fusion Ink — яркие и долговечные."],
  ["fas fa-gem", "Эксклюзивные эскизы", "Каждый дизайн создаётся специально для вас."],
  ["fas fa-infinity", "Пожизненная гарантия", "Если потребуется коррекция через годы — сделаю бесплатно."],
  ["fas fa-couch", "Максимальный комфорт", "Просторный кабинет, музыка, кино и игры во время сеанса."],
  ["fas fa-location-dot", "Центр Москвы", "Студия рядом со станцией метро «Смоленская»."],
];

function Benefits() {
  return (
    <section id="benefits" className="rt-snap" style={{ background: "var(--bg-surface)", minHeight: "100vh", display: "flex", alignItems: "center", padding: "110px 0" }}>
      <div className="rt-reveal" style={{ maxWidth: MAXW, margin: "0 auto", padding: "0 32px", width: "100%" }}>
        <div style={{ marginBottom: "48px" }}>
          <Kicker index="05" label="Преимущества" />
          <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(32px, 4vw, 52px)", lineHeight: 1, letterSpacing: "-0.01em", margin: "20px 0 0", maxWidth: "16ch" }}>Почему мне доверяют</h2>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "1px", background: "var(--border-hair)", border: "1px solid var(--border-hair)" }} className="rt-benefits-grid">
          {BENEFITS.map(([icon, title, body]) => (
            <div key={title} style={{ background: "var(--bg-surface)", padding: "36px 32px", display: "flex", flexDirection: "column", gap: "14px" }}>
              <i className={icon} style={{ color: "var(--accent)", fontSize: "24px" }}></i>
              <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", letterSpacing: "0.01em", fontSize: "21px", fontWeight: 500, margin: 0 }}>{title}</h3>
              <p style={{ color: "var(--text-muted)", margin: 0, lineHeight: 1.6, fontSize: "15px" }}>{body}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ----------------------------------------------------------- Testimonials */
const YANDEX_REVIEWS = "https://yandex.ru/maps/org/rated_tattoo/1269715236/reviews/";
const REVIEWS = [
  { name: "Артур", date: "08.05.2026", avatar: "./assets/img/reviews/av-artur.jpg", photo: "./assets/img/reviews/ph-artur.jpg", text: "Очень круто, всё понравилось — лучшее место, где я набивал тату." },
  { name: "Елизавета З.", date: "21.02.2026", avatar: "./assets/img/reviews/av-elizaveta.jpg", photo: "./assets/img/reviews/ph-elizaveta.jpg", text: "Решалась долго, но когда созрела — нашла Тимура и не прогадала. Работы зацепили даже через экран, а в кабинете невероятная атмосфера. Всё аккуратно и стерильно." },
  { name: "Лидия Лукьянова", date: "29.10.2025", avatar: "./assets/img/reviews/av-lidiya.jpg", photo: "./assets/img/reviews/ph-lidiya.jpg", text: "Тимур — профессионал, и при этом с ним кайфово: поболтать, фильм, поржать. Кайфуешь от всего процесса, а не только от результата. Теперь только ему доверяю." },
  { name: "Владимир З.", date: "24.10.2025", avatar: "./assets/img/reviews/av-vladimir.jpg", photo: null, text: "Татуировка огонь, качественная работа, рука мастера. Вайбовая атмосфера, приятное общение." },
  { name: "Елена Пушкина", date: "07.08.2025", avatar: "./assets/img/reviews/av-elena.jpg", photo: null, text: "Всё прошло идеально: профессионально, аккуратно и очень красиво. Тимур внимателен к деталям и создаёт комфортную атмосферу." },
  { name: "Дмитрий Кэт", date: "24.03.2025", avatar: null, photo: "./assets/img/reviews/ph-dmitriy.jpg", text: "Тимур лучший мастер! Судьба свела с ним повторно после долгого перерыва — невероятно круто. Профессионализм и внимание к деталям." },
  { name: "Нина Г.", date: "05.11.2024", avatar: "./assets/img/reviews/av-nina.jpg", photo: "./assets/img/reviews/ph-nina.jpg", text: "Была у Тимура ещё 7 лет назад — и снова знала, к кому ехать. Мои друзья тоже ездили, все остались довольны!" },
  { name: "Юлия В.", date: "26.05.2024", avatar: "./assets/img/reviews/av-yulia.jpg", photo: "./assets/img/reviews/ph-yulia.jpg", text: "Невозможно выразить словами восторг от результата! Тимур — не просто тату-мастер, а художник, реализующий любую фантазию." },
];

function ReviewStars() {
  return (
    <div style={{ display: "flex", gap: "3px", color: "var(--star)", fontSize: "12px" }} aria-label="5 из 5">
      {[0, 1, 2, 3, 4].map((i) => <i key={i} className="fas fa-star" aria-hidden="true"></i>)}
    </div>
  );
}

function Testimonials() {
  return (
    <section id="reviews" className="rt-snap" style={{ background: "var(--bg-base)", minHeight: "100vh", display: "flex", alignItems: "center", padding: "110px 0", borderTop: "1px solid var(--border-hair)" }}>
      <div className="rt-reveal" style={{ maxWidth: MAXW, margin: "0 auto", padding: "0 32px", width: "100%" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: "20px", marginBottom: "44px" }}>
          <div>
            <Kicker index="06" label="Отзывы" />
            <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(32px, 4vw, 52px)", lineHeight: 1, letterSpacing: "-0.01em", margin: "20px 0 0" }}>Что говорят клиенты</h2>
          </div>
          <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "12px" }}>
            <StarRating value={5} score="5.0" count="160 оценок · Яндекс" size="18px" />
            <a href={YANDEX_REVIEWS} target="_blank" rel="noopener noreferrer" style={{ fontFamily: "var(--font-body)", fontSize: "12px", fontWeight: 700, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--text-muted)", transition: "color .2s" }}
               onMouseEnter={(e) => (e.currentTarget.style.color = "var(--bone)")}
               onMouseLeave={(e) => (e.currentTarget.style.color = "var(--text-muted)")}>Все отзывы на Яндексе →</a>
          </div>
        </div>
        <div className="rt-reviews-grid" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: "14px", alignItems: "stretch" }}>
          {REVIEWS.map((r) => (
            <div key={r.name + r.date} style={{ border: "1px solid var(--border-hair)", background: "var(--bg-surface)", padding: "22px", display: "flex", flexDirection: "column", gap: "16px", height: "100%" }}>
              <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
                {r.avatar
                  ? <img src={r.avatar} alt={r.name} loading="lazy" style={{ width: "46px", height: "46px", borderRadius: "50%", objectFit: "cover", flexShrink: 0, background: "var(--ink-700)" }} />
                  : <div style={{ width: "46px", height: "46px", borderRadius: "50%", flexShrink: 0, background: "var(--accent)", color: "var(--white)", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: "20px" }}>{r.name[0]}</div>}
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", letterSpacing: "0.03em", fontSize: "16px", fontWeight: 500, lineHeight: 1.1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.name}</div>
                  <div style={{ display: "flex", alignItems: "center", gap: "8px", marginTop: "6px" }}>
                    <ReviewStars />
                    <span style={{ color: "var(--text-faint)", fontSize: "12px", letterSpacing: "0.04em" }}>{r.date}</span>
                  </div>
                </div>
              </div>
              <p style={{ color: "var(--text-body)", margin: 0, lineHeight: 1.55, fontSize: "14.5px", minHeight: "67px", display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: 3, overflow: "hidden" }}>{r.text}</p>
              <div style={{ marginTop: "auto", overflow: "hidden", height: "188px", border: "1px solid var(--border-hair)" }}>
                {r.photo ? (
                  <a href={YANDEX_REVIEWS} target="_blank" rel="noopener noreferrer" aria-label={"Татуировка — отзыв " + r.name} style={{ display: "block", height: "100%", lineHeight: 0 }}>
                    <img src={r.photo} alt={"Татуировка — отзыв " + r.name} loading="lazy" style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: "center", display: "block" }} />
                  </a>
                ) : (
                  <div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--bg-base)", position: "relative" }}>
                    <span style={{ fontFamily: "var(--font-display)", color: "var(--accent)", fontSize: "120px", lineHeight: 0.5, opacity: 0.5 }}>”</span>
                    <span style={{ position: "absolute", bottom: "14px", fontFamily: "var(--font-body)", fontSize: "10px", fontWeight: 700, letterSpacing: "0.2em", textTransform: "uppercase", color: "var(--text-faint)" }}>Отзыв с Яндекса</span>
                  </div>
                )}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* --------------------------------------------------------------- FAQ */
const FAQ = [
  { q: "Как узнать стоимость татуировки?", a: <p style={{ margin: 0 }}>Стоимость зависит от размера, сложности, места нанесения и деталей. Минимальная стоимость сеанса — 15 000 ₽. Запишитесь на бесплатную консультацию для точного расчёта.</p> },
  { q: "Больно ли делать тату?", a: <p style={{ margin: 0 }}>Ощущения индивидуальны и зависят от зоны тела и болевого порога. Использую обезболивающие гели. Рекомендую выспаться, плотно поесть и не пить алкоголь за 24 часа.</p> },
  { q: "Как ухаживать за татуировкой?", a: <p style={{ margin: 0 }}>Не снимать плёнку 2–4 часа, аккуратно мыть тёплой водой с мылом, наносить мазь 2–3 раза в день, избегать солнца и бассейнов. Мы всегда на связи при заживлении.</p> },
];

function Faq() {
  return (
    <section id="faq" className="rt-snap" style={{ background: "var(--bg-surface)", minHeight: "100vh", display: "flex", alignItems: "center", padding: "110px 0" }}>
      <div className="rt-reveal rt-faq-grid" style={{ maxWidth: "1000px", margin: "0 auto", padding: "0 32px", width: "100%", display: "grid", gridTemplateColumns: "0.7fr 1.3fr", gap: "64px", alignItems: "start" }}>
        <div>
          <Kicker index="07" label="FAQ" />
          <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(30px, 3.4vw, 46px)", lineHeight: 1, letterSpacing: "-0.01em", margin: "20px 0 0" }}>Частые вопросы</h2>
        </div>
        <Accordion items={FAQ} />
      </div>
    </section>
  );
}

/* --------------------------------------------------------------- CTA */
function Cta({ onBook }) {
  return (
    <section id="cta" className="rt-snap" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", display: "flex", alignItems: "center", padding: "120px 0" }}>
      <div style={{ position: "absolute", inset: 0, zIndex: 0 }}>
        <img src="./assets/img/work-mandala.jpg" alt="" style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: "center 35%", filter: "grayscale(0.7) contrast(1.05) brightness(0.5)" }} />
        <div style={{ position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(10,10,12,.86), rgba(10,10,12,.92))" }}></div>
      </div>
      <div className="rt-reveal" style={{ position: "relative", zIndex: 1, maxWidth: MAXW, margin: "0 auto", padding: "0 32px", textAlign: "center", width: "100%" }}>
        <div style={{ display: "inline-block" }}><Kicker index="—" label="Запись открыта" /></div>
        <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--bone)", textTransform: "uppercase", fontSize: "clamp(40px, 7vw, 96px)", lineHeight: 0.95, letterSpacing: "-0.01em", margin: "26px 0 28px" }}>
          Создадим вашу<br />татуировку<span style={{ color: "var(--accent)" }}>.</span>
        </h2>
        <p style={{ fontSize: "var(--fs-lead)", color: "var(--gray-200)", maxWidth: "46ch", margin: "0 auto 40px", lineHeight: 1.55 }}>
          Запишитесь на бесплатную консультацию — обсудим идею, стиль и точную стоимость.
        </p>
        <div style={{ display: "flex", gap: "14px", justifyContent: "center", flexWrap: "wrap" }}>
          <Button variant="primary" size="lg" onClick={onBook} iconRight="fas fa-arrow-right">Записаться сейчас</Button>
          <Button as="a" href="tel:+79689752099" variant="glass" size="lg" iconLeft="fas fa-phone">+7 (968) 975-20-99</Button>
        </div>
      </div>
    </section>
  );
}

/* ----------------------------------------------------------------- Footer */
function Footer() {
  const socials = [
    ["fab fa-vk", "VK", "https://vk.com/ratedtattoo"],
    ["fab fa-yandex", "Яндекс Услуги", "https://uslugi.yandex.ru/profile/RatedTattoo-sprav1269715236"],
  ];
  return (
    <footer style={{ background: "var(--bg-base)", borderTop: "1px solid var(--border-hair)", padding: "72px 0 36px" }}>
      <div style={{ maxWidth: MAXW, margin: "0 auto", padding: "0 32px" }}>
        <div style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr 1fr", gap: "48px" }} className="rt-footer-grid">
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: "12px", marginBottom: "18px" }}>
              <img src={LOGO} alt="" style={{ height: "40px" }} />
              <span style={{ fontFamily: "var(--font-logo)", color: "var(--bone)", fontSize: "22px", letterSpacing: "0.16em" }}>RATED TATTOO</span>
            </div>
            <p style={{ color: "var(--text-muted)", margin: "0 0 22px", maxWidth: "38ch", lineHeight: 1.6 }}>Премиум татуировки в центре Москвы. Авторский подход и абсолютная безопасность.</p>
            <div style={{ display: "flex", gap: "10px" }}>
              {socials.map(([s, t, u]) => (
                <a key={s} href={u} title={t} aria-label={t} target="_blank" rel="noopener noreferrer" style={{ width: "42px", height: "42px", display: "flex", alignItems: "center", justifyContent: "center", border: "1px solid var(--border-hair)", color: "var(--text-body)", fontSize: "16px", transition: "all .2s" }}
                   onMouseEnter={(e) => { e.currentTarget.style.color = "var(--bone)"; e.currentTarget.style.borderColor = "var(--accent)"; e.currentTarget.style.background = "var(--accent)"; }}
                   onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-body)"; e.currentTarget.style.borderColor = "var(--border-hair)"; e.currentTarget.style.background = "transparent"; }}><i className={s} aria-hidden="true"></i></a>
              ))}
            </div>
          </div>
          <div>
            <h4 style={{ fontFamily: "var(--font-body)", color: "var(--text-faint)", fontSize: "12px", letterSpacing: "0.18em", textTransform: "uppercase", margin: "0 0 18px" }}>Навигация</h4>
            <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: "12px" }}>
              {[["#about", "О мастере"], ["#works", "Работы"], ["#benefits", "Преимущества"], ["#faq", "Вопросы"]].map(([h, l]) => (
                <li key={h}><a href={h} style={{ color: "var(--text-body)", fontSize: "15px" }}>{l}</a></li>
              ))}
            </ul>
          </div>
          <div>
            <h4 style={{ fontFamily: "var(--font-body)", color: "var(--text-faint)", fontSize: "12px", letterSpacing: "0.18em", textTransform: "uppercase", margin: "0 0 18px" }}>Контакты</h4>
            <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: "12px", color: "var(--text-body)", fontSize: "15px" }}>
              <li><a href="tel:+79689752099">+7 (968) 975-20-99</a></li>
              <li>Новинский бульвар, 10, стр. 1</li>
              <li>Пн–Вс · 11:00 – 21:00</li>
            </ul>
          </div>
        </div>
        <div style={{ marginTop: "56px", paddingTop: "24px", borderTop: "1px solid var(--border-hair)", display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: "10px", color: "var(--text-faint)", fontSize: "12px", letterSpacing: "0.04em" }}>
          <span>© 2026 Тимур Тэд · Rated Tattoo</span>
          <span>Москва · с 2015</span>
        </div>
      </div>
    </footer>
  );
}

/* -------------------------------------------------------- Booking modal */
/* Formspree endpoint. Submissions land in the Formspree dashboard / forwarded
   email for form xaqzkbwy. Plain fetch POST (no @formspree/* package — this
   site has no bundler), with loading + error states handled below. */
const FORMSPREE_ENDPOINT = "https://formspree.io/f/xaqzkbwy";

function BookingModal({ open, onClose }) {
  const [done, setDone] = React.useState(false);
  const [sending, setSending] = React.useState(false);
  const [error, setError] = React.useState("");
  const [form, setForm] = React.useState({ name: "", phone: "", idea: "" });

  React.useEffect(() => {
    if (open) { setDone(false); setError(""); setSending(false); setForm({ name: "", phone: "", idea: "" }); }
  }, [open]);
  React.useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, onClose]);

  const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));

  const submit = async (e) => {
    e.preventDefault();
    if (sending) return;
    setError("");
    if (!form.name.trim() || !form.phone.trim()) {
      setError("Укажите имя и телефон.");
      return;
    }
    setSending(true);
    try {
      const res = await fetch(FORMSPREE_ENDPOINT, {
        method: "POST",
        headers: { "Accept": "application/json", "Content-Type": "application/json" },
        body: JSON.stringify({
          name: form.name,
          phone: form.phone,
          idea: form.idea,
          _subject: "Новая заявка с сайта Rated Tattoo",
        }),
      });
      if (res.ok) {
        setDone(true);
      } else {
        const data = await res.json().catch(() => ({}));
        const msg = data && data.errors && data.errors.map((x) => x.message).join(", ");
        setError(msg || "Не удалось отправить заявку. Попробуйте позже или позвоните нам.");
      }
    } catch (_) {
      setError("Нет связи с сервером. Проверьте интернет или позвоните по телефону.");
    } finally {
      setSending(false);
    }
  };

  if (!open) return null;
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 100, background: "rgba(5,5,6,.82)", backdropFilter: "blur(6px)", display: "flex", alignItems: "center", justifyContent: "center", padding: "20px" }}>
      <div onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Запись на консультацию" style={{ width: "100%", maxWidth: "460px", background: "var(--bg-surface)", border: "1px solid var(--border-hair)", padding: "40px", position: "relative" }}>
        <span style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "3px", background: "var(--accent)" }}></span>
        {done ? (
          <div style={{ textAlign: "center" }}>
            <div style={{ fontSize: "44px", color: "var(--accent)", marginBottom: "16px" }}><i className="fas fa-circle-check"></i></div>
            <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", margin: "0 0 10px", fontSize: "26px", fontWeight: 600 }}>Заявка отправлена</h3>
            <p style={{ color: "var(--text-muted)", margin: "0 0 28px" }}>Тимур свяжется с вами в течение часа.</p>
            <Button variant="primary" block onClick={onClose}>Готово</Button>
          </div>
        ) : (
          <form onSubmit={submit}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "26px" }}>
              <div>
                <Kicker index="—" label="Запись" />
                <h3 style={{ fontFamily: "var(--font-display)", color: "var(--bone)", textTransform: "uppercase", margin: "14px 0 0", fontSize: "28px", fontWeight: 600, lineHeight: 1 }}>Бесплатная<br />консультация</h3>
              </div>
              <button type="button" onClick={onClose} aria-label="Закрыть" style={{ background: "none", border: "none", color: "var(--text-muted)", fontSize: "20px", cursor: "pointer" }}><i className="fas fa-xmark"></i></button>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
              <Input label="Имя" icon="fas fa-user" name="name" value={form.name} onChange={set("name")} disabled={sending} required placeholder="Как вас зовут?" />
              <Input label="Телефон" icon="fas fa-phone" type="tel" name="phone" value={form.phone} onChange={set("phone")} disabled={sending} required placeholder="+7 (___) ___-__-__" />
              <Input label="Опишите идею" as="textarea" name="idea" value={form.idea} onChange={set("idea")} disabled={sending} placeholder="Стиль, размер, место на теле…" />
              {error ? (
                <div role="alert" style={{ color: "var(--accent-soft)", fontSize: "13px", lineHeight: 1.5 }}>{error}</div>
              ) : null}
              <Button variant="primary" block type="submit" disabled={sending} iconRight={sending ? undefined : "fas fa-arrow-right"}>{sending ? "Отправка…" : "Отправить заявку"}</Button>
            </div>
          </form>
        )}
      </div>
    </div>
  );
}

/* --------------------------------------------------------------- Dot nav */
function DotNav() {
  const items = [["hero", "Главная"], ["about", "Мастер"], ["works", "Работы"], ["process", "Вживую"], ["services", "Услуги"], ["benefits", "Преимущества"], ["reviews", "Отзывы"], ["faq", "Вопросы"], ["cta", "Запись"]];
  const [active, setActive] = React.useState("hero");
  React.useEffect(() => {
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) setActive(e.target.id); });
    }, { threshold: 0.5 });
    items.forEach(([id]) => { const el = document.getElementById(id); if (el) io.observe(el); });
    return () => io.disconnect();
  }, []);
  return (
    <nav className="rt-dotnav" style={{ position: "fixed", right: "26px", top: "50%", transform: "translateY(-50%)", zIndex: 60, display: "flex", flexDirection: "column", gap: "15px", alignItems: "flex-end" }}>
      {items.map(([id, label]) => {
        const on = active === id;
        return (
          <a key={id} href={"#" + id} className="rt-dot" title={label} style={{ display: "flex", alignItems: "center", gap: "12px", justifyContent: "flex-end" }}>
            <span className="rt-dot-label" style={{ fontFamily: "var(--font-body)", fontSize: "10px", fontWeight: 700, letterSpacing: "0.18em", textTransform: "uppercase", color: on ? "var(--bone)" : "var(--text-muted)", opacity: 0, transform: "translateX(6px)", transition: "opacity .25s, transform .25s, color .25s", whiteSpace: "nowrap" }}>{label}</span>
            <span style={{ width: on ? "11px" : "7px", height: on ? "11px" : "7px", borderRadius: "50%", background: on ? "var(--accent)" : "transparent", border: "1px solid", borderColor: on ? "var(--accent)" : "var(--text-faint)", transition: "all .25s var(--ease-out)" }}></span>
          </a>
        );
      })}
    </nav>
  );
}

Object.assign(window, { Header, Hero, About, Works, Services, Benefits, Testimonials, Faq, Cta, Footer, BookingModal, DotNav });
