/* LightLife — wspólne komponenty serwisu: nagłówek, stopka, koszyk, slider, FAQ, newsletter */
const {
  Button, Badge, Card, Icon, IconButton, Input, Select, Checkbox, Radio, Avatar,
  Toast, ProgressBar, FeatureItem
} = window.LightLifeDesignSystem_eb2a9e;
const LL = window.LL_DATA;

/* ── Koszyk (localStorage) ── */
const LL_CART_KEY = 'll_cart_v1';
const LL_CART_TTL = 30 * 24 * 60 * 60 * 1000;   // koszyk „żyje” miesiąc od ostatniej zmiany; potem znika
function llGetCart() {
  try {
    const c = JSON.parse(localStorage.getItem(LL_CART_KEY));
    if (!c) return null;
    if (c._savedAt && (Date.now() - c._savedAt) > LL_CART_TTL) { localStorage.removeItem(LL_CART_KEY); return null; }
    return c;
  } catch (e) { return null; }
}
function llSetCart(cart) {
  localStorage.setItem(LL_CART_KEY, JSON.stringify({ ...cart, _savedAt: Date.now() }));   // stempel czasu → wygaśnięcie po miesiącu
  window.dispatchEvent(new Event('ll-cart'));
}
function llClearCart() {
  localStorage.removeItem(LL_CART_KEY);
  window.dispatchEvent(new Event('ll-cart'));
}
/* Kody rabatowe: domyślny fallback; nadpisywane kodami z panelu admina (data/promo-codes.json).
   Format magazynu: [{ code, pct, active }]. Tu trzymamy mapę KOD→ułamek (pct/100), bo llCalc mnoży cenę. */
const PROMO_CODES = { LIGHT10: 0.10 };
function llApplyPromoData(list) {
  const arr = Array.isArray(list) ? list : [];
  if (!arr.length) return false;                       // pusty/niedostępny magazyn → zostaw domyślne
  Object.keys(PROMO_CODES).forEach((k) => { delete PROMO_CODES[k]; });
  arr.forEach((c) => {
    const code = String((c && c.code) || '').trim().toUpperCase();
    const pct = Number(c && c.pct);
    if (code && c.active !== false && pct > 0) PROMO_CODES[code] = pct / 100;
  });
  return true;
}
function llLoadPromoCodes() {
  return fetch('/data/promo-codes.json', { cache: 'no-store' })
    .then((r) => (r.ok ? r.json() : null))
    .then((list) => { llApplyPromoData(list); return PROMO_CODES; })
    .catch(() => PROMO_CODES);
}
function llCalc(cart) {
  const plan = LL.PLANS.find((p) => p.id === cart.plan) || LL.PLANS[0];
  const months = cart.months || 1;
  const monthly = plan.monthly[months];
  const total = monthly * months;
  const base = plan.monthly[1] * months;
  const saving = base - total;
  const promoPct = cart.promo && PROMO_CODES[cart.promo] ? PROMO_CODES[cart.promo] : 0;
  const discount = Math.round(total * promoPct);
  return { plan, months, monthly, total, base, saving, promoPct, discount, final: total - discount };
}
function fmtPLN(n) { return n.toLocaleString('pl-PL') + ' zł'; }
function periodLabel(m) { const p = LL.PERIODS.find((x) => x.m === m); return p ? p.label : m + ' mies.'; }
/* Dzień startu = dziś + 3 dni (plan gotowy w 72 h) → np. „w środę, 25.07". Ten sam mechanizm co belka pod pakietami. */
function deliveryStartDate() { const d = new Date(); d.setDate(d.getDate() + 3); return d; }
function deliveryDayWord() {
  const DNI = ['w niedzielę', 'w poniedziałek', 'we wtorek', 'w środę', 'w czwartek', 'w piątek', 'w sobotę'];
  return DNI[deliveryStartDate().getDay()];
}
function deliveryDateShort() {
  const d = deliveryStartDate();
  return String(d.getDate()).padStart(2, '0') + '.' + String(d.getMonth() + 1).padStart(2, '0');
}
function deliveryDayLabel() { return deliveryDayWord() + ', ' + deliveryDateShort(); }

/* ── Nagłówek serwisu ── */
function SiteHeader({ active }) {
  const [hasCart, setHasCart] = React.useState(!!llGetCart());
  const [menuOpen, setMenuOpen] = React.useState(false);
  React.useEffect(() => {
    const f = () => setHasCart(!!llGetCart());
    window.addEventListener('ll-cart', f);
    window.addEventListener('storage', f);
    return () => { window.removeEventListener('ll-cart', f); window.removeEventListener('storage', f); };
  }, []);
  /* Realna wysokość sticky-nagłówka (z belką promo) → --header-h. Jedno źródło dla całego serwisu:
     sticky/kotwice (oferta .plan-stick/.cmp, artykuł .bp-readbar, .article-body h2) klejące się „pod menu". */
  React.useEffect(() => {
    const el = document.querySelector('.site-header');
    if (!el) return;
    const promo = el.querySelector('.header-promo');
    const bar = el.querySelector('.bar');
    // Promo odpływa przy scrollu (nagłówek sticky z top:-promo), więc przyklejony jest sam pasek → --header-h = wysokość paska.
    const set = () => {
      if (promo) document.documentElement.style.setProperty('--promo-h', promo.offsetHeight + 'px');
      document.documentElement.style.setProperty('--header-h', (bar || el).offsetHeight + 'px');
    };
    set();
    window.addEventListener('resize', set);
    let ro;
    if (window.ResizeObserver) { ro = new ResizeObserver(set); ro.observe(el); }
    return () => { window.removeEventListener('resize', set); if (ro) ro.disconnect(); };
  }, []);
  /* Menu mobilne otwarte → blokada przewijania strony w tle. U nas scrolluje <html>, więc blokujemy html i body. */
  React.useEffect(() => {
    const html = document.documentElement;
    const v = menuOpen ? 'hidden' : '';
    html.style.overflow = v;
    document.body.style.overflow = v;
    return () => { html.style.overflow = ''; document.body.style.overflow = ''; };
  }, [menuOpen]);
  const links = [
    ['oferta.html', 'oferta', 'Oferta'],
    ['metamorfozy.html', 'metamorfozy', 'Metamorfozy'],
    ['oferta.html#kalkulator', 'kalkulator', 'Kalkulator'],
    ['quiz.html', 'quiz', 'Test dietetyczny'],
    ['blog.html', 'blog', 'Blog'],
    ['kontakt.html', 'kontakt', 'Kontakt'],
  ];
  const isHome = active === 'home' || /(?:^\/$)|index\.html$/.test(window.location.pathname);   // „Strona główna” aktywna na home
  /* Klik w pozycję menu: inna strona → NIE zamykaj overlaya (kryje przeładowanie, bez migania „stara strona → reload”);
     ta sama strona bez kotwicy → tylko zamknij menu, bez zbędnego reloadu; z kotwicą → zamknij i pozwól zescrollować. */
  const onNav = (href) => (e) => {
    let u;
    try { u = new URL(href, window.location.href); } catch (err) { return; }
    if (u.pathname === window.location.pathname) {
      setMenuOpen(false);
      if (!u.hash || u.hash.length <= 1) e.preventDefault();
    }
  };
  return (
    <header className="site-header">
      <a className="header-promo" href="oferta.html" aria-label="Aktualna oferta — sprawdź ofertę">
        <div className="header-promo__inner">
          <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9"></path>
            <path d="M13.7 21a2 2 0 0 1-3.4 0"></path>
          </svg>
          <span className="header-promo__text"><span className="header-promo__lead">Dość teorii? </span>Zamów dziś, a gotowy plan dostajesz już <b><span className="header-promo__day">{deliveryDayWord()}, </span>{deliveryDateShort()}</b>.</span>
          <span className="header-promo__cta">Sprawdź ofertę</span>
          <svg className="header-promo__arrow" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
        </div>
      </a>
      <div className="bar">
        <a className="site-logo" href="index.html" aria-label="LightLife — strona główna">
          <img src="assets/lightlife-logo-menu.svg?v=1" alt="LightLife" />
        </a>
        <nav id="site-main-nav" className={'site-nav' + (menuOpen ? ' is-open' : '')} aria-label="Nawigacja główna">
          <div className="site-nav-top">
            <a className="site-nav-logo" href="index.html" onClick={onNav('index.html')} aria-label="LightLife — strona główna">
              <img src="assets/lightlife-logo-menu.svg?v=1" alt="LightLife" />
            </a>
            <div className="site-nav-top-r">
              <Button href="oferta.html">Sprawdź ofertę</Button>
              <button className="site-nav-close" type="button" aria-label="Zamknij menu" onClick={() => setMenuOpen(false)}>
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
              </button>
            </div>
          </div>
          <a href="index.html" className={'nav-home' + (isHome ? ' on' : '')} onClick={onNav('index.html')}>Strona główna</a>
          {links.map(([href, id, label]) => (
            <a key={id} href={href} className={active === id ? 'on' : ''} onClick={onNav(href)}>{label}</a>
          ))}
        </nav>
        <div className="header-actions">
          {hasCart ? (
            <a className="cart-btn" href="koszyk.html" aria-label="Koszyk">
              <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"></path>
                <path d="M3 6h18"></path>
                <path d="M16 10a4 4 0 0 1-8 0"></path>
              </svg>
              <span className="cart-count">1</span>
            </a>
          ) : null}
          <span className="header-cta">
            <Button href="oferta.html">Sprawdź ofertę</Button>
          </span>
          <button
            className={'header-menu-toggle' + (menuOpen ? ' is-open' : '')}
            type="button"
            aria-label={menuOpen ? 'Zamknij menu' : 'Otwórz menu'}
            aria-controls="site-main-nav"
            aria-expanded={menuOpen}
            onClick={() => setMenuOpen((open) => !open)}
          >
            <span></span><span></span><span></span>
          </button>
        </div>
      </div>
    </header>
  );
}

/* ── Nagłówek sekcji ── */
function SectionHead({ overline, title, sub, center }) {
  return (
    <div className={'sec-head' + (center ? ' center' : '')}>
      {overline ? <div className="overline-row"><span>{overline}</span></div> : null}
      {title ? <h2>{title}</h2> : null}
      {sub ? <p className="sub">{sub}</p> : null}
    </div>
  );
}

/* ── Nagłówek podstrony ── */
function PageHead({ overline, title, sub, children }) {
  return (
    <div className="page-head container">
      {overline ? <div className="overline-row"><span>{overline}</span></div> : null}
      <h1>{title}</h1>
      {sub ? <p className="sub">{sub}</p> : null}
      {children}
    </div>
  );
}

/* ── Nagłówek wyśrodkowany ze złotą linią (grupa: sekcje editorial) ── */
function CenterHead({ overline, title, sub, line = true }) {
  return (
    <div className="center-head">
      {overline ? <span className="center-overline">{overline}</span> : null}
      <h2>{title}</h2>
      {line ? <span className="gold-line" aria-hidden="true"></span> : null}
      {sub ? <p>{sub}</p> : null}
    </div>
  );
}

/* ── Slider z bocznymi strzałkami (wspólny silnik: metamorfozy, opinie) ── */
function ArrowSlider({ children, progress = false, autoplay = false, interval = 4500 }) {
  const ref = React.useRef(null);
  const pauseRef = React.useRef(0);
  const [bar, setBar] = React.useState({ w: 33, x: 0 });
  const [can, setCan] = React.useState({ back: false, fwd: true });
  const update = () => {
    const el = ref.current;
    if (!el) return;
    const max = el.scrollWidth - el.clientWidth;
    setBar({ w: el.clientWidth / el.scrollWidth * 100, x: max > 0 ? el.scrollLeft / el.scrollWidth * 100 : 0 });
    setCan({ back: el.scrollLeft > 16, fwd: el.scrollLeft < max - 16 });
  };
  React.useEffect(() => {
    update();
    const el = ref.current;
    el.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    return () => { el.removeEventListener('scroll', update); window.removeEventListener('resize', update); };
  }, []);
  // auto-przewijanie (opcjonalne): sam jedzie, pauzuje na hover i 7s po ręcznym sterowaniu, zapętla na końcu
  React.useEffect(() => {
    if (!autoplay) return;
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    let hovering = false;
    const bump = () => { pauseRef.current = Date.now() + 7000; };
    const enter = () => { hovering = true; };
    const leave = () => { hovering = false; };
    el.addEventListener('pointerdown', bump);
    el.addEventListener('touchstart', bump, { passive: true });
    el.addEventListener('mouseenter', enter);
    el.addEventListener('mouseleave', leave);
    const id = setInterval(() => {
      if (hovering || Date.now() < pauseRef.current) return;
      const max = el.scrollWidth - el.clientWidth;
      if (max <= 0) return;
      if (el.scrollLeft >= max - 8) el.scrollTo({ left: 0, behavior: 'smooth' });
      else el.scrollBy({ left: el.clientWidth * 0.9, behavior: 'smooth' });
    }, interval);
    return () => { clearInterval(id); el.removeEventListener('pointerdown', bump); el.removeEventListener('touchstart', bump); el.removeEventListener('mouseenter', enter); el.removeEventListener('mouseleave', leave); };
  }, [autoplay, interval]);
  const by = (d) => {
    const el = ref.current;
    if (el) el.scrollBy({ left: d * el.clientWidth * 0.8, behavior: 'smooth' });
    pauseRef.current = Date.now() + 7000;   // po ręcznym kliknięciu strzałki też wstrzymaj auto
  };
  return (
    <div className="arrow-slider">
      {progress ? (
        <div className="ms-progress" aria-hidden="true">
          <span style={{ width: bar.w + '%', left: bar.x + '%' }}></span>
        </div>
      ) : null}
      <div className="slider-track ms-track" ref={ref}>{children}</div>
      {can.back ? (
        <button type="button" className="ms-arrow ms-left" onClick={() => by(-1)} aria-label="Poprzednie">
          <Icon name="arrow-left" size={22} />
        </button>
      ) : null}
      {can.fwd ? (
        <button type="button" className="ms-arrow ms-right" onClick={() => by(1)} aria-label="Następne">
          <Icon name="arrow-right" size={22} />
        </button>
      ) : null}
    </div>
  );
}

/* ── Slider poziomy ── */
function Slider({ head, children }) {
  const ref = React.useRef(null);
  const by = (dir) => {
    const el = ref.current;
    if (el) el.scrollBy({ left: dir * el.clientWidth * 0.85, behavior: 'smooth' });
  };
  return (
    <div>
      <div className="slider-head">
        <div>{head}</div>
        <div className="slider-arrows">
          <IconButton icon="arrow-left" label="Poprzednie" onClick={() => by(-1)} />
          <IconButton icon="arrow-right" label="Następne" onClick={() => by(1)} />
        </div>
      </div>
      <div className="slider-track" ref={ref}>{children}</div>
    </div>
  );
}

/* ── Karta metamorfozy (styl editorial: zdjęcia przed/po, imię, plakietka kg, opis) ── */
/* Ponawianie nieudanego pobrania zdjęcia metamorfozy — przy zerwanym połączeniu ponawia do 3× z narastającą zwłoką. */
function metaImgRetry(e) {
  const el = e.currentTarget;
  const tries = (parseInt(el.dataset.retry, 10) || 0) + 1;
  if (tries > 3) return;
  el.dataset.retry = String(tries);
  const base = (el.getAttribute('src') || '').split('?')[0];
  if (base) window.setTimeout(() => { el.src = base + '?r=' + tries; }, 400 * tries);
}
/* spadek do odznaki: pełne kg (bez ułamków) — równa długość, jak u konkurencji „−10 kg" */
function lossWholeKg(loss) {
  const mm = String(loss == null ? '' : loss).match(/(\d+(?:[.,]\d+)?)/);
  if (!mm) return String(loss == null ? '' : loss).replace(/\s*kg\s*/i, '').trim();
  return '−' + Math.round(parseFloat(mm[1].replace(',', '.')));
}
function MetaCard({ m, showQuote, open, onToggle, bare }) {
  const controlled = typeof onToggle === 'function';
  const [internalOpen, setInternalOpen] = React.useState(false);
  const expanded = controlled ? open : internalOpen;
  const toggle = controlled ? onToggle : () => setInternalOpen((v) => !v);
  const [overflows, setOverflows] = React.useState(false);
  const quoteRef = React.useRef(null);
  React.useEffect(() => {
    const el = quoteRef.current;
    if (el) setOverflows(el.scrollHeight - el.clientHeight > 2);   // czy opis wykracza poza obcięcie
  }, [m.quote]);
  return (
    <article className={'meta-card' + (bare ? ' meta-card--bare' : '')} data-comment-anchor={'meta-' + m.id}>
      <div className="meta-imgs">
        <figure>
          {m.przed
            ? <img className="meta-photo" src={m.przed} alt={'Przed — ' + m.name} decoding="async" onError={metaImgRetry} />
            : <image-slot id={'meta-' + m.id + '-przed'} shape="rounded" radius="12" placeholder="Wgraj zdjęcie „przed”" style={{ width: '100%', height: 'auto', aspectRatio: '3 / 4' }}></image-slot>}
          <figcaption>Przed</figcaption>
        </figure>
        <figure>
          {m.po
            ? <img className="meta-photo" src={m.po} alt={'Po — ' + m.name} decoding="async" onError={metaImgRetry} />
            : <image-slot id={'meta-' + m.id + '-po'} shape="rounded" radius="12" placeholder="Wgraj zdjęcie „po”" style={{ width: '100%', height: 'auto', aspectRatio: '3 / 4' }}></image-slot>}
          <figcaption>Po</figcaption>
        </figure>
        {bare && m.loss ? <span className="meta-loss-badge"><span className="mlb-in"><b>{lossWholeKg(m.loss)}</b><small>kg</small></span></span> : null}
      </div>
      {bare ? null : (
      <div className="meta-info">
        <div className="meta-row">
          <h3 className="meta-name">{m.name}</h3>
          <p className="meta-loss">{m.loss}</p>
        </div>
        {m.months ? <p className="meta-sub">{m.months} mies. metamorfozy</p> : null}
        {showQuote && m.quote ? (
          <div className="meta-desc">
            <p ref={quoteRef} className={'meta-quote' + (expanded ? ' is-open' : '')}>{m.quote}</p>
            {(overflows || expanded) ? (
              <button type="button" className="meta-desc-toggle" aria-expanded={expanded} onClick={toggle}>
                {expanded ? 'Mniej' : 'Więcej'}
              </button>
            ) : null}
          </div>
        ) : null}
      </div>
      )}
    </article>
  );
}

/* ── Logo Google „G” (oficjalne kolory) ── */
function GMark({ size = 18 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 48 48" aria-label="Google" role="img">
      <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
      <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" />
      <path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" />
      <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
    </svg>
  );
}

/* ── Gwiazdki ── */
function Stars({ n = 5, size = 15 }) {
  return (
    <span style={{ display: 'inline-flex', gap: 3, color: 'var(--gold)' }} aria-label={n + ' na 5 gwiazdek'}>
      {Array.from({ length: n }).map((_, i) => <Icon key={i} name="star" size={size} filled />)}
    </span>
  );
}

/* ── FAQ (akordeon) ── */
function FaqList({ items }) {
  const [open, setOpen] = React.useState(0);
  return (
    <div className="faq">
      {items.map((it, i) => (
        <div key={i} className={'faq-item' + (open === i ? ' open' : '')}>
          <button type="button" className="faq-q" aria-expanded={open === i} onClick={() => setOpen(open === i ? -1 : i)}>
            {it.q}
            <span className="chev"><Icon name="chevron-down" size={18} /></span>
          </button>
          {open === i ? <div className="faq-a">{it.a}</div> : null}
        </div>
      ))}
    </div>
  );
}

/* ── Newsletter ── */
function NewsletterBox() {
  const [email, setEmail] = React.useState('');
  const [state, setState] = React.useState('idle');
  const submit = (e) => {
    e.preventDefault();
    if (!email.trim() || email.indexOf('@') < 1) { setState('error'); return; }
    setState('done');
  };
  return (
    <div className="newsletter" data-comment-anchor="newsletter">
      <div>
        <div className="overline-row"><span>Newsletter</span></div>
        <h3 style={{ marginTop: 12 }}>Więcej takich treści raz w tygodniu</h3>
        <p className="nl-sub">Krótki przegląd wiedzy, przepisów i nawyków. Zero spamu — możesz wypisać się jednym kliknięciem.</p>
      </div>
      {state === 'done' ? (
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, font: '600 15px/1.5 var(--font-body)', color: 'var(--text-heading)' }}>
          <span style={{ color: 'var(--gold-600)' }}><Icon name="check-circle" size={22} /></span>
          Dziękujemy! Sprawdź skrzynkę, aby potwierdzić zapis.
        </div>
      ) : (
        <form className="nl-form" onSubmit={submit}>
          <Input type="email" placeholder="Twój adres e-mail" value={email}
            error={state === 'error' ? 'Podaj poprawny adres e-mail' : undefined}
            onChange={(e) => { setEmail(e.target.value); if (state === 'error') setState('idle'); }} />
          <Button type="submit" arrow style={{ flex: 'none' }}>Zapisz się</Button>
        </form>
      )}
    </div>
  );
}

/* ── Pasek CTA ── */
function CtaBand({ title, sub, cta = 'Zobacz ofertę', href = 'oferta.html' }) {
  return (
    <section className="cta-band" data-screen-label="Pasek CTA">
      <div className="inner">
        <div>
          <h2>{title}</h2>
          {sub ? <p>{sub}</p> : null}
        </div>
        <Button variant="secondary" size="lg" arrow href={href}>{cta}</Button>
      </div>
    </section>
  );
}

/* ── Stopka ── */
function SiteFooter() {
  const C = LL.CONTACT;
  const FooterIcon = ({ name }) => {
    const icons = {
      mail: (
        <React.Fragment>
          <rect x="3.5" y="5.5" width="17" height="13" rx="2.4" />
          <path d="m4 7.5 8 5.7 8-5.7" />
        </React.Fragment>
      ),
      clock: (
        <React.Fragment>
          <circle cx="12" cy="12" r="8.5" />
          <path d="M12 7.5V12l3 1.8" />
        </React.Fragment>
      )
    };
    return (
      <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.65" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
        {icons[name]}
      </svg>
    );
  };
  const menuLinks = [
    ['oferta.html', 'Oferta'],
    ['oferta.html?plec=kobieta', 'Dieta dla kobiet'],
    ['oferta.html?plec=mezczyzna', 'Dieta dla mężczyzn'],
    ['oferta.html?plec=para', 'Dieta dla par'],
    ['oferta.html?plec=dzieci', 'Dieta dla dzieci'],
    ['metamorfozy.html', 'Metamorfozy'],
    ['kontakt.html', 'Kontakt']
  ];
  const aboutLinks = [
    ['oferta.html#zespol', 'Zespół'],
    ['oferta.html#faq', 'FAQ']
  ];
  const knowledgeLinks = [
    ['oferta.html#kalkulator', 'Kalkulator kalorii'],
    ['oferta.html#kalkulator', 'Kalkulator CPM i PPM'],
    ['oferta.html#kalkulator', 'Kalkulator BMI'],
    ['blog.html', 'Blog']
  ];
  const paymentLinks = ['BLIK', 'Karta płatnicza', 'Przelew online', 'Apple Pay', 'Google Pay'];
  return (
    <footer className="site-footer" data-screen-label="Stopka">
      <div className="container foot-grid">
        <div className="foot-col">
          <h4>Kontakt</h4>
          <div className="foot-contact-list">
            <a className="foot-contact-item" href={'mailto:' + C.email}>
              <span className="foot-contact-dot"><FooterIcon name="mail" /></span>
              <span>{C.email}</span>
            </a>
            <div className="foot-contact-item">
              <span className="foot-contact-dot"><FooterIcon name="clock" /></span>
              <span><b>Godziny pracy</b>{C.hours}</span>
            </div>
          </div>
        </div>
        <div className="foot-col">
          <h4>Menu</h4>
          <ul>
            {menuLinks.map(([href, label]) => <li key={label}><a href={href}>{label}</a></li>)}
          </ul>
        </div>
        <div className="foot-col">
          <h4>O nas</h4>
          <ul>
            {aboutLinks.map(([href, label]) => <li key={label}><a href={href}>{label}</a></li>)}
          </ul>
        </div>
        <div className="foot-col">
          <h4>Wiedza</h4>
          <ul>
            {knowledgeLinks.map(([href, label]) => <li key={label}><a href={href}>{label}</a></li>)}
          </ul>
        </div>
        <div className="foot-col foot-payments">
          <h4>Płatności</h4>
          <ul>
            {paymentLinks.map((label) => <li key={label}><a href="oferta.html">{label}</a></li>)}
            <li><span className="foot-safe">Bezpieczne zakupy · SSL</span></li>
          </ul>
        </div>
      </div>
      <div className="container foot-bottom">
        <span className="foot-copy">© 2026 LightLife — Dietetyka online. Wszystkie prawa zastrzeżone.</span>
        <span className="foot-legal">
          <a href="https://lightlifediet.pl/regulamin.pdf" target="_blank" rel="noopener">Regulamin</a>
          <a href="https://lightlifediet.pl/polityka-prywatnosci.pdf" target="_blank" rel="noopener">Polityka prywatności</a>
        </span>
      </div>
    </footer>
  );
}

/* ── „Smak, do którego się wraca” — karta posiłku + ikony kategorii.
   JEDNO ŹRÓDŁO dla strony głównej (home.jsx → TastySection) i oferty (oferta.html → MenuSection).
   Dane z LL.MENU (data.js). Zdjęcia: wgrane przez <image-slot id="menu-{kat}-{i}"> trafiają do
   wspólnego sidecara .image-slots.state.json (kluczowanego po id) — więc TA SAMA fotka pokazuje
   się i na stronie głównej, i na ofercie; albo generowane assets/dish-gen/tasty-{kat}-{i}.png jako src.
   Edytuj kartę TU raz — zmienia się na obu stronach. */
const MENU_CAT_ICON = {
  'Śniadanie': '<path d="M12 2v8"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m8 6 4-4 4 4"/><path d="M16 18a4 4 0 0 0-8 0"/>',
  'II śniadanie': '<path d="M12 6.528V3a1 1 0 0 1 1-1h0"/><path d="M18.237 21A15 15 0 0 0 22 11a6 6 0 0 0-10-4.472A6 6 0 0 0 2 11a15.1 15.1 0 0 0 3.763 10 3 3 0 0 0 3.648.648 5.5 5.5 0 0 1 5.178 0A3 3 0 0 0 18.237 21"/>',
  'Obiad': '<path d="M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2"/><path d="M7 2v20"/><path d="M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7"/>',
  'Kolacja': '<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/>',
};
function MenuCatIcon({ cat, size = 18 }) {
  return <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none"
    stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"
    dangerouslySetInnerHTML={{ __html: MENU_CAT_ICON[cat] || MENU_CAT_ICON['Obiad'] }} />;
}
function MealCard({ m, slotId }) {
  // Generowane zdjęcia dań (Studio, wspólny blat referencyjny): Śniadanie/II śniadanie/Obiad (menu-0..2-*).
  // Kolacja (menu-3-*) jeszcze w toku — bez src, pokazuje placeholder, aż wszystkie 20 powstaną.
  const mm = /^menu-([0-3])-(\d+)$/.exec(slotId || '');
  const photo = mm ? 'assets/dish-gen/tasty-' + mm[1] + '-' + mm[2] + '.png?v=9' : undefined;
  return (
    <article className="dishx-card">
      <div className="dishx-photo">
        <image-slot id={slotId} src={photo} shape="rect" fit="cover" placeholder="Zdjęcie posiłku"
          style={{ width: '100%', height: '100%' }}></image-slot>
      </div>
      <div className="dishx-body">
        <div className="dishx-head">
          <h3 className="dishx-name">{m.n}</h3>
          <span className="dishx-kcal"><b>{m.k}</b><small>kcal</small></span>
        </div>
        <div className="dishx-macros">
          <div className="macbar" aria-hidden="true">
            <span className="macbar-seg mac-p" style={{ flex: m.b }}></span>
            <span className="macbar-seg mac-f" style={{ flex: m.t }}></span>
            <span className="macbar-seg mac-c" style={{ flex: m.w }}></span>
          </div>
          <div className="macleg">
            <span className="mac mac-p"><i>Białko</i> <b>{m.b} g</b></span>
            <span className="mac mac-f"><i>Tłuszcze</i> <b>{m.t} g</b></span>
            <span className="mac mac-c"><i>Węgle</i> <b>{m.w} g</b></span>
          </div>
        </div>
      </div>
    </article>
  );
}

/* ── Opinie Google (wyciąg jak z Google; treści przykładowe — do podmiany) ── */
/* UWAGA: opinie PRZYKŁADOWE (placeholder na czas budowy) — do podmiany na prawdziwe, potwierdzone opinie klientów PRZED publikacją strony (fałszywe opinie są zakazane, dyrektywa Omnibus). */
const GOOGLE_REVIEWS = [
  { name: 'Wiola Banasik', photo: 'assets/reviews/wiola.webp', meta: 'Lokalny przewodnik · 83 opinie · 10 zdjęć', when: 'tydzień temu', isNew: true,
    text: 'Jestem w LightLife od 2 msc. Przez wakacje niestety nie pilnowałam diety, ale dzięki opcji zawieszenia konta zyskałam trwały dostęp. Dieta jest faktycznie ułożona pod Ciebie. Dietetyk jest z Tobą w kontakcie i podpowiada różne triki na trzymanie się diety. To wsparcie jest mega. Wystarczy motywacja i zapał, a będzie dobrze. Polecam.' },
  { name: 'Wiktoria Rewerska', photo: 'assets/reviews/wiktoria.webp', meta: '1 opinia · 5 zdjęć', when: 'tydzień temu', isNew: true,
    text: 'Korzystam z LightLife ponad 4 miesiące. Jestem bardzo zadowolona. Proponowane posiłki są bardzo smaczne, a ich przygotowanie zajmuje dosłownie kilka minut. Czuję się rewelacyjnie, zapomniałam o bólu nóg i ciągłym zmęczeniu, a waga i pomiary lecą jak szalone. Opiekun celu odpowiada na wszystko zrozumiale i szczegółowo. Gorąco polecam!' },
  { name: 'Sandra A.', meta: '7 opinii', when: 'tydzień temu', isNew: true,
    text: 'Aplikacja działa rewelacyjnie — mam wszystko w jednym miejscu od treningów po listę zakupów. Stały kontakt z dietetykiem, możliwość modyfikacji posiłków do wyboru, monitorowanie zrobionych kroków itp. Warto samemu spróbować. Bardzo polecam!' },
  { name: 'Marek Jankowski', photo: 'assets/reviews/marek.webp', meta: 'Lokalny przewodnik · 41 opinii', when: '2 tygodnie temu',
    text: 'Po zawale lekarz kazał zmienić styl życia. Sam nie wiedziałem, od czego zacząć. Tu dostałem konkretny plan, kontakt z dietetykiem co tydzień, edukację co i dlaczego. Minus 16 kg, lepsze wyniki badań i realne wsparcie, nie gotowiec z internetu.' },
  { name: 'Natalia Wojciechowska', photo: 'assets/reviews/natalia.webp', meta: '12 opinii · 3 zdjęcia', when: '3 tygodnie temu',
    text: 'Słodycze były moją zgubą. Myślałam, że nie da się bez nich żyć. Dietetyk wprowadził zdrowe alternatywy i nauczył, jak ogarniać zachcianki. Już nie podjadam, a i tak schudłam 9 kg. Pierwszy plan, którego nie rzuciłam po dwóch tygodniach.' },
  { name: 'Łukasz Mazur', photo: 'assets/reviews/lukasz.webp', meta: 'Lokalny przewodnik · 128 opinii', when: 'miesiąc temu',
    text: 'Krótko: działa. Jadłospis dopasowany do moich treningów, realne wsparcie i konkretne porcje. W trzy miesiące zbudowałem formę, o jaką walczyłem latami na siłowni. Polecam każdemu, kto ma dość „średnio dla wszystkich”.' },
  { name: 'Aleksandra Pruszyńska', meta: '4 opinie', when: 'miesiąc temu',
    text: 'Aplikacja robi robotę: lista zakupów, wymienniki i podgląd makro. Pierwszy raz wytrwałam dłużej niż dwa tygodnie, bo w końcu wiedziałam, co i kiedy jeść. Dietetyk odpowiadał na każdą wątpliwość w kilka godzin.' },
  { name: 'Paweł Domański', photo: 'assets/reviews/pawel.webp', meta: '9 opinii · 2 zdjęcia', when: 'miesiąc temu',
    text: 'Jedzenie normalne, nie „dietetyczne”. Rodzina je to samo co ja, tylko porcje inne. Żadnych głodówek, a mimo to −11 kg w pół roku. Najbardziej cenię, że plan uwzględnił moją pracę zmianową.' },
  { name: 'Ewa Sikora', photo: 'assets/reviews/ewa.webp', meta: 'Lokalny przewodnik · 56 opinii', when: '2 miesiące temu',
    text: 'Plan uwzględnił moją insulinooporność i niedoczynność tarczycy. Czuję się lżej, mam więcej energii i wróciły prawidłowe wyniki. Kontakt z dietetykiem szybki i rzeczowy, bez oceniania. Bardzo polecam.' },
  { name: 'Michał Stępień', meta: '18 opinii', when: '2 miesiące temu',
    text: 'Rok pracy, zero głodówek, −22 kg. Najlepsza decyzja od lat. Konsultacje wideo w pakiecie Pro były strzałem w dziesiątkę — za każdym razem wychodziłem z konkretnym planem na kolejny miesiąc.' },
  { name: 'Katarzyna Michalska', photo: 'assets/reviews/katarzyna.webp', meta: 'Lokalny przewodnik · 38 opinii · 4 zdjęcia', when: '3 tygodnie temu',
    text: 'Od lat walczyłam z insulinoopornością i wiecznie byłam głodna albo senna. W LightLife dietetyczka ułożyła plan tak, że w końcu nie mam napadów na słodkie po obiedzie. Jem naprawdę smacznie, schudłam 8 kg, a cukier wrócił do normy. Do tego szybko odpisuje, jak mam pytanie. Bardzo polecam.' },
  { name: 'Tomasz Zieliński', photo: 'assets/reviews/tomasz.webp', meta: '27 opinii · 3 zdjęcia', when: 'pół roku temu',
    text: 'Po zawale kardiolog kazał mi zrzucić wagę i ogarnąć cholesterol. Nie wierzyłem w diety z internetu, ale w LightLife dostałem plan ułożony konkretnie pode mnie, z uwzględnieniem leków, które biorę. Po pięciu miesiącach 12 kg mniej, LDL spadł ze 160 na 98. Dietetyczka odpisuje szybko i nie robi problemu, jak coś zjem poza planem. Żona je teraz to samo co ja, bo posiłki są normalne, obiady jak u mamy, tylko przemyślane.' },
  { name: 'Sylwia Grabowska', meta: '2 opinie', when: 'tydzień temu', isNew: true,
    text: 'Plan prosty, jedzenie smaczne, LightLife polecam z czystym sumieniem.' },
  { name: 'Joanna K.', meta: '2 opinie', when: 'tydzień temu', isNew: true,
    text: 'W LightLife w końcu trafiłam na dietę, przy której nie chodzę głodna. Polecam.' },
  { name: 'Robert Sadowski', meta: '5 opinii', when: 'kilka dni temu', isNew: true,
    text: 'W końcu dieta, która nie wymaga ode mnie myślenia, wszystko podane, 8 kg w dół.' },
  { name: 'Kamil S.', meta: '1 opinia', when: '2 tygodnie temu',
    text: 'Studiuję i gotuję w akademiku, więc miało być tanio i szybko. Mój plan w LightLife jest dokładnie taki, a lista zakupów w apce oszczędza mi masę czasu.' },
  { name: 'Magdalena Wróbel', meta: '5 opinii', when: 'miesiąc temu',
    text: 'Mam dwójkę małych dzieci i zero czasu na kombinowanie. Posiłki są proste i szybkie, a lista zakupów w aplikacji robi mi pół roboty. Z LightLife zrzuciłam 6 kg i pierwszy raz mi się nie posypało.' },
  { name: 'Marcin Krawczyk', meta: '3 opinie', when: 'tydzień temu', isNew: true,
    text: 'Rok liczyłem kalorie sam w darmowej aplikacji i wiecznie odpuszczałem po miesiącu. Tu ktoś to w końcu przemyślał za mnie, porcje sycące, a waga leci w dobrą stronę, na razie minus 6 kg. Najbardziej zaskoczyło mnie, że dietetyk faktycznie odpisuje, a nie jakiś bot.' },
  { name: 'Ola B.', meta: '3 opinie', when: 'kilka dni temu', isNew: true,
    text: 'LightLife to najlepsza decyzja w tym roku, serio.' },
  { name: 'Agnieszka Dąbrowska', photo: 'assets/reviews/agnieszka.webp', meta: '11 opinii · 1 zdjęcie', when: '2 miesiące temu',
    text: 'Niedoczynność tarczycy i Hashimoto rozłożyły mnie na łopatki, ciągłe zmęczenie i tycie mimo małych porcji. Plan w LightLife uwzględnił moje wyniki i leki. Po trzech miesiącach mam więcej energii, przestałam puchnąć i spadło 7 kg. Dietetyczka tłumaczy wszystko spokojnie, bez oceniania.' },
  { name: 'Andrzej Pawlak', meta: '2 opinie', when: '3 miesiące temu',
    text: 'Mam 67 lat i cukrzycę typu 2, syn wykupił mi LightLife, bo sam bym się nie zebrał. Dietetyk ułożył jadłospis z rzeczy, które znam, wszystko do kupienia w zwykłym sklepie. Cukry na czczo spadły ze 140 na okolice 105, doktor zmniejszył mi metforminę. Schudłem 9 kg bez głodzenia się. Jak ja daję radę, to każdy da.' },
  { name: 'Weronika Zawadzka', photo: 'assets/reviews/weronika.webp', meta: '6 opinii · 1 zdjęcie', when: '2 miesiące temu',
    text: 'Z LightLife schudłam 5 kg przed ślubem bez żadnych głodówek. Suknia leżała idealnie, a ja pierwszy raz nie liczyłam każdego kęsa.' },
  { name: 'Monika Lewandowska', meta: '7 opinii', when: 'miesiąc temu',
    text: 'W okolicach menopauzy waga zaczęła rosnąć, choć nic nie zmieniłam. Tu dostałam plan dopasowany do tego etapu i po dwóch miesiącach ruszyło. Minus 5 kg i wróciła mi energia na spacery.' },
  { name: 'Krzysztof B.', meta: '1 opinia', when: '2 tygodnie temu',
    text: 'Jeżdżę w trasie, a plan ułożyli pod moje godziny, minus 7 kg.' },
  { name: 'Patrycja Lis', meta: '1 opinia', when: 'kilka dni temu', isNew: true,
    text: 'Apka LightLife wymiata, zamiana posiłków i lista zakupów ratują mi tydzień.' },
  { name: 'Dorota S.', meta: '3 opinie', when: '2 tygodnie temu',
    text: 'LightLife to najlepiej wydane pieniądze od dawna, polecam w ciemno.' },
  { name: 'Piotr Kaczmarek', photo: 'assets/reviews/piotr.webp', meta: 'Lokalny przewodnik · 52 opinie', when: 'miesiąc temu',
    text: 'Trenuję siłowo od paru lat i pierwszy raz mam redukcję bez uczucia, że zaraz zjem ścianę. Białko rozpisane pod moje treningi, makro widzę w aplikacji, a jak mi się posiłek znudzi, wymieniam go jednym kliknięciem. Zszedłem z 94 na 86 kg i siła praktycznie nie spadła.' },
  { name: 'Damian Górski', meta: '14 opinii', when: 'pół roku temu',
    text: 'Piszę tę opinię rok po zakończeniu współpracy z LightLife, bo dopiero teraz widać, czy coś działa. Zrzuciłem wtedy 12 kg i nic nie wróciło, chociaż dawno nie mam już planu. Nauczyłem się po prostu normalnie jeść, bez dzielenia jedzenia na dozwolone i zakazane. Przy okazji cholesterol wrócił do normy.' },
  { name: 'Karolina Nowicka', photo: 'assets/reviews/karolina.webp', meta: 'Lokalny przewodnik · 44 opinie · 6 zdjęć', when: 'pół roku temu',
    text: 'Mam PCOS i przez lata słyszałam tylko, że mam mniej jeść i więcej się ruszać. W LightLife pierwszy raz ktoś ułożył jadłospis pod moją gospodarkę hormonalną. Cykl się uregulował, cera się poprawiła, a przy okazji schudłam 10 kg. Aplikacja banalnie prosta, wymieniam posiłki jednym kliknięciem. Jestem po roku i nadal się tego trzymam.' },
  { name: 'Grzegorz Woźniak', meta: '9 opinii', when: '2 miesiące temu',
    text: 'Pracuję na trzy zmiany i każda wcześniejsza dieta się sypała, bo nocki wszystko rozwalały. Tutaj pierwsze, o co zapytał dietetyk, to mój grafik. Dostałem dwa warianty rozpisane pod dzienną i nocną zmianę. Po czterech miesiącach 10 kg w dół i przestałem jeść byle co o trzeciej w nocy.' },
  { name: 'Ania K.', meta: '4 opinie', when: 'miesiąc temu',
    text: 'Zero głodówek, a waga w końcu leci w dół.' },
  { name: 'Iwona Kowalczyk', meta: '4 opinie', when: '3 tygodnie temu',
    text: 'Po ciąży zostało mi kilka kilo, których nie mogłam ruszyć. Plan w LightLife był ułożony pod karmienie, więc niczego się nie bałam. Powoli, ale skutecznie, wróciłam do swoich ubrań.' },
  { name: 'Rafał W.', meta: '4 opinie', when: 'kilka dni temu', isNew: true,
    text: 'LightLife: konkretny plan, szybki kontakt, zero ściemy, polecam.' },
  { name: 'Sebastian Urban', photo: 'assets/reviews/sebastian.webp', meta: '8 opinii · 2 zdjęcia', when: 'rok temu',
    text: 'Podchodziłem sceptycznie, bo próbowałem już kilku diet z internetu. Po pierwszym miesiącu w LightLife 3 kg w dół i wiedziałem, że zostaję na dłużej.' },
  { name: 'Justyna Adamczyk', photo: 'assets/reviews/justyna.webp', meta: '9 opinii · 2 zdjęcia', when: '2 miesiące temu',
    text: 'Bałam się, że dieta to gotowanie osobno dla siebie. Nic z tych rzeczy, cała rodzina je to samo, tylko ja mam rozpisane porcje. Proste przepisy, dużo warzyw i naprawdę smaczne.' },
  { name: 'Dariusz Zając', photo: 'assets/reviews/dariusz.webp', meta: '14 opinii · 1 zdjęcie', when: 'rok temu',
    text: 'Zapisałem się do LightLife ze 118 kilogramami i ciśnieniem 150/95. Plan był ułożony pod moje nadciśnienie, mniej soli, ale jedzenie dalej miało smak. Dziś ważę 100 kg, ciśnienie w normie bez zwiększania leków. Po drodze zawieszałem konto na urlop i nikt nie robił z tego problemu.' },
  { name: 'Ewelina Szymańska', meta: '5 opinii', when: 'tydzień temu',
    text: 'Mój dietetyk z LightLife trafił w moje smaki, jem pysznie i chudnę.' },
  { name: 'Renata Piotrowska', meta: '6 opinii', when: 'miesiąc temu',
    text: 'Praca biurowa i kanapki na szybko zrobiły swoje. Dietetyczka pokazała mi, jak jeść normalnie w biegu, bez pudełkowania na siłę. Trzy miesiące i jestem o dwa rozmiary mniej.' },
  { name: 'Adam Wieczorek', photo: 'assets/reviews/adam.webp', meta: 'Lokalny przewodnik · 87 opinii', when: 'pół roku temu',
    text: 'Dojeżdżam do pracy 3 godziny dziennie i jadłem głównie na stacjach benzynowych. Dietetyczka rozpisała posiłki tak, że dwa zabieram ze sobą, a przygotowanie zajmuje kwadrans wieczorem. Pół roku, minus 11 kg i w końcu nie przysypiam za kierownicą po obiedzie.' },
  { name: 'Bartek Olszewski', photo: 'assets/reviews/bartek.webp', meta: 'Lokalny przewodnik · 47 opinii · 12 zdjęć', when: 'miesiąc temu',
    text: 'Pracuję w trasie i połowę tygodnia jem poza domem, więc żadna dieta wcześniej nie miała u mnie szans. Dietetyczka z LightLife ułożyła plan pod delegacje, a jak coś nie pasuje, zamieniam posiłek w aplikacji. Po czterech miesiącach mam 7 kg mniej i nie zasypiam po obiedzie. Pakiet Comfort w zupełności mi wystarcza.' },
];

function GoogleReview({ r }) {
  return (
    <article className="grev-card">
      <div className="grev-top">
        <span className="grev-back" aria-hidden="true"><Icon name="arrow-left" size={18} /></span>
        <Avatar name={r.name} src={r.photo} size={40} />
        <div className="grev-who">
          <b>{r.name}</b>
          <span>{r.meta}</span>
        </div>
        <span className="grev-g" aria-hidden="true"><GMark size={20} /></span>
        <span className="grev-menu" aria-hidden="true"><Icon name="ellipsis" size={18} /></span>
      </div>
      <div className="grev-rate">
        <Stars size={15} />
        <span>{r.when}</span>
      </div>
      <p className="grev-text">{r.text}</p>
    </article>
  );
}

Object.assign(window, {
  llGetCart, llSetCart, llClearCart, llCalc, fmtPLN, periodLabel, PROMO_CODES, llLoadPromoCodes, llApplyPromoData,
  SiteHeader, SiteFooter, SectionHead, PageHead, Slider, MetaCard, Stars, FaqList, NewsletterBox, CtaBand,
  CenterHead, ArrowSlider, GMark, MealCard, MenuCatIcon,
  GOOGLE_REVIEWS, GoogleReview,
});
