// СУ 47 — Section components · "Industrial Precision" IA.
// Nav · Hero · About/trust · Prices · Order(merge) · Pump · FAQ · Contacts · Footer · StickyCall · EditBridge

const { useState: useStateS, useEffect: useEffectS, useRef: useRefS } = React;

const PHONE_TEL = '+79278916160';
const PHONE_HUMAN = '+7 (927) 891-61-60';
// City office landline (shown alongside the mobile — see Contacts / Footer).
const PHONE2_TEL = '+78482616160';
const PHONE2_HUMAN = '+7 (8482) 61-61-60';

function Nav() {
  const [open, setOpen] = useStateS(false);
  const [tuck, setTuck] = useStateS(false); // hide-on-scroll-down (mobile)
  const menuBtnRef = useRefS(null);
  const overlayRef = useRefS(null);

  // Tuck the header away while scrolling down, reveal on scroll up — so the
  // order form on mobile isn't sandwiched between the sticky header and the
  // sticky total/submit. Near the top it's always shown; never tuck when the
  // menu is open. CSS gates the actual transform to mobile widths.
  useEffectS(() => {
    // Peak/trough hysteresis so momentum, sub-pixel rounding and iOS rubber-band
    // can't flip the state every frame. To HIDE, scrollY must rise DELTA px past
    // the lowest point seen since we last revealed; to SHOW, it must drop DELTA
    // px below the highest point since we last hid. The extreme only moves in the
    // favourable direction, so small back-and-forth wobble never re-crosses.
    // DELTA (20px) sits above typical momentum/overscroll jitter while still
    // revealing after a single normal scroll step. scrollY is clamped to >=0 to
    // ignore the negative overscroll (rubber-band) region.
    // Auto-hide is disabled on iOS. In older in-app WebViews (e.g. Telegram on
    // older iPhones) the scroll position and viewport height wobble as the browser
    // chrome shows/hides, which makes the show/hide toggle flicker no matter how
    // much hysteresis we add. There the header simply stays sticky and visible.
    // Android / desktop-mobile keep the hide-on-scroll behaviour.
    const isIOS = /iP(hone|od|ad)/.test(navigator.userAgent)
      || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
    if (isIOS) return;
    const SHOW_NEAR_TOP = 120, DELTA = 20;
    let tucked = false, peak = Math.max(0, window.scrollY), trough = peak, ticking = false;
    const update = () => {
      ticking = false;
      const y = Math.max(0, window.scrollY);
      if (y > peak) peak = y;
      if (y < trough) trough = y;
      let next = tucked;
      if (y < SHOW_NEAR_TOP) next = false;
      else if (!tucked && y > trough + DELTA) { next = true; peak = y; }
      else if (tucked && y < peak - DELTA) { next = false; trough = y; }
      // Pin the relevant extreme to the current position while inside the band,
      // so a reversal is measured from the true turning point.
      if (next) peak = Math.max(peak, y); else trough = Math.min(trough, y);
      if (next !== tucked) { tucked = next; setTuck(next); } // only set on real change
    };
    const onScroll = () => { if (!ticking) { ticking = true; requestAnimationFrame(update); } };
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  const links = [
    { href: '#prices', label: 'Цены' },
    { href: '#order', label: 'Расчёт' },
    { href: '#reviews', label: 'Отзывы' },
    { href: '#faq', label: 'FAQ' },
    { href: '#contacts', label: 'Контакты' },
  ];

  // Lock scroll while the overlay is open; move focus into the overlay on open.
  // The body class lets the sticky tap-to-call bar hide behind the full menu.
  useEffectS(() => {
    document.body.style.overflow = open ? 'hidden' : '';
    document.body.classList.toggle('menu-open', open);
    if (open) overlayRef.current?.querySelector('a')?.focus();
    return () => { document.body.style.overflow = ''; document.body.classList.remove('menu-open'); };
  }, [open]);

  const closeMenu = () => { setOpen(false); menuBtnRef.current?.focus(); };

  // Keyboard: Escape closes (returning focus to the toggle); Tab is trapped
  // within the open overlay so focus can't wander to the page behind it.
  const onOverlayKeyDown = (e) => {
    if (e.key === 'Escape') { e.preventDefault(); closeMenu(); return; }
    if (e.key !== 'Tab') return;
    const items = overlayRef.current?.querySelectorAll('a, button');
    if (!items || !items.length) return;
    const first = items[0], last = items[items.length - 1];
    if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
    else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
  };

  return (
    <>
    <nav className={`nav ${tuck && !open ? 'nav--tuck' : ''}`}>
      <div className="container">
        <div className="nav__inner">
          <a href="#top" className="nav__logo" onClick={() => setOpen(false)}>
            СУ 47 <span className="dot">·</span> <sub>бетон</sub>
          </a>

          <div className="nav__links">
            {links.map(l => <a key={l.href} href={l.href}>{l.label}</a>)}
          </div>

          <div className="nav__right">
            <a href={`tel:${PHONE_TEL}`} className="nav__phone">
              <svg className="nav__phone-ic" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.81.36 1.6.7 2.34a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.74-1.27a2 2 0 0 1 2.11-.45c.74.34 1.53.57 2.34.7A2 2 0 0 1 22 16.92z"/></svg>
              {PHONE_HUMAN}
            </a>
            <a href="#order" className="btn btn--rust btn--sm nav__cta">Заявка <span className="arrow">→</span></a>
            <button
              ref={menuBtnRef}
              className="nav__menu-btn"
              aria-label={open ? 'Закрыть меню' : 'Открыть меню'}
              aria-expanded={open}
              aria-controls="nav-overlay"
              onClick={() => setOpen(o => !o)}
            >
              <span></span>
            </button>
          </div>
        </div>
      </div>
    </nav>

    <div
      id="nav-overlay"
      ref={overlayRef}
      className={`nav__overlay ${open ? 'nav__overlay--open' : ''}`}
      hidden={!open}
      onKeyDown={onOverlayKeyDown}
    >
      <span className="nav__overlay-eyebrow">Меню&nbsp;· СУ&nbsp;47&nbsp;— бетон</span>
      <div className="nav__overlay-links">
        {links.map((l, i) => (
          <a key={l.href} href={l.href} onClick={closeMenu}>
            <span className="nav__overlay-num">{String(i + 1).padStart(2, '0')}</span>
            <span>{l.label}</span>
          </a>
        ))}
      </div>
      <div className="nav__overlay-foot">
        <a className="nav__overlay-call" href={`tel:${PHONE_TEL}`} onClick={closeMenu}>
          <span className="nav__overlay-call-k">Звонок бесплатный</span>
          <span className="nav__overlay-call-v">{PHONE_HUMAN}</span>
        </a>
        <a href="#order" className="btn btn--rust btn--lg btn--block" onClick={closeMenu}>Оставить заявку <span className="arrow">→</span></a>
        <span className="nav__overlay-hours">пн–сб&nbsp;· 7:00–19:00&nbsp;· Тольятти и&nbsp;область</span>
      </div>
    </div>
    </>
  );
}

// Numeric grades of the first price group (Товарный бетон) — the concrete marks.
// Read straight from the live price list so the hero's "М100–М400" follows
// whatever grades the owner keeps in the admin panel. Stops at the second section
// divider so the раствор group never widens the concrete range.
function concreteGradeNums(prices) {
  const nums = [];
  let sections = 0;
  for (const row of (prices || [])) {
    if (row.section) { sections++; if (sections > 1) break; continue; }
    const n = parseInt(String(row.grade || '').replace(/\D/g, ''), 10);
    if (Number.isFinite(n)) nums.push(n);
  }
  return nums;
}
// "М100–М400" (or just "М300" if a single grade). `sep` joins min/max so the
// hero lede can repeat the «М» prefix and the stat strip can drop it.
function gradeRange(prices, sep = '–М') {
  const nums = concreteGradeNums(prices);
  if (!nums.length) return 'М100–М400';
  const min = Math.min(...nums), max = Math.max(...nums);
  return min === max ? `М${min}` : `М${min}${sep}${max}`;
}

function Hero() {
  // Lowest price comes from the live data, so the "от … ₽/м³" line stays correct
  // when the owner edits the price list in the admin panel.
  const priceNums = (SU47_DATA.prices || []).filter(p => typeof p.price === 'number').map(p => p.price);
  const minPrice = priceNums.length ? Math.min(...priceNums) : 4300;
  // Marka range, also live from the price list (see gradeRange).
  const gradesLede = gradeRange(SU47_DATA.prices, '–М'); // "М100–М400"
  const gradesStat = gradeRange(SU47_DATA.prices, '–');  // "М100–400"
  return (
    <header className="hero" id="top">
      {/* photo: Pexels — Russian batching plant + mixer · pexels.com/photo/12032991 */}
      <img className="hero__photo" src="/assets/photos/hero.jpg" alt="" aria-hidden="true" loading="eager" fetchpriority="high" decoding="async" />
      <div className="container hero__inner">
        <p className="hero__eyebrow">
          <span className="hero__live"><span className="dot" aria-hidden="true"></span>Тольятти и&nbsp;Самарская область</span>
          <span className="hero__eyebrow-sep" aria-hidden="true"></span>
          <span>Пн–сб&nbsp;· 7:00–19:00</span>
        </p>

        <h1 className="hero__title">Бетонно-растворный завод <em>«СУ&nbsp;47»</em></h1>

        <p className="hero__lede">
          Товарный бетон {gradesLede} и&nbsp;раствор от&nbsp;<strong>{minPrice.toLocaleString('ru')}&nbsp;₽/м³</strong>. Производство, продажа и&nbsp;доставка по&nbsp;Тольятти и&nbsp;Самарской области.
        </p>

        <div className="hero__actions">
          <a href="#order" className="btn btn--rust btn--lg">Узнать цену с&nbsp;доставкой <span className="arrow">→</span></a>
          <a href={`tel:${PHONE_TEL}`} className="btn btn--on-dark btn--lg hero__call">Позвонить — ответим сразу</a>
        </div>

        <dl className="hero__stats">
          <div className="hero__stat"><dt>С&nbsp;2009</dt><dd>года на&nbsp;рынке</dd></div>
          <div className="hero__stat"><dt>{gradesStat}</dt><dd>бетон и&nbsp;раствор</dd></div>
          <div className="hero__stat"><dt>{Math.max(10, Math.floor((SU47_DATA.districts?.length || 0) / 10) * 10)}+</dt><dd>районов доставки</dd></div>
          <div className="hero__stat"><dt>GPS</dt><dd>контроль рейсов</dd></div>
        </dl>
      </div>
    </header>
  );
}

function About() {
  const proof = [
    { label: 'Качество', desc: 'На всех этапах производства осуществляется строгий контроль за качеством изготовляемой продукции.' },
    { label: 'Ответственность', desc: '«Недогруз» или отгрузка продукции другой марки исключены. GPS на каждом рейсе.' },
    { label: 'Сервис', desc: 'Мы всегда готовы предоставить бесплатную и компетентную консультацию по любому вашему вопросу.' },
  ];
  return (
    <section className="section about" id="about">
      <div className="container">
        <div className="about__grid">
          <div className="head head--wide about__head reveal">
            <span className="head__eyebrow">«Строительное Управление&nbsp;47»</span>
            <h2 className="about__title">Производим и&nbsp;доставляем <em>сами</em></h2>
            <div className="about__copy">
              <p>У&nbsp;нас вы можете купить товарный бетон и&nbsp;цементный раствор по&nbsp;доступным ценам&nbsp;— для организаций и&nbsp;частных лиц.</p>
              <p>Мы производим продукцию высокого качества на&nbsp;полностью автоматизированных заводах&nbsp;— это позволяет контролировать качество на&nbsp;всех этапах производства.</p>
              <p>Собственный парк техники и&nbsp;удобное расположение заводов снижают стоимость доставки, а&nbsp;современное оборудование позволяет выпускать до&nbsp;1500&nbsp;м³ бетона в&nbsp;сутки. Мы не&nbsp;завышаем и&nbsp;не&nbsp;занижаем цены.</p>
            </div>
          </div>

          {/* photo: Pexels — batching plant + mixer fleet, aerial · pexels.com/photo/9716229 */}
          <figure className="about__media reveal">
            <img
              className="about__img"
              src="/assets/photos/about.jpg"
              alt="Бетонный завод — производственный узел с силосами цемента"
              width="1280"
              height="960"
              loading="lazy"
            />
          </figure>
        </div>

        <div className="proof reveal reveal-stagger">
          {proof.map((p, i) => (
            <div className="proof__item" key={i}>
              <span className="proof__label">{p.label}<span className="proof__desc">{p.desc}</span></span>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

function Prices() {
  const items = SU47_DATA.prices;
  return (
    <section className="section prices" id="prices">
      <div className="container">
        <div className="head head--center reveal">
          <h2>Цены на&nbsp;бетон и&nbsp;раствор</h2>
          <p className="head__lede">Цена за&nbsp;м³. Доставка считается отдельно — по&nbsp;району и&nbsp;объёму.</p>
        </div>

        <div className="prices__sheet reveal reveal-stagger">
          {(() => {
            // Carry the price SECTION as the product with the deep-link, so a
            // раствор row preselects раствор (at раствор price) and a бетон row
            // preselects бетон — the two share mark names at different prices.
            let secLabel = '';
            return items.map((p, i) => {
            if (p.section) { secLabel = p.section; return <div className="price-section" key={i}>{p.section}</div>; }
            const product = secLabel;
            return (
              <a
                className={`price-row ${p.popular ? 'price-row--popular' : ''}`}
                href="#order"
                key={i}
                onClick={() => {
                  if (p.grade) {
                    window.dispatchEvent(new CustomEvent('su47:selectGrade', { detail: { grade: p.grade, product } }));
                  }
                }}
              >
                <div className="price-row__grade">
                  <b>{p.grade}</b>
                  {p.popular && <span className="price-row__pop">★ популярный</span>}
                </div>
                <div className="price-row__detail">
                  <span className="price-row__use">{p.use}</span>
                  <span className="price-row__spec">{p.spec}</span>
                </div>
                <div className="price-row__price">
                  <b>{p.price.toLocaleString('ru')}</b>
                  <small>₽/м³</small>
                </div>
                <span className="price-row__go" aria-hidden="true">→</span>
              </a>
            );
            });
          })()}
        </div>

        <p className="prices__note">
          * Указанные цены не&nbsp;являются публичной офертой. Точная стоимость рассчитывается с&nbsp;учётом объёма, места доставки и&nbsp;дополнительных услуг.
        </p>
      </div>
    </section>
  );
}

function Order() {
  return (
    <section className="section order" id="order">
      <div className="container">
        <div className="reveal"><QuoteTicket /></div>
      </div>
    </section>
  );
}

// Customer reviews — embeds the live Yandex.Maps reviews widget for СУ 47 (org
// 1085434400) and links out to the full listings. 2ГИС shows once its firm URL
// is set. Real third-party reviews are the strongest trust signal for this buy.
const YANDEX_REVIEWS_ORG = '1085434400';   // СУ 47 на Яндекс.Картах
const TWOGIS_URL = '';                       // 2ГИС: вставить ссылку на карточку фирмы
function Reviews() {
  const yandexSrc = YANDEX_REVIEWS_ORG ? `https://yandex.ru/maps-reviews-widget/${YANDEX_REVIEWS_ORG}?comments` : '';
  if (!yandexSrc && !TWOGIS_URL) return null;
  const orgReviews = `https://yandex.ru/maps/org/${YANDEX_REVIEWS_ORG}/reviews/`;
  return (
    <section className="section reviews" id="reviews">
      <div className="container">
        <div className="reviews__layout">
          <div className="reviews__intro reveal">
            <h2 className="reviews__title">Что говорят покупатели</h2>
            <div className="reviews__links">
              {YANDEX_REVIEWS_ORG && <a className="btn btn--ghost reviews__btn" href={orgReviews} target="_blank" rel="noopener noreferrer">Все отзывы на Яндекс.Картах <span className="arrow">→</span></a>}
              {TWOGIS_URL && <a className="btn btn--ghost reviews__btn" href={TWOGIS_URL} target="_blank" rel="noopener noreferrer">Отзывы на 2ГИС <span className="arrow">→</span></a>}
            </div>
          </div>
          {yandexSrc && (
            <div className="reviews__widget reveal">
              <iframe className="reviews__frame" src={yandexSrc} title="Отзывы о СУ 47 на Яндекс.Картах" loading="lazy"></iframe>
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

function FAQ() {
  const [open, setOpen] = useStateS(0);
  return (
    <section className="section faq" id="faq">
      <div className="container">
        <div className="head reveal">
          <h2>Частые вопросы</h2>
        </div>

        <div className="faq__list reveal">
          {SU47_DATA.faqs.map((item, i) => {
            const isOpen = open === i;
            return (
              <div key={i} className={`faq__item ${isOpen ? 'faq__item--open' : ''}`}>
                <button id={`faq-q-${i}`} className="faq__q" aria-expanded={isOpen} aria-controls={`faq-a-${i}`} onClick={() => setOpen(isOpen ? -1 : i)}>
                  <span className="faq__q-num">{String(i + 1).padStart(2, '0')}</span>
                  <span className="faq__q-text">{item.q}</span>
                  <span className="faq__icon" aria-hidden="true"></span>
                </button>
                <div id={`faq-a-${i}`} className="faq__a" role="region" aria-labelledby={`faq-q-${i}`}>
                  <div className="faq__a-inner"><p>{item.a}</p></div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

function Contacts() {
  const [form, setForm] = useStateS({ name: '', phone: '', comment: '', website: '' }); // website = honeypot
  const [touched, setTouched] = useStateS({});
  const [submitting, setSubmitting] = useStateS(false);
  const [error, setError] = useStateS(null);
  const [sent, setSent] = useStateS(false);
  const openedAt = useRefS(Date.now()); // form-open time → server bot check (fill speed)

  const errors = {
    name: isValidName(form.name) ? null : 'Укажите имя (минимум 2 символа)',
    phone: isValidPhone(form.phone) ? null : 'Введите номер полностью: +7 (___) ___-__-__',
  };
  const valid = !errors.name && !errors.phone;
  const upd = (k, v) => setForm(s => ({ ...s, [k]: v }));
  const touch = (k) => setTouched(t => ({ ...t, [k]: true }));

  const onSubmit = async (e) => {
    e.preventDefault();
    setTouched({ name: true, phone: true });
    if (!valid) return;
    setSubmitting(true);
    setError(null);
    const res = await submitQuote({
      kind: 'question',
      name: form.name,
      phone: '+' + phoneDigits(form.phone),
      comment: form.comment,
      website: form.website,                       // honeypot (stays empty for humans)
      createdAt: new Date().toISOString(),
      _elapsed: Date.now() - openedAt.current,     // ms the form was open (bot check)
    });
    setSubmitting(false);
    if (res.ok) setSent(true);
    else setError('Не удалось отправить. Попробуйте ещё раз или позвоните нам напрямую.');
  };

  return (
    <section className="section contacts" id="contacts">
      <div className="container">
        <div className="contacts__grid">
          <div className="contacts__panel reveal">
            <span className="head__eyebrow">Контакты</span>
            <h2 className="contacts__title">Позвоните или&nbsp;напишите</h2>

            <a className="contacts__phone" href={`tel:${PHONE_TEL}`}>
              <span className="contacts__phone-label">Мобильный&nbsp;· звонок бесплатный</span>
              <span className="contacts__phone-num">{PHONE_HUMAN}</span>
            </a>

            <div className="contacts__lines">
              <div className="contacts__line">
                <span className="label">Городской</span>
                <a href={`tel:${PHONE2_TEL}`}>{PHONE2_HUMAN}</a>
              </div>
              <div className="contacts__line">
                <span className="label">E-mail</span>
                <a href="mailto:cy47tlt@yandex.ru">cy47tlt@yandex.ru</a>
              </div>
              <div className="contacts__line">
                <span className="label">Режим работы</span>
                <span className="v">пн–сб&nbsp;· 7:00–19:00</span>
              </div>
            </div>

            {/* photo: Pexels — concrete delivery & pour on site · pexels.com/photo/37121406 */}
            <figure className="contacts__media">
              <img
                className="contacts__img"
                src="/assets/photos/contacts.jpg"
                alt="Миксер СУ 47 на площадке — доставка бетона"
                width="1280"
                height="853"
                loading="lazy"
              />
            </figure>
          </div>

          <form className="contacts__form reveal" onSubmit={onSubmit} noValidate>
            <h3 className="contacts__form-title">Задать вопрос</h3>
            <p className="contacts__form-sub">Оставьте телефон и&nbsp;вопрос — ответим в&nbsp;течение 15&nbsp;минут. Звонок ни&nbsp;к&nbsp;чему не&nbsp;обязывает.</p>
            {sent ? (
              <div className="contacts__success">
                <span className="contacts__success-badge"><span aria-hidden="true">✓</span> Спасибо!</span>
                <p>Получили ваш вопрос. Перезвоним на&nbsp;{form.phone || 'указанный номер'} в&nbsp;течение 15&nbsp;минут.</p>
              </div>
            ) : (
              <>
                <div className="field">
                  <label className="label" htmlFor="c-name">Имя <span className="field__req">*</span></label>
                  <input
                    id="c-name"
                    className={`input ${touched.name && errors.name ? 'input--error' : ''}`}
                    value={form.name}
                    maxLength={80}
                    onChange={e => upd('name', e.target.value)}
                    onBlur={() => touch('name')}
                    aria-invalid={!!(touched.name && errors.name)}
                    placeholder="Как к вам обращаться"
                  />
                  {touched.name && errors.name && <span className="field__error">{errors.name}</span>}
                </div>
                <div className="field">
                  <label className="label" htmlFor="c-phone">Телефон <span className="field__req">*</span></label>
                  <input
                    id="c-phone"
                    className={`input ${touched.phone && errors.phone ? 'input--error' : ''}`}
                    type="tel"
                    inputMode="tel"
                    value={form.phone}
                    onChange={e => upd('phone', formatRuPhone(e.target.value))}
                    onBlur={() => touch('phone')}
                    aria-invalid={!!(touched.phone && errors.phone)}
                    placeholder="+7 (___) ___-__-__"
                  />
                  {touched.phone && errors.phone && <span className="field__error">{errors.phone}</span>}
                </div>
                <div className="field">
                  <label className="label" htmlFor="c-q">Вопрос</label>
                  <textarea id="c-q" className="textarea" rows="3" maxLength={1000} value={form.comment} onChange={e => upd('comment', e.target.value)} placeholder="Что заливаете, какой объём"></textarea>
                </div>
                {/* Honeypot — invisible to people, tempting to bots. Filled ⇒ the
                    server silently drops the message. Off-screen + aria-hidden +
                    tabIndex -1 keeps it away from keyboard and screen-reader users. */}
                <input
                  type="text"
                  name="website"
                  tabIndex={-1}
                  autoComplete="off"
                  aria-hidden="true"
                  value={form.website}
                  onChange={e => upd('website', e.target.value)}
                  style={{ position: 'absolute', left: '-9999px', width: 1, height: 1, opacity: 0 }}
                />
                {error && <div className="contacts__error" role="alert">{error}</div>}
                <button type="submit" className="btn btn--primary" disabled={submitting}>
                  {submitting ? 'Отправляем…' : <>Отправить <span className="arrow">→</span></>}
                </button>
                <p className="contacts__consent">Нажимая «Отправить», вы&nbsp;соглашаетесь с&nbsp;<a href="/privacy.html#policy">политикой обработки персональных данных</a>.</p>
              </>
            )}
          </form>
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer className="footer">
      <div className="container">
        <div className="footer__grid">
          <div className="footer__about">
            <span className="footer__logo">СУ 47 <span className="dot">·</span> бетон</span>
            <p>Производство, продажа и&nbsp;доставка товарного бетона и&nbsp;цементного раствора строительным организациям и&nbsp;частным лицам в&nbsp;Тольятти и&nbsp;Самарской области с&nbsp;2009&nbsp;года.</p>
          </div>
          <div className="footer__col">
            <h4>Меню</h4>
            <a href="#prices">Цены</a>
            <a href="#order">Расчёт</a>
            <a href="#reviews">Отзывы</a>
            <a href="#faq">FAQ</a>
            <a href="#contacts">Контакты</a>
          </div>
          <div className="footer__col">
            <h4>Контакты</h4>
            <a href={`tel:${PHONE_TEL}`}>{PHONE_HUMAN}</a>
            <a href={`tel:${PHONE2_TEL}`}>{PHONE2_HUMAN}</a>
            <a href="mailto:cy47tlt@yandex.ru">cy47tlt@yandex.ru</a>
            <span className="footer__hours">пн–сб&nbsp;· 7:00–19:00</span>
            <span className="footer__hours">Самарская область, Тольятти</span>
          </div>
          <div className="footer__col">
            <h4>Документы</h4>
            <a href="/privacy.html#policy">Политика конфиденциальности</a>
            <a href="/privacy.html#consent">Согласие на&nbsp;обработку ПД</a>
          </div>
        </div>
        <div className="footer__bottom">
          <span>©&nbsp;2026&nbsp;· ООО «СУ&nbsp;47»</span>
          <span>Фото: Pexels</span>
          <a href="/privacy.html#policy">Политика конфиденциальности</a>
        </div>
      </div>
    </footer>
  );
}

function StickyCall() {
  // The floating tap-to-call bar floats over the upper sections, then performs a
  // clean HAND-OFF: it ducks away (slides down + fades) exactly as the contacts
  // phone slab — which carries the same number — comes on screen, so it reads as
  // "handing over" to the real block rather than vanishing out of the blue. It's
  // also hidden over the order form (its own CTA) and anywhere past contacts, and
  // it's non-interactive while hidden so it never sits on top of footer links.
  const ref = useRefS(null);
  const [mode, setMode] = useStateS('show'); // 'show' | 'hide'
  useEffectS(() => {
    if (!('IntersectionObserver' in window)) return;
    const order = document.getElementById('order');
    const phone = document.querySelector('.contacts__phone');
    const footer = document.querySelector('.footer');
    let overOrder = false, atFooter = false, phoneState = 'below'; // below | in | above
    const apply = () => {
      const hide = overOrder || phoneState === 'in' || phoneState === 'above' || atFooter;
      setMode(hide ? 'hide' : 'show');
    };
    const ios = [];
    const obs = (target, cb, opts) => { if (!target) return; const io = new IntersectionObserver(([e]) => { cb(e); apply(); }, opts); io.observe(target); ios.push(io); };
    obs(order, (e) => { overOrder = e.isIntersecting; }, { rootMargin: '-25% 0px -35% 0px' });
    // Trigger the hand-off a little before the slab is centred, so the bar is
    // already gone by the time the eye lands on the real slab.
    obs(phone, (e) => { phoneState = e.isIntersecting ? 'in' : (e.boundingClientRect.top < 0 ? 'above' : 'below'); }, { rootMargin: '0px 0px -25% 0px' });
    obs(footer, (e) => { atFooter = e.isIntersecting; }, { rootMargin: '0px 0px 0px 0px' });
    return () => ios.forEach(io => io.disconnect());
  }, []);
  return (
    <a ref={ref} href={`tel:${PHONE_TEL}`}
      className={`sticky-call ${mode === 'hide' ? 'sticky-call--hidden' : ''}`}
      aria-hidden={mode !== 'show'} tabIndex={mode === 'show' ? 0 : -1}>
      <span className="sticky-call__v">
        <small>Позвонить</small>
        <b>{PHONE_HUMAN}</b>
      </span>
      <span className="sticky-call__arrow" aria-hidden="true">→</span>
    </a>
  );
}

// Edit-mode bridge for the design harness. No UI; preserves the postMessage
// wiring the harness depends on (was the old Tweaks component).
function EditBridge() {
  useEffectS(() => {
    const handler = () => {}; // harness sends activate/deactivate; nothing to toggle now.
    window.addEventListener('message', handler);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', handler);
  }, []);
  return null;
}

Object.assign(window, { Nav, Hero, About, Prices, Order, Reviews, FAQ, Contacts, Footer, StickyCall, EditBridge });
