/* global React, ReactDOM, KKS_DATA, TweaksPanel, useTweaks, TweakSection, TweakSlider, TweakToggle, TweakRadio, TweakSelect, TweakColor */
const { useState, useEffect, useRef, useMemo } = React;

/* ---------- Persist selected character across reload ---------- */
const useLocalState = (key, initial) => {
  const [v, setV] = useState(() => {
    try { const s = localStorage.getItem(key); return s ? JSON.parse(s) : initial; }
    catch { return initial; }
  });
  useEffect(() => { try { localStorage.setItem(key, JSON.stringify(v)); } catch {} }, [key, v]);
  return [v, setV];
};

/* ---------- Progress bar + reveal-on-scroll ---------- */
function useScrollProgress() {
  useEffect(() => {
    const bar = document.querySelector('.progress-bar');
    const onScroll = () => {
      const h = document.documentElement;
      const pct = (h.scrollTop / (h.scrollHeight - h.clientHeight)) * 100;
      if (bar) bar.style.width = pct + '%';
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
}
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll('.reveal');
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
    }, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  });
}
function useActiveAnchor(ids) {
  const [active, setActive] = useState(ids[0]);
  useEffect(() => {
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => { if (e.isIntersecting) setActive(e.target.id); });
    }, { rootMargin: '-40% 0px -55% 0px', threshold: 0 });
    ids.forEach(id => { const el = document.getElementById(id); if (el) io.observe(el); });
    return () => io.disconnect();
  }, [ids.join(',')]);
  return active;
}

/* ============================================================
   NAV
   ============================================================ */
function Nav() {
  const active = useActiveAnchor(['hero','manifest','dom','bohaterowie','tomy','mapa','poznan','autor']);
  return (
    <nav className="nav">
      <div className="nav-inner">
        <a href="#hero" className="nav-mark">
          <span className="nav-mark-sigil">KŚS</span>
          <span>Klub Środowych Spacerów</span>
        </a>
        <div className="nav-anchors">
          <a href="#manifest" className={active==='manifest'?'active':''}>O serii</a>
          <a href="#dom" className={active==='dom'?'active':''}>Pod Lipami</a>
          <a href="#bohaterowie" className={active==='bohaterowie'?'active':''}>Bohaterowie</a>
          <a href="#tomy" className={active==='tomy'?'active':''}>Tomy</a>
          <a href="#poznan" className={active==='poznan'?'active':''}>Świat</a>
          <a href="#autor" className={active==='autor'?'active':''}>Autor</a>
        </div>
        <div className="nav-cta">
          <a className="btn btn-primary btn-arrow" href="#tomy">Poznaj serię</a>
        </div>
      </div>
    </nav>
  );
}

/* ============================================================
   HERO
   ============================================================ */
function Hero() {
  return (
    <section id="hero" className="hero" data-screen-label="01 Hero">
      <div className="hero-bg">
        <img className="hero-photo" src="assets/hero-villa-ultrawide.webp" alt="Willa Pod Lipami — Sołacz, Poznań" fetchpriority="high"/>
        <div className="hero-photo-overlay" aria-hidden="true"></div>
        <svg className="hero-villa" viewBox="0 0 1200 700" preserveAspectRatio="xMaxYMax meet" aria-hidden="true" style={{display:'none'}}>
          <defs>
            <linearGradient id="lg-v" x1="0" x2="0" y1="0" y2="1">
              <stop offset="0" stopColor="#142820" />
              <stop offset="1" stopColor="#0C1511" />
            </linearGradient>
          </defs>
          {/* linden trees canopy */}
          <path d="M0 380 Q80 240 200 340 T400 320 T620 340 T860 300 T1200 340 L1200 700 L0 700 Z" fill="url(#lg-v)" opacity="0.85"/>
          {/* villa silhouette */}
          <g opacity="0.9">
            <rect x="720" y="360" width="380" height="220" fill="#0C1511"/>
            <polygon points="700,360 910,270 1120,360" fill="#0C1511"/>
            <rect x="880" y="240" width="60" height="140" fill="#0C1511"/>
            {/* two warm windows */}
            <rect x="760" y="420" width="30" height="40" fill="#B69359" opacity="0.55"/>
            <rect x="830" y="420" width="30" height="40" fill="#B69359" opacity="0.35"/>
            <rect x="990" y="430" width="26" height="34" fill="#B69359" opacity="0.5"/>
          </g>
          {/* garden path */}
          <path d="M0 640 Q400 600 800 620 T1200 610" stroke="#5E665F" strokeWidth="1" fill="none" opacity="0.35"/>
          {/* 4 tiny walker silhouettes */}
          <g fill="#0C1511" opacity="0.95">
            <ellipse cx="380" cy="630" rx="3" ry="2"/><rect x="378" y="618" width="4" height="14" rx="1"/>
            <ellipse cx="420" cy="628" rx="3" ry="2"/><rect x="418" y="616" width="4" height="14" rx="1"/>
            <ellipse cx="470" cy="626" rx="3" ry="2"/><rect x="468" y="613" width="4" height="15" rx="1"/>
            <ellipse cx="512" cy="624" rx="3" ry="2"/><rect x="510" y="611" width="4" height="15" rx="1"/>
          </g>
        </svg>
      </div>
      <div className="hero-grain" aria-hidden="true"></div>

      <div className="hero-corner">
        POZNAŃ / SOŁACZ<br/>
        <span style={{color:'var(--tertiary)'}}>MMXXVI</span>
      </div>

      <div className="container hero-content reveal">
        <div className="hero-eyebrow label">Tomasz Kotliński przedstawia</div>
        <h1 className="hero-title">
          <span className="hero-title-line">Klub</span>
          <span className="hero-title-line hero-title-line-2">Środowych</span>
          <span className="hero-title-line hero-title-line-3">Spacerów</span>
        </h1>
        <p className="hero-lead">
          W Poznaniu jest dom, w którym środy zaczynają się od herbaty.
          A czasem od pytania, którego nikt nie chciał już zadać.
        </p>
        <div className="hero-ctas">
          <a className="btn btn-primary btn-arrow" href="#manifest">Wejdź do świata serii</a>
          <a className="btn btn-secondary" href="#tomy">Poznaj siedem tomów</a>
        </div>
        <p className="hero-microcopy">
          Siedmiotomowa seria kryminalna o pamięci, winie i odwadze, by nie odwrócić wzroku.
        </p>
      </div>

      <div className="hero-scroll-hint">Scroll</div>
    </section>
  );
}

/* ============================================================
   MANIFEST
   ============================================================ */
function Manifest() {
  return (
    <section id="manifest" className="manifest" data-screen-label="02 Manifest">
      <div className="container">
        <div className="manifest-grid">
          <div className="manifest-lhs reveal">
            <div className="section-eyebrow label">Manifest</div>
            <h2 className="manifest-h2">
              Prawda<br/>nie ma<br/><em>terminu ważności.</em>
            </h2>
          </div>
          <div className="manifest-rhs reveal">
            <p>
              Nie wszyscy bohaterowie zaczynają od nowa. Niektórzy noszą całe życie w jednej teczce,
              w jednym niewysłanym liście, w zdaniu, którego nie wypowiedzieli na czas.
            </p>
            <p>
              „Klub Środowych Spacerów” to historia ludzi, którzy nie godzą się na wygodne wyjaśnienia.
              W starej willi na poznańskim Sołaczu spotykają się, piją herbatę, idą przez park —
              i słuchają uważniej niż świat, który zdążył uznać ich za niewidzialnych.
            </p>
            <p>
              Każdy tom rozpoczyna się od sprawy. Wszystkie razem prowadzą do pytania trudniejszego
              niż „kto zabił?”: jak żyć, kiedy prawda przychodzi za późno — ale jednak przychodzi?
            </p>
            <blockquote className="manifest-quote">„Każda środa ma swoją prawdę.”</blockquote>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ============================================================
   DOM POD LIPAMI
   ============================================================ */
function Dom() {
  const [active, setActive] = useState(0);
  const points = [
    { label: 'Weranda', preview: 'rozmowy przy oknie o zmierzchu.', text: 'Rozmowy, które nie chcą się skończyć.', img: 'assets/dom-weranda.webp', alt: 'Weranda willi Pod Lipami o zmierzchu' },
    { label: 'Ogród różany', preview: 'pierwsza pora spotkania.', text: 'Pierwsza sprawa. I pierwsze pytanie.', img: 'assets/dom-ogrod-rozany.webp', alt: 'Ogród różany przy willi' },
    { label: 'Park Sołacki', preview: 'ścieżka po deszczu.', text: 'Droga, na której łatwiej powiedzieć prawdę.', img: 'assets/dom-park-solacki.webp', alt: 'Park Sołacki po deszczu' },
    { label: 'Gabinet', preview: 'lupa, maszyna do pisania, wieczór nad aktami.', text: 'Tu prawda przestaje się kryć między wierszami.', img: 'assets/gabinet-klub-przy-pracy.webp', alt: 'Władek, Tadeusz, Helena i Gabriela pracują nad sprawą w gabinecie willi Pod Lipami' }
  ];
  return (
    <section id="dom" className="dom" data-screen-label="03 Dom Pod Lipami">
      <div className="container-wide">
        <div className="dom-header reveal">
          <div>
            <div className="section-eyebrow label">Poznań / Sołacz</div>
            <h2 className="dom-h2">Dom, który pamięta<br/><em>więcej, niż mówi.</em></h2>
          </div>
          <div className="dom-copy">
            <p>„Pod Lipami” nie jest kryjówką ani luksusowym azylem. To stara willa z mniej niż idealnym światłem,
              skrzypiącym parkietem, zegarem w holu i werandą, na której herbaty nigdy nie pije się całkiem bez powodu.</p>
            <p>Tu zaczyna się każda środa. Tu ktoś milczy odrobinę za długo. Tu dokumenty wracają z przeszłości,
              a ogród różany pamięta, że najgorsze rzeczy nie zawsze dzieją się w ciemności.</p>
          </div>
        </div>

        <div className="dom-image reveal">
          {points.map((p, i) => (
            <img
              key={i}
              src={p.img}
              alt={p.alt}
              loading={i === 0 ? undefined : 'lazy'}
              className={`dom-image-photo ${active === i ? 'is-active' : ''}`}
            />
          ))}
          <div className="dom-image-caption">
            <span className="dom-image-caption-num">0{Math.max(0, active) + 1} / 0{points.length}</span>
            <span className="dom-image-caption-label">{(points[active] || points[0]).label}</span>
          </div>
        </div>

        <div className={`dom-points dom-points-${points.length}`}>
          {points.map((p, i) => (
            <div key={i}
                 className={`dom-point ${active===i?'active':''}`}
                 onClick={()=>setActive(i)}
                 tabIndex="0"
                 role="button"
                 aria-pressed={active === i}
                 aria-label={`Pokaż zdjęcie: ${p.label}`}
                 onKeyDown={e=>{ if(e.key==='Enter'||e.key===' '){e.preventDefault(); setActive(i);}}}>
              <div className="dom-point-label" data-idx={`0${i+1}`}>{p.label}</div>
              <div className="dom-point-preview">{p.preview}</div>
              <div className="dom-point-text">{p.text}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ============================================================
   BOHATEROWIE
   ============================================================ */
function Bohaterowie({ variant }) {
  const chars = KKS_DATA.characters;
  const [selectedId, setSelectedId] = useLocalState('kks-selected-char', chars[0].id);
  const [hoveredId, setHoveredId] = useState(null);
  const focusId = hoveredId || selectedId;
  const focusChar = chars.find(c => c.id === focusId) || chars[0];
  const selectedChar = chars.find(c => c.id === selectedId) || chars[0];

  if (variant === 'grid') {
    return (
      <section id="bohaterowie" className="bohaterowie" data-screen-label="04 Bohaterowie">
        <div className="container-wide">
          <div className="boh-header reveal">
            <div>
              <div className="section-eyebrow label">Klub</div>
              <h2 className="boh-h2">Ci, którzy<br/><em>patrzą dłużej.</em></h2>
            </div>
            <p className="boh-intro">
              Nie są nieomylni. Nie są bezpieczni. Nie są już na początku życia —
              i właśnie dlatego wiedzą, ile kosztuje każda decyzja.
            </p>
          </div>
          <div className="boh-grid reveal">
            {chars.map(c => (
              <div key={c.id} className="boh-portrait" onClick={()=>setSelectedId(c.id)}>
                <img src={c.img} alt={c.name} loading="lazy"/>
                <div className="boh-portrait-caption">
                  <div className="boh-portrait-name">{c.name.split(' ').slice(-1)[0]}</div>
                  <div className="boh-portrait-role">{c.shortRole}</div>
                </div>
              </div>
            ))}
          </div>
          <div className="boh-detail" style={{position:'relative'}}>
            <div className="boh-detail-left">
              <div className="boh-detail-tag">{selectedChar.tag}</div>
              <div className="boh-detail-name">{selectedChar.name}</div>
              <div className="boh-detail-role">{selectedChar.role}</div>
            </div>
            <div className="boh-detail-right">
              <p>{selectedChar.copy}</p>
            </div>
          </div>
        </div>
      </section>
    );
  }

  // Default: horizontal rail
  return (
    <section id="bohaterowie" className="bohaterowie" data-screen-label="04 Bohaterowie">
      <div className="container-wide">
        <div className="boh-header reveal">
          <div>
            <div className="section-eyebrow label">Klub</div>
            <h2 className="boh-h2">Ci, którzy<br/><em>patrzą dłużej.</em></h2>
          </div>
          <p className="boh-intro">
            Nie są nieomylni. Nie są bezpieczni. Nie są już na początku życia —
            i właśnie dlatego wiedzą, ile kosztuje każda decyzja.
          </p>
        </div>

        <div className="boh-rail-wrap reveal">
          <div className="boh-rail" style={{gridTemplateColumns: chars.map(c => c.id === focusId ? '55fr' : '22fr').join(' ')}}>
            {chars.map((c, i) => (
              <div key={c.id}
                   className={`boh-portrait ${c.id !== focusId ? 'dimmed' : ''}`}
                   onMouseEnter={()=>setHoveredId(c.id)}
                   onMouseLeave={()=>setHoveredId(null)}
                   onClick={()=>setSelectedId(c.id)}
                   onFocus={()=>setHoveredId(c.id)}
                   onBlur={()=>setHoveredId(null)}
                   tabIndex="0"
                   role="button"
                   aria-label={`Wybierz postać: ${c.name}`}
                   style={{transition:'grid-column 0.5s ease'}}>
                <img src={c.img} alt={c.name} loading="lazy"/>
                <div className="boh-portrait-corner">0{i+1}</div>
                {c.id === focusId && (
                  <div className="boh-portrait-caption">
                    <div className="boh-portrait-name">{c.name}</div>
                    <div className="boh-portrait-role">{c.shortRole.toUpperCase()}</div>
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>

        <div className="boh-detail" style={{position:'relative'}}>
          <div className="boh-detail-left">
            <div className="boh-detail-tag">{selectedChar.tag}</div>
            <div className="boh-detail-name">{selectedChar.name}</div>
            <div className="boh-detail-role">{selectedChar.role}</div>
          </div>
          <div className="boh-detail-right">
            <p>{selectedChar.copy}</p>
          </div>
          <div className="boh-detail-key">0{chars.findIndex(c=>c.id===selectedId)+1}</div>
        </div>
      </div>
    </section>
  );
}

/* ============================================================
   TOMY
   ============================================================ */
function Tomy({ variant }) {
  const vols = KKS_DATA.volumes;
  const [activeIdx, setActiveIdx] = useState(0);
  const containerRef = useRef(null);
  const itemRefs = useRef([]);

  useEffect(() => {
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          const idx = itemRefs.current.indexOf(e.target);
          if (idx !== -1) setActiveIdx(idx);
        }
      });
    }, { rootMargin: '-40% 0px -55% 0px', threshold: 0 });
    itemRefs.current.forEach(el => el && io.observe(el));
    return () => io.disconnect();
  }, []);

  const activeVol = vols[activeIdx];

  return (
    <section id="tomy" className="tomy" data-screen-label="05 Tomy">
      <div className="container">
        <div className="tomy-header reveal">
          <div>
            <div className="section-eyebrow label" style={{color: 'var(--wine)'}}>
              <style>{`.tomy .section-eyebrow::before { background: var(--wine); }`}</style>
              Siedem tomów
            </div>
            <h2 className="tomy-h2">Siedem spraw.<br/><em>Jedna historia.</em></h2>
          </div>
          <p className="tomy-intro">
            Każdy tom można otworzyć jak osobną zagadkę. Ale dopiero razem tworzą opowieść o tym,
            co zostaje po przemocy, milczeniu i ludziach, którzy odmawiają oddania pamięci cudzym wersjom.
          </p>
        </div>

        <div className="tomy-grid" ref={containerRef}>
          <div className="tomy-num-sticky">
            <span className="tomy-num-sticky-num">{activeVol.num}</span>
            <span className="tomy-num-sticky-arc">{activeVol.arc}</span>
          </div>

          <div className="tomy-list">
            {vols.map((v, i) => (
              <React.Fragment key={v.num}>
                {i === 4 && (
                  <div className="tomy-arc-divider reveal">
                    <div className="arc-label">Trylogia Dziedzictwo</div>
                    <hr/>
                  </div>
                )}
                <article className="tomy-item reveal" ref={el => itemRefs.current[i] = el}>
                  <a href={`tomy/tom-${v.num}.html`} className="tomy-cover" aria-label={`Zobacz tom ${v.num}: ${v.title}`}>
                    <img src={v.img} alt={`Okładka: ${v.title}`} loading="lazy" className="tomy-cover-img"/>
                  </a>
                  <div className="tomy-text">
                    <div className="tomy-tomnum">Tom {v.num}</div>
                    <h3 className="tomy-title">{v.title}</h3>
                    <div className="tomy-motto">„{v.motto}”</div>
                    <p className="tomy-copy">{v.copy}</p>
                    <a href={`tomy/tom-${v.num}.html`} className="btn btn-secondary-dark btn-arrow">Zobacz tom</a>
                  </div>
                </article>
              </React.Fragment>
            ))}
          </div>
        </div>

        <div className="tomy-cta-wrap reveal">
          <div className="tomy-cta-eyebrow">Nie musisz zaczynać na początku. Ale warto.</div>
          <a href="tomy/tom-01.html" className="btn btn-primary btn-arrow">Zacznij od pierwszej środy</a>
        </div>

        {/* Boxset showcase */}
        <div className="boxset reveal">
          <div className="boxset-image">
            <img src="assets/boxset-open-gold-ribbon.webp" alt="Otwarte pudełko kolekcjonerskie ze złotą wstążką i trzema tomami serii Klub Środowych Spacerów"/>
          </div>
          <div className="boxset-content">
            <div className="label">Edycja kolekcjonerska</div>
            <h3 className="boxset-title">Siedem tomów w jednym pudle.</h3>
            <p>
              Kompletna seria w burgundowym opakowaniu ze złotą wstążką.
              Dla tych, którzy chcą przejść całą drogę od pierwszej środy do ostatniego spotkania —
              nie odkładając niczego na później.
            </p>
            <a href="#" data-link="boxset" className="btn btn-secondary-dark btn-arrow">Zapytaj o zestaw</a>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ============================================================
   MAPA / ARC
   ============================================================ */
function Mapa() {
  const vols = KKS_DATA.volumes;
  const [activeVol, setActiveVol] = useState(0);
  const romans = ['I','II','III','IV','V','VI','VII'];
  const motifIcons = ['cup','mask','envelope','teapot','typewriter','badge','lamp'];

  const MotifSVG = ({ kind }) => {
    const props = { stroke: 'currentColor', strokeWidth: '1', fill: 'none', strokeLinecap: 'round', strokeLinejoin: 'round' };
    switch (kind) {
      case 'cup':
        return <svg viewBox="0 0 48 48" {...props}><path d="M12 20 L36 20 L34 34 Q34 38 30 38 L18 38 Q14 38 14 34 Z"/><path d="M36 24 Q42 24 42 29 Q42 34 36 34"/><path d="M18 14 Q18 12 20 12 M22 14 Q22 12 24 12 M26 14 Q26 12 28 12"/></svg>;
      case 'mask':
        return <svg viewBox="0 0 48 48" {...props}><path d="M8 16 Q8 12 12 12 L36 12 Q40 12 40 16 L40 26 Q40 38 24 38 Q8 38 8 26 Z"/><circle cx="18" cy="22" r="2" fill="currentColor"/><circle cx="30" cy="22" r="2" fill="currentColor"/><path d="M18 30 Q24 32 30 30"/></svg>;
      case 'envelope':
        return <svg viewBox="0 0 48 48" {...props}><rect x="8" y="14" width="32" height="22"/><path d="M8 14 L24 26 L40 14"/><path d="M8 36 L18 24 M40 36 L30 24"/></svg>;
      case 'teapot':
        return <svg viewBox="0 0 48 48" {...props}><path d="M10 22 L38 22 L36 34 Q36 38 32 38 L16 38 Q12 38 12 34 Z"/><path d="M38 26 L44 22 L44 30 L38 30"/><path d="M18 22 Q18 16 24 16 Q30 16 30 22"/><circle cx="24" cy="14" r="1.5" fill="currentColor"/></svg>;
      case 'typewriter':
        return <svg viewBox="0 0 48 48" {...props}><rect x="8" y="20" width="32" height="16" rx="1"/><path d="M14 20 L14 12 L34 12 L34 20"/><path d="M20 12 L20 8 L28 8 L28 12"/><line x1="14" y1="28" x2="34" y2="28"/><circle cx="17" cy="32" r="0.8" fill="currentColor"/><circle cx="21" cy="32" r="0.8" fill="currentColor"/><circle cx="25" cy="32" r="0.8" fill="currentColor"/><circle cx="29" cy="32" r="0.8" fill="currentColor"/></svg>;
      case 'badge':
        return <svg viewBox="0 0 48 48" {...props}><path d="M24 6 L38 12 L38 22 Q38 34 24 42 Q10 34 10 22 L10 12 Z"/><path d="M18 22 L22 26 L30 18"/></svg>;
      case 'lamp':
        return <svg viewBox="0 0 48 48" {...props}><path d="M14 16 L34 16 L30 26 L18 26 Z"/><line x1="24" y1="26" x2="24" y2="38"/><ellipse cx="24" cy="38" rx="8" ry="2"/><line x1="20" y1="12" x2="28" y2="12"/><line x1="24" y1="8" x2="24" y2="12"/></svg>;
      default:
        return null;
    }
  };

  return (
    <section id="mapa" className="mapa" data-screen-label="06 Mapa serii">
      <div className="container">
        <div className="mapa-header reveal">
          <div className="section-eyebrow label" style={{justifyContent:'center'}}>Łuk narracyjny</div>
          <h2 className="mapa-h2">Od naprawy<br/><em>do dziedzictwa.</em></h2>
          <p className="mapa-subtitle">
            Dwie arki. Siedem środ. Jedna metoda. Kliknij lub najedź na kolejne tomy,
            aby zobaczyć, jak rozwija się opowieść.
          </p>
        </div>

        {/* Big roman numeral background */}
        <div className="mapa-roman-bg" aria-hidden="true">
          {romans[activeVol]}
        </div>

        {/* Phases split label */}
        <div className="mapa-phase-labels reveal">
          <div className="mapa-phase-tag mapa-phase-tag-1">
            <span className="mapa-phase-tag-arc">Arka I · Tetralogia</span>
            <span className="mapa-phase-tag-name">Naprawa</span>
            <span className="mapa-phase-tag-toms">I — IV</span>
          </div>
          <div className="mapa-phase-divider"></div>
          <div className="mapa-phase-tag mapa-phase-tag-2">
            <span className="mapa-phase-tag-arc">Arka II · Trylogia</span>
            <span className="mapa-phase-tag-name">Dziedzictwo</span>
            <span className="mapa-phase-tag-toms">V — VII</span>
          </div>
        </div>

        {/* Interactive medallion timeline */}
        <div className="mapa-timeline reveal">
          <svg className="mapa-timeline-svg" viewBox="0 0 1200 100" preserveAspectRatio="none" aria-hidden="true">
            <defs>
              <linearGradient id="mapa-arc1" x1="0" x2="1" y1="0" y2="0">
                <stop offset="0" stopColor="#B69359" stopOpacity="0.15"/>
                <stop offset="0.55" stopColor="#B69359" stopOpacity="0.95"/>
                <stop offset="0.6" stopColor="#B69359" stopOpacity="0.35"/>
              </linearGradient>
              <linearGradient id="mapa-arc2" x1="0" x2="1" y1="0" y2="0">
                <stop offset="0.4" stopColor="#5A2433" stopOpacity="0.35"/>
                <stop offset="0.45" stopColor="#5A2433" stopOpacity="0.95"/>
                <stop offset="1" stopColor="#5A2433" stopOpacity="0.15"/>
              </linearGradient>
            </defs>
            {/* Two arcs meeting in the middle */}
            <path d="M 20 60 Q 300 20 600 50" stroke="url(#mapa-arc1)" strokeWidth="1.2" fill="none"/>
            <path d="M 600 50 Q 900 80 1180 60" stroke="url(#mapa-arc2)" strokeWidth="1.2" fill="none"/>
            {/* Center pivot point */}
            <circle cx="600" cy="50" r="4" fill="#B69359" opacity="0.85"/>
            <circle cx="600" cy="50" r="10" fill="none" stroke="#B69359" strokeWidth="0.5" opacity="0.4"/>
          </svg>

          <div className="mapa-medallions">
            {vols.map((v, i) => {
              const isArc2 = i >= 4;
              return (
                <button
                  key={v.num}
                  type="button"
                  className={`mapa-medallion ${activeVol === i ? 'active' : ''} ${isArc2 ? 'arc-2' : 'arc-1'}`}
                  onMouseEnter={() => setActiveVol(i)}
                  onFocus={() => setActiveVol(i)}
                  onClick={() => setActiveVol(i)}
                  aria-label={`Tom ${romans[i]}: ${v.title}`}
                >
                  <div className="mapa-medallion-icon">
                    <MotifSVG kind={motifIcons[i]}/>
                  </div>
                  <div className="mapa-medallion-num">{romans[i]}</div>
                  <div className="mapa-medallion-title">{v.title}</div>
                </button>
              );
            })}
          </div>
        </div>

        {/* Active volume detail — reveals on hover/click */}
        <div className="mapa-detail reveal">
          <div className="mapa-detail-inner">
            <div className="mapa-detail-num">
              <span className="mapa-detail-roman">{romans[activeVol]}</span>
              <span className="mapa-detail-arc">
                {activeVol < 4 ? 'Naprawa' : 'Dziedzictwo'}
              </span>
            </div>
            <div className="mapa-detail-content">
              <div className="mapa-detail-title">{vols[activeVol].title}</div>
              <div className="mapa-detail-motto">„{vols[activeVol].motto}"</div>
              <p className="mapa-detail-copy">{vols[activeVol].copy}</p>
              <a href={`tomy/tom-${vols[activeVol].num}.html`} className="btn btn-secondary btn-arrow">
                Otwórz tom {romans[activeVol]}
              </a>
            </div>
          </div>
        </div>

        <p className="mapa-footer reveal">
          Nie ma tu łatwych rozgrzeszeń. Jest droga, na której wreszcie można mówić pełnym głosem.
        </p>
      </div>
    </section>
  );
}

/* ============================================================
   POZNAŃ
   ============================================================ */
function Poznan() {
  return (
    <section id="poznan" className="poznan" data-screen-label="07 Poznań">
      {/* Full-bleed panorama at top */}
      <div className="poznan-panorama reveal">
        <img src="assets/solacz-aerial.webp" alt="Sołacz z lotu ptaka — Poznań"/>
        <div className="poznan-panorama-caption">
          <span className="poznan-panorama-coords">52° 26′ N · 16° 54′ E</span>
          <span className="poznan-panorama-sep">·</span>
          <span>Poznań / Sołacz</span>
        </div>
      </div>

      <div className="container">
        <div className="poznan-header reveal">
          <div className="poznan-header-left">
            <div className="section-eyebrow label">Miasto</div>
            <h2 className="poznan-h2">Poznań, który<br/><em>zna twoje imię.</em></h2>
          </div>
          <div className="poznan-copy">
            <p className="poznan-lead">
              Sołacz nie jest tłem. Jest rytmem tej serii — cień lip, parkowa aleja, wilgotny bruk po deszczu, teatralne foyer, archiwum.
            </p>
            <p>
              To miasto ludzi, którzy mijają się codziennie, a potem przypominają sobie jedno zdanie za późno.
              Miasto, w którym spacer może stać się śledztwem — nie dlatego, że świat nagle robi się niezwykły,
              ale dlatego, że ktoś wreszcie patrzy uważnie.
            </p>
          </div>
        </div>

        {/* Cinematic slider gallery — jedyny wizualny blok pod nagłówkiem sekcji */}
        <PoznanGallery/>
      </div>
    </section>
  );
}

/* ============================================================
   POZNAŃ GALLERY — filmowy slider z auto-play
   ============================================================ */
function PoznanGallery() {
  const slides = [
    { src: 'assets/hero-villa-ultrawide.webp', title: 'Pod Lipami', caption: 'Willa na Sołaczu o świcie. Cztery sylwetki idą przez ogród.' },
    { src: 'assets/solacz-aerial.webp', title: 'Sołacz z góry', caption: 'Dzielnica lip i willi. Miejsce, w którym gubi się pośpiech.' },
    { src: 'assets/solacz-ulica.webp', title: 'Ulica po deszczu', caption: 'Bruk, latarnia, ktoś idzie z parasolem. Zwykły wieczór.' },
    { src: 'assets/park-solacki.webp', title: 'Park Sołacki', caption: 'Aleja lip w poranku. Ścieżka, na której łatwiej myśleć.' },
    { src: 'assets/srodowy-spacer-ogrod.webp', title: 'Środowy spacer', caption: 'Ogród różany. Herbata już na stole. Nikt jeszcze nie musi mówić.' },
    { src: 'assets/willa-hol.webp', title: 'Hol Pod Lipami', caption: 'Wejście, które słyszało wszystko. Zegar w rogu. Płaszcz na ławce.' },
    { src: 'assets/poznan-teatr.webp', title: 'Foyer teatru', caption: 'Burgundowe kurtyny. Ostatnia sceny scena. Ktoś jeszcze nie odszedł.' },
    { src: 'assets/poznan-rynek.webp', title: 'Stary Rynek', caption: 'Ratusz, kamienice, ludzie, którzy tu byli zawsze.' },
    { src: 'assets/detal-roze-teczki.webp', title: 'Papier, który wraca', caption: 'Teczka. Zegarek. Jedna róża, która czeka na drugą.' },
    { src: 'assets/alejka-lipy.webp', title: 'Aleja lip', caption: 'Droga, na której cisza mówi więcej niż zdanie.' }
  ];
  const [idx, setIdx] = useState(0);
  const [paused, setPaused] = useState(false);
  const timerRef = useRef(null);

  const go = (n) => setIdx(((n % slides.length) + slides.length) % slides.length);

  useEffect(() => {
    if (paused) return;
    timerRef.current = setInterval(() => setIdx(i => (i + 1) % slides.length), 5000);
    return () => clearInterval(timerRef.current);
  }, [paused, slides.length]);

  return (
    <div className="pg-wrap reveal">
      <div className="pg-header">
        <div className="section-eyebrow label" style={{color:'var(--tertiary)'}}>Galeria świata</div>
        <h3 className="pg-title">Świat serii <em>w dziesięciu kadrach.</em></h3>
        <p className="pg-copy">
          Miejsca, w których toczy się „Klub Środowych Spacerów". Sołacz, willa „Pod Lipami",
          Park Sołacki, poznańskie foyer i archiwa. Każdy kadr można zatrzymać — świat się wtedy nie kończy, tylko czeka.
        </p>
      </div>

      <div
        className="pg-stage"
        onMouseEnter={() => setPaused(true)}
        onMouseLeave={() => setPaused(false)}
      >
        {slides.map((s, i) => (
          <div key={i} className={`pg-slide ${i === idx ? 'is-active' : ''}`} aria-hidden={i !== idx}>
            <img src={s.src} alt={s.title} loading={i === 0 ? undefined : 'lazy'}/>
          </div>
        ))}

        <button className="pg-nav pg-nav-prev" onClick={() => go(idx - 1)} aria-label="Poprzedni kadr">
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18 L9 12 L15 6"/></svg>
        </button>
        <button className="pg-nav pg-nav-next" onClick={() => go(idx + 1)} aria-label="Następny kadr">
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6 L15 12 L9 18"/></svg>
        </button>

        <div className="pg-caption">
          <div className="pg-caption-num">
            <span className="pg-caption-num-current">{String(idx + 1).padStart(2, '0')}</span>
            <span className="pg-caption-num-sep">/</span>
            <span className="pg-caption-num-total">{String(slides.length).padStart(2, '0')}</span>
          </div>
          <div className="pg-caption-text">
            <div className="pg-caption-title">{slides[idx].title}</div>
            <div className="pg-caption-line">{slides[idx].caption}</div>
          </div>
        </div>
      </div>

      {/* Thumbnail strip */}
      <div className="pg-thumbs">
        {slides.map((s, i) => (
          <button
            key={i}
            className={`pg-thumb ${i === idx ? 'is-active' : ''}`}
            onClick={() => go(i)}
            aria-label={`Kadr ${i + 1}: ${s.title}`}
          >
            <img src={s.src} alt="" loading="lazy"/>
            <span className="pg-thumb-num">{String(i + 1).padStart(2, '0')}</span>
          </button>
        ))}
      </div>

      {/* Progress bar */}
      <div className="pg-progress" aria-hidden="true">
        <div className="pg-progress-track">
          <div
            className="pg-progress-fill"
            style={{width: `${((idx + 1) / slides.length) * 100}%`}}
          ></div>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   AUTOR
   ============================================================ */
function Autor() {
  return (
    <section id="autor" className="autor" data-screen-label="08 Autor">
      <div className="container">
        <div className="autor-grid">
          <div className="autor-portrait-wrap reveal">
            <div className="autor-portrait">
              <img src="assets/author-tomasz-kotlinski.webp" alt="Tomasz Kotliński — autor serii" loading="lazy"/>
            </div>
            <div className="autor-portrait-caption">Fot. Archiwum autora</div>
          </div>
          <div className="autor-content reveal">
            <div className="autor-eyebrow label">Autor</div>
            <h2 className="autor-h2">Tomasz Kotliński</h2>
            <p>
              Autor serii „Klub Środowych Spacerów” — opowieści o zbrodniach, które nie kończą się
              wraz z odkryciem sprawcy, i o ludziach, którzy uczą się żyć bez wygodnych wersji
              własnej historii.
            </p>
            <p>
              W centrum jego świata stoją pamięć, rozmowa i miejsca, które zachowują więcej,
              niż można byłoby przypuszczać. Poznań jest tu nie tylko miastem akcji, ale sposobem
              patrzenia: uważnym, konkretnym i czułym wobec tego, co zwykle pozostaje poza kadrem.
            </p>
            <blockquote className="autor-quote">
              „Najbardziej interesuje mnie chwila, w której człowiek przestaje odwracać wzrok.”
            </blockquote>
            <div className="autor-meta">
              <a href="#">Wydawca</a>
              <a href="#">Kontakt</a>
              <a href="#">Media</a>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ============================================================
   NEWSLETTER
   ============================================================ */
function Newsletter() {
  const [email, setEmail] = useState('');
  const [sent, setSent] = useState(false);
  const submit = (e) => {
    e.preventDefault();
    if (!email || !email.includes('@')) return;
    setSent(true);
    setTimeout(() => { setSent(false); setEmail(''); }, 6000);
  };
  return (
    <section id="newsletter" className="newsletter" data-screen-label="09 Newsletter">
      <div className="container">
        <div className="newsletter-grid">
          <div className="reveal">
            <div className="newsletter-eyebrow label">List środowy</div>
            <h2 className="newsletter-h2">Nie przegap<br/><em>kolejnej środy.</em></h2>
            <p className="newsletter-copy">
              Listy od autora, kulisy świata „Pod Lipami”, wiadomości o wydaniach i spotkaniach.
              Rzadko. Tylko wtedy, kiedy jest po co pisać.
            </p>
            {!sent ? (
              <form className="newsletter-form" onSubmit={submit}>
                <input
                  type="email"
                  value={email}
                  onChange={e => setEmail(e.target.value)}
                  placeholder="Twój adres e-mail"
                  aria-label="Adres e-mail"
                  required
                />
                <button type="submit">Zapisuję się →</button>
              </form>
            ) : (
              <div className="newsletter-success visible">
                Dziękujemy. Napiszemy tylko wtedy, kiedy będzie po co.
              </div>
            )}
            <p className="newsletter-consent">
              Zapisując się, akceptujesz politykę prywatności. Bez spamu. Bez udawanej bliskości.
            </p>
          </div>
          <div className="newsletter-right reveal">
            <img src="assets/newsletter-maszyna.webp" alt="List środowy — maszyna do pisania przy oknie" loading="lazy" className="newsletter-photo"/>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ============================================================
   RICH FOOTER — imported from rich-footer.js (window.RichFooter)
   Aliased locally so JSX <FooterMounted/> resolves at render time.
   ============================================================ */
function FooterMounted() {
  const C = window.RichFooter;
  if (!C) return null;
  return React.createElement(C, { pathPrefix: '' });
}

/* ============================================================
   FINAŁ (bez footer — footer jest niżej jako RichFooter)
   ============================================================ */
function Final() {
  return (
    <>
      <section id="final" className="final" data-screen-label="10 Finał">
        <svg className="final-silhouettes" viewBox="0 0 800 200" preserveAspectRatio="xMidYMax meet" aria-hidden="true">
          {/* Six walkers, receding into light */}
          <g fill="#0C1511">
            {[
              {cx: 140, s: 1.0, o: 1},
              {cx: 240, s: 0.92, o: 0.95},
              {cx: 340, s: 0.85, o: 0.9},
              {cx: 440, s: 0.78, o: 0.82},
              {cx: 520, s: 0.7, o: 0.72},
              {cx: 600, s: 0.62, o: 0.6}
            ].map((f, i) => (
              <g key={i} transform={`translate(${f.cx}, ${180 - i*4}) scale(${f.s})`} opacity={f.o}>
                <ellipse cx="0" cy="-2" rx="6" ry="7"/>
                <path d="M -8 6 L 8 6 L 6 42 L -6 42 Z"/>
              </g>
            ))}
          </g>
          {/* Path highlight */}
          <path d="M 60 190 Q 400 170 780 180" stroke="#B69359" strokeWidth="0.5" fill="none" opacity="0.4"/>
        </svg>

        <div className="final-content reveal">
          <div className="section-eyebrow label" style={{justifyContent:'center'}}>Finał</div>
          <h2 className="final-h2">Nie wszystko da się naprawić.</h2>
          <p className="final-close">Ale można przestać milczeć.</p>
          <div className="final-cta">
            <a href="tomy/tom-01.html" className="btn btn-primary btn-arrow">Poznaj pierwszy tom</a>
          </div>
        </div>
      </section>

      <FooterMounted/>
    </>
  );
}

/* ============================================================
   APP
   ============================================================ */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "bohInteraction": "rail",
  "goldIntensity": 1,
  "showBoxset": true,
  "manifestQuoteColor": "wine",
  "grainOpacity": 0.5
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useScrollProgress();
  useReveal();

  // apply live css vars from tweaks
  useEffect(() => {
    document.documentElement.style.setProperty('--gold-intensity', t.goldIntensity);
    const grain = document.querySelector('.hero-grain');
    if (grain) grain.style.opacity = t.grainOpacity;
    const quote = document.querySelector('.manifest-h2 em');
    if (quote) quote.style.color = t.manifestQuoteColor === 'gold' ? 'var(--tertiary)' : 'var(--wine)';
  }, [t]);

  return (
    <>
      <div className="progress-bar"></div>
      <Nav/>
      <main>
        <Hero/>
        <Manifest/>
        <Dom/>
        <Bohaterowie variant={t.bohInteraction}/>
        <Tomy/>
        <Mapa/>
        <Poznan/>
        <Autor/>
        <Newsletter/>
        <Final/>
      </main>

      <TweaksPanel title="Tweaks">
        <TweakSection title="Bohaterowie">
          <TweakRadio
            label="Układ galerii"
            value={t.bohInteraction}
            onChange={v => setTweak('bohInteraction', v)}
            options={[
              { value: 'rail', label: 'Rail' },
              { value: 'grid', label: 'Siatka' }
            ]}
          />
        </TweakSection>

        <TweakSection title="Manifest">
          <TweakRadio
            label="Akcent w cytacie"
            value={t.manifestQuoteColor}
            onChange={v => setTweak('manifestQuoteColor', v)}
            options={[
              { value: 'wine', label: 'Burgund' },
              { value: 'gold', label: 'Złoto' }
            ]}
          />
        </TweakSection>

        <TweakSection title="Atmosfera">
          <TweakSlider
            label="Intensywność złota"
            value={t.goldIntensity}
            min={0.4} max={1.4} step={0.05}
            onChange={v => setTweak('goldIntensity', v)}
          />
          <TweakSlider
            label="Ziarno filmowe (hero)"
            value={t.grainOpacity}
            min={0} max={0.8} step={0.05}
            onChange={v => setTweak('grainOpacity', v)}
          />
        </TweakSection>

        <TweakSection title="Zestaw kolekcjonerski">
          <TweakToggle
            label="Pokazuj sekcję boxset"
            value={t.showBoxset}
            onChange={v => {
              setTweak('showBoxset', v);
              const el = document.querySelector('.boxset');
              if (el) el.style.display = v ? '' : 'none';
            }}
          />
        </TweakSection>
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
