/* Veld — homepage app */

const { useState, useEffect, useRef } = React;

/* Parallax scroll hook — same shape as the original useParallax */
function useParallax(speed = 0.3) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const rect = el.getBoundingClientRect();
        const viewCenter = window.innerHeight / 2;
        const elCenter = rect.top + rect.height / 2;
        const delta = (viewCenter - elCenter) * speed;
        el.style.transform = `translateY(${delta}px)`;
      });
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => {
      window.removeEventListener('scroll', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [speed]);
  return ref;
}

/* Full-bleed parallax banner */
function ParallaxBanner({ src, caption, height = '36vw', minH = '240px', maxH = '480px', speed = 0.22 }) {
  const ref = useParallax(speed);
  return (
    <div className="parallax-banner" style={{ height, minHeight: minH, maxHeight: maxH }}>
      <div ref={ref} className="bg-layer" style={{ backgroundImage: `url("${src}")` }} />
      <div className="grain-layer" />
      <div className="overlay-layer" />
      {caption && <div className="caption">{caption}</div>}
    </div>
  );
}

/* ---------- SCROLL PROGRESS THREAD (the golden thread) ---------- */
const WAYPOINTS = [
  { id: 'top',        label: 'Hearth' },
  { id: 'what',       label: 'Veld' },
  { id: 'fireflies',  label: 'Magic' },
  { id: 'arc',        label: 'The Arc' },
  { id: 'travelers',  label: 'The Room' },
  { id: 'tiers',      label: 'Contribution' },
  { id: 'apply',      label: 'Apply' },
];

function ScrollThread() {
  const thumbRef = useRef(null);
  const wayRefs = useRef([]);
  const rafRef = useRef(0);
  const activeIdRef = useRef(null);

  useEffect(() => {
    const onScroll = () => {
      if (rafRef.current) return;
      rafRef.current = requestAnimationFrame(() => {
        rafRef.current = 0;
        const total = document.documentElement.scrollHeight - window.innerHeight;
        const progress = total > 0 ? Math.min(100, Math.max(0, (window.scrollY / total) * 100)) : 0;
        if (thumbRef.current) thumbRef.current.style.height = `${progress}%`;

        let newActive = null;
        for (const w of WAYPOINTS) {
          const el = document.getElementById(w.id);
          if (!el) continue;
          const r = el.getBoundingClientRect();
          if (r.top < window.innerHeight / 2 && r.bottom > window.innerHeight / 2) {
            newActive = w.id; break;
          }
        }
        if (newActive !== activeIdRef.current) {
          activeIdRef.current = newActive;
          WAYPOINTS.forEach((w, i) => {
            const node = wayRefs.current[i];
            if (!node) return;
            const pos = (i / (WAYPOINTS.length - 1)) * 100;
            const passed = progress > pos;
            node.className = `way ${activeIdRef.current === w.id ? 'active' : passed ? 'passed' : ''}`;
          });
        }
      });
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => {
      window.removeEventListener('scroll', onScroll);
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
    };
  }, []);

  return (
    <div className="scroll-thread" aria-hidden="true">
      <div className="track"></div>
      <div ref={thumbRef} className="thumb"></div>
      {WAYPOINTS.map((w, i) => {
        const pos = (i / (WAYPOINTS.length - 1)) * 100;
        return (
          <a
            key={w.id}
            href={`#${w.id}`}
            ref={(el) => { wayRefs.current[i] = el; }}
            className="way"
            style={{ top: `calc(32px + (100% - 64px) * ${pos / 100})` }}
          >
            <div className="dot"></div>
            <span className="lbl">{w.label}</span>
          </a>
        );
      })}
    </div>
  );
}

/* ---------- NAV ---------- */
const NAV_LINKS = [
  { key: 'castle', label: 'The Castle' },
  { key: 'week',   label: 'The Week' },
  { key: 'rooms',  label: 'Rooms & Pricing' },
  { key: 'who',    label: 'Who Comes' },
  { key: 'faq',    label: 'FAQ' },
];
function Nav({ page }) {
  const { visible, atTop } = useSmartNav(80);
  return (
    <nav className={`nav ${atTop ? '' : 'scrolled'} ${visible ? '' : 'hidden'}`}>
      <div className="wrap nav-row">
        <a href="#/" className="nav-mark">
          <FlickerWord>Veld</FlickerWord>
        </a>
        <div className="nav-links">
          {NAV_LINKS.map(l => (
            <a key={l.key} href={`#/${l.key}`} className={`nav-page ${page === l.key ? 'active' : ''}`}>{l.label}</a>
          ))}
          <a href="#/apply" className="nav-cta">Request a seat</a>
        </div>
      </div>
    </nav>
  );
}

/* ---------- HERO ---------- */
let HERO_INTRO_DONE = false;
const HERO_HOOKS = [
  { primary: "You’ve spent years becoming someone.",
    accent:  "For five days, set it down." },
  { primary: "Builders, creators, thinkers, visionaries —",
    accent:  "all here to stop performing." },
  { primary: "From every field, every background.",
    accent:  "Here to connect, not to perform." },
  { primary: "Stop being your title, and see",
    accent:  "what becomes possible." },
];

function Hero({ hookIndex }) {
  const hook = HERO_HOOKS[hookIndex % HERO_HOOKS.length];
  const bgRef = useParallax(0.32);
  const zoomRef = useRef(null);
  const innerRef = useRef(null);
  const enterVeilRef = useRef(null);
  const guestName = useGuestName();
  const returning = useReturningVisitor();
  const [greeting, setGreeting] = useState('');

  useEffect(() => {
    setGreeting(getTimeGreeting());
  }, []);

  // The hero PINS; scroll is consumed by a push THROUGH the gate, then releases
  // to the next section — you enter, and arrive on the other side.
  useEffect(() => {
    const el = zoomRef.current;
    if (!el) return;
    const inner = innerRef.current;
    const veil = enterVeilRef.current;
    let raf = 0;
    let active = false;
    const clamp = (x) => Math.min(Math.max(x, 0), 1);
    const smooth = (x) => { x = clamp(x); return x * x * (3 - 2 * x); };
    const apply = () => {
      raf = 0;
      const wrap = el.closest('.hero-pin-wrap');
      if (!wrap) return;
      const total = wrap.offsetHeight - window.innerHeight;
      const p = total > 0 ? clamp(-wrap.getBoundingClientRect().top / total) : 0;
      const scale = 1 + smooth(p) * 3.4;            // hold + push THROUGH the gate to ~4.4x
      el.style.transform = `scale(${scale.toFixed(4)})`;
      if (inner) inner.style.opacity = (1 - smooth(p / 0.45)).toFixed(3);
      if (veil) veil.style.opacity = smooth((p - 0.62) / 0.38).toFixed(3);
    };
    const onScroll = () => { if (active && !raf) raf = requestAnimationFrame(apply); };
    const onEnd = () => { active = true; el.style.animation = 'none'; apply(); };
    el.addEventListener('animationend', onEnd);
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => {
      el.removeEventListener('animationend', onEnd);
      window.removeEventListener('scroll', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  const showReturning = returning && !guestName;
  const [intro] = useState(() => { const first = !HERO_INTRO_DONE; HERO_INTRO_DONE = true; return first; });

  return (
    <section className="hero-pin-wrap" id="top">
      <div className="hero">
        <div ref={bgRef} className="hero-bg parallax-hero">
          <div ref={zoomRef} className="hero-bg-zoom"></div>
        </div>
        <div className="hero-veil"></div>
        <div ref={enterVeilRef} className="hero-enter-veil"></div>

        <div className="hero-embers-layer">
          <EmberCanvas density={0.04} opacity={0.85} />
        </div>

        <div ref={innerRef} className={`hero-inner wrap ${intro ? 'intro' : ''}`}>
        {guestName && (
          <p className="guest-line">A seat has been held for {guestName}.</p>
        )}

        {greeting && (
          <p className="time-greeting">{greeting}</p>
        )}

        <div className="hero-eyebrow">
          <span className="dot"></span>
          <span>October 4 — 8, 2026 · Zamek Czocha</span>
          <span className="sep">·</span>
          <MoonPhase />
        </div>

        <h1 className="wordmark">
          <FlickerWord>Veld</FlickerWord>
        </h1>

        {showReturning ? (
          <p className="hero-tagline">
            Welcome back. <em>The gate is still open.</em>
          </p>
        ) : (
          <p className="hero-tagline">
            {hook.primary} <em>{hook.accent}</em>
          </p>
        )}

        <p className="hero-descriptor">
          Veld is a five-day, invitation-only gathering &mdash; eighty builders,
          artists and thinkers, <em>living together inside eight centuries of stone.</em>
          Every bed, every feast, every fire-circle included.
        </p>

        <div className="hero-meta hero-inventory">
          <span>Five Days</span>
          <span className="sep">·</span>
          <span>Four Nights</span>
          <span className="sep">·</span>
          <span>Eighty Souls</span>
          <span className="sep">·</span>
          <span className="hero-price">From &euro;1,800, all-in</span>
        </div>

        <div className="hero-pact-wrap">
          <span className="hero-pact-glyph" data-whisper="the mark of those who convene" aria-hidden="true">✦</span>
          <p className="hero-pact">No stages. No logos. No audience.</p>
        </div>

        <MagneticCTA strength={0.4} radius={120}>
          <a href="#apply" className="hero-cta">
            <span>Step over the threshold</span>
          </a>
        </MagneticCTA>

        <Countdown />
      </div>

        <div className="hero-scrollhint">
          <span>Begin</span>
          <span className="line"></span>
        </div>
      </div>
    </section>
  );
}

/* ---------- MANIFESTO — 4 stanzas, each lands on its own scroll ---------- */
function ManifestoStanza({ glyph, html }) {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) { setVisible(true); io.disconnect(); } },
      { threshold: 0.35, rootMargin: '0px 0px -10% 0px' }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <div ref={ref} className={`manifesto-stanza ${visible ? 'in' : ''}`}>
      <div className="stanza-glyph">{glyph}</div>
      <ScrambleText html={html} active={visible} className="stanza-text" />
    </div>
  );
}

const STANZAS = [
  {
    glyph: "✦",
    html: "<strong>Veld</strong> is an <em>interdisciplinary crossroads</em> of doers, thinkers, and creators, <scramble>a community of communities</scramble>, where authentic conversation, deep vulnerability, and play make space for the kind of exchange that isn&rsquo;t possible elsewhere."
  },
  {
    glyph: "✺",
    html: "It is where you come to debate big ideas, pull yourself out of daily monotone, and <scramble>rediscover the truth of who you really are</scramble>. A place to learn and unlearn, to find your magic through meaningful connection, and to flow with serendipity, synchronicity, and surprise."
  },
  {
    glyph: "◈",
    html: "Veld is a <scramble>place out of time</scramble>, where magic blends with reality and every form of life arrives as its true self. Expression, co-creation, trust, embodiment, community: not words, but <em>ways of living</em>."
  },
  {
    glyph: "✷",
    html: "Veld is the place we <scramble>dreamed of as children</scramble>. The hope we hold for the world. The place every human being would want to come back to."
  },
];

function Manifesto() {
  return (
    <section className="manifesto-flow" id="what">
      <div className="manifesto-opening">
        <span className="eyebrow">— What is Veld —</span>
        <h2 className="manifesto-title">
          <InkReveal text="A community of communities." charDelay={30} />
        </h2>
      </div>

      {STANZAS.map((s, i) => (
        <ManifestoStanza key={i} glyph={s.glyph} html={s.html} />
      ))}

      <div className="ornament" aria-hidden="true">
        <span className="line"></span>
        <span className="glyph">✦</span>
        <span className="line"></span>
      </div>
    </section>
  );
}

/* ---------- WHY THIS EXISTS — founder voice ---------- */
function WhyExists() {
  const ref = useRef(null);
  const bgRef = useParallax(0.16);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) { setVisible(true); io.disconnect(); } },
      { threshold: 0.3 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <section className="why-exists why-exists-bg" id="why" ref={ref}>
      <div ref={bgRef} className="why-bg" style={{ backgroundImage: 'url("images/castle-sideview.jpg")' }}></div>
      <div className="why-scrim"></div>
      <div className="wrap">
        <div className={`inner place-body ${visible ? 'in' : ''}`}>
          <span className="eyebrow">— Why this exists —</span>
          <p>
            The most alive I&rsquo;ve ever felt at any event wasn&rsquo;t in the programme.
            It was the conversation at <em>1am</em>, the walk the next morning, the moment
            someone said something honest and the whole room shifted.
          </p>
          <p>
            So we stopped trying to recreate the official part.
            <em> We just built the part that matters.</em>
          </p>
          <p>
            Five days. A castle. People who show up as themselves: not their CV,
            not their company, not their title. Eighty humans, present and undefended,
            in one of the most extraordinary buildings in Europe.
          </p>
          <div className="why-rule"></div>
          <p className="why-coda">That&rsquo;s it. That&rsquo;s the whole idea.</p>
          <div className="why-sig">— The conveners</div>
        </div>
      </div>
    </section>
  );
}

/* ---------- THREE MOVEMENTS ---------- */
const MOVEMENTS = [
  {
    glyph: "✶",
    num: "01",
    titleA: "Shed the title.",
    titleB: "Be human first.",
    body: "You will not be your CEO badge, your VC fund, your TEDx talk, your accolade. For five days the room treats you as you are, not as what you have done, and you may discover that you prefer it that way."
  },
  {
    glyph: "✺",
    num: "02",
    titleA: "Unlearn what",
    titleB: "no longer serves.",
    body: "Vulnerability is the entry fee. Bring the question you have been avoiding. Bring the part of yourself the work calendar has been quiet about. Walk out lighter than you walked in."
  },
  {
    glyph: "❋",
    num: "03",
    titleA: "Co-create the",
    titleB: "experience.",
    body: "Veld is not plug-and-play. You are not a guest at a festival. You are part of how the week happens, through conversations you start, rooms you fill, gifts you offer. Nothing is scheduled; everything is possible. What you bring is the programme; what you leave with is up to you."
  },
];

function Movements() {
  return (
    <section className="movements section-pad-sm">
      <div className="wrap">
        <div style={{ textAlign: 'center' }}>
          <span className="eyebrow" style={{ display: 'block', marginBottom: '14px' }}>— Three quiet promises —</span>
          <h2 style={{
            fontFamily: "'Cormorant', serif",
            fontWeight: 300,
            fontStyle: 'italic',
            fontSize: 'clamp(36px, 4.5vw, 56px)',
            lineHeight: 1.1,
            margin: 0,
            color: 'var(--ink)',
            letterSpacing: '-0.015em'
          }}>
            What might happen if you say <span style={{ color: 'var(--accent)', fontStyle: 'normal', fontWeight: 500 }}>yes</span>.
          </h2>
        </div>

        <div className="movements-grid">
          {MOVEMENTS.map((m, i) => (
            <div className="movement" key={i}>
              <div className="num">{m.num} / 03</div>
              <div className="glyph">{m.glyph}</div>
              <h3>
                {m.titleA}<br/>
                <span className="accent">{m.titleB}</span>
              </h3>
              <p>{m.body}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- KINGS HALL FEATURED BANNER ---------- */
function KingsHall() {
  const sectionRef = useRef(null);
  const zoomRef = useRef(null);
  const copyRef = useRef(null);
  const [revealed, setRevealed] = useState(false);
  useEffect(() => {
    let raf = 0;
    const update = () => {
      raf = 0;
      const section = sectionRef.current;
      const zoom = zoomRef.current;
      const copy = copyRef.current;
      if (!section || !zoom) return;
      const rect = section.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = rect.height + vh;
      const passed = vh - rect.top;
      const p = Math.max(0, Math.min(1, passed / total));
      const scale = 1 + p * 0.75;
      const brightness = 0.78 - p * 0.30;
      zoom.style.transform = `scale(${scale.toFixed(3)})`;
      zoom.style.filter = `sepia(10%) contrast(1.08) brightness(${brightness.toFixed(3)}) saturate(0.92)`;
      if (copy) {
        const o = Math.max(0, Math.min(1, (0.85 - p) / 0.35));
        copy.style.opacity = o.toFixed(3);
        copy.style.transform = `translateY(${(p * -24).toFixed(1)}px)`;
      }
      // Reveal once the section first enters substantially
      if (!revealed && p > 0.05) setRevealed(true);
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll, { passive: true });
    update();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [revealed]);

  return (
    <section ref={sectionRef} className="kings-hall" aria-label="The Knight’s Hall, Zamek Czocha">
      <div ref={zoomRef} className="zoom-layer"></div>
      <div className="grain"></div>
      <div className="veil"></div>
      <div ref={copyRef} className={`copy ${revealed ? 'revealed' : ''}`}>
        <div className="kh-tag">
          <span className="kh-rule-h"></span>
          <span className="kh-eyebrow">The Knight&rsquo;s Hall</span>
          <span className="kh-rule-h"></span>
        </div>

        <div className="kh-preface">Here.</div>

        <ScrambleText
          as="h2"
          className="kh-heading"
          html="This is the room where the week <scramble>begins</scramble>."
          active={revealed}
        />

        <div className="kh-rule-v"></div>

        <div className="kh-caption">
          <span>Eight metres of ceiling.</span>
          <span className="dot">·</span>
          <span>Candlelight.</span>
          <span className="dot">·</span>
          <span>Silence between sentences.</span>
        </div>
      </div>
    </section>
  );
}

/* ---------- THE PLACE — full-bleed, scramble heading, prominent ---------- */
function Place() {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) { setVisible(true); io.disconnect(); } },
      { threshold: 0.35 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <section className={`place ${visible ? 'in' : ''}`} id="place" ref={ref}>
      <div className="place-image">
        <div className="bg-img"></div>
      </div>
      <div className="place-overlay"></div>
      <div className="place-year" aria-hidden="true">1247</div>
      <div className="place-copy">
        <div className="wrap">
          <div className="inner">
            <span className="eyebrow">— A place out of time —</span>
            <h2 className="place-heading">
              <span className="place-name">Zamek Czocha,</span>
              <ScrambleText
                as="span"
                className="place-subhead"
                html="where the centuries <scramble>lose count</scramble>."
                active={visible}
              />
            </h2>
            <div className={`place-body ${visible ? 'in' : ''}`}>
              <p className="place-lead">
                A 13th-century castle on a forest lake. Towers, vaults, a great hall,
                a library that smells of leather, hidden passageways the staff still
                find by accident.
              </p>
              <div className="place-rule"></div>
              <p className="place-coda">
                <span className="coda-lead">For five days, <em>it is yours.</em></span>
                Eighty souls and eight centuries of stone, agreeing to keep each
                other&rsquo;s secrets.
              </p>
              <div className="place-meta">
                <span>51.0322° N · 15.3018° E</span>
                <span className="sep">·</span>
                <span>Founded 1247</span>
                <span className="sep">·</span>
                <span>Lake Leśniańskie</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- THE ARC ---------- */
const DAYS = [
  {
    n: "I",
    label: "Day One",
    title: "Arrival, & the crossing of the threshold.",
    time: "Oct 4 · Sun",
    body: "The world outside falls away at the gate. The first hours are unhurried: recognised faces, new ones, the warmth of a fire someone lit before you arrived. By the time the first meal ends, the room knows its own shape.",
    img: "images/day1-firepit.png",
  },
  {
    n: "II",
    label: "Day Two",
    title: "Immersion, & the first real conversation.",
    time: "Oct 5 · Mon",
    body: "The castle starts to feel like a body; you learn its rooms, its rhythms, its quiet corners. Conversations that began as small talk shift somewhere more honest by the third or fourth crossing. By dusk, most of the titles people walked in with have quietly come off.",
    img: "images/castle-hall.jpg",
  },
  {
    n: "III",
    label: "Day Three",
    title: "Deepening, & the version of you nobody&rsquo;s met.",
    time: "Oct 6 · Tue",
    body: "You meet a version of yourself you weren’t planning to bring. Quiet rooms invite admission you didn’t prepare for. Loud rooms invite confession you didn’t prepare for either. After dark, the castle becomes another castle entirely — and so does the room you came to find.",
    img: "images/concept-party.png",
  },
  {
    n: "IV",
    label: "Day Four",
    title: "Culmination, & what we built without noticing.",
    time: "Oct 7 · Wed",
    body: "What has been quietly assembling all week becomes visible. Small intentions find one another and grow into bigger ones. The last feast is long, candlelit, unhurried, and the conversations at this one tend to outlive the rest of the year.",
    img: "images/concept-workshop.png",
  },
  {
    n: "V",
    label: "Day Five",
    title: "Return, & the promise carried home.",
    time: "Oct 8 · Thu",
    body: "Departure is deliberate, not abrupt. Promises made in the dark are repeated in daylight, so they survive the journey home. The gate opens once more, and you walk back into a world that has not changed, but you have.",
    img: "images/castle-bridge.jpg",
  },
];

function DayRow({ d, i }) {
  const ref = useRef(null);
  const imgRef = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) { setVisible(true); io.disconnect(); } },
      { threshold: 0.3 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  // Scroll-driven scale on bg + content collapse as you scroll past
  useEffect(() => {
    let raf = 0;
    const update = () => {
      raf = 0;
      const section = ref.current;
      const img = imgRef.current;
      if (!section || !img) return;
      const rect = section.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = rect.height + vh;
      const passed = vh - rect.top;
      const p = Math.max(0, Math.min(1, passed / total));
      // Bg zooms in slightly as you pass through (enters at scale 1.05, leaves at 1.2)
      const scale = 1.05 + p * 0.15;
      img.style.transform = `scale(${scale.toFixed(3)})`;
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    window.addEventListener('scroll', onScroll, { passive: true });
    update();
    return () => {
      window.removeEventListener('scroll', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
  return (
    <section ref={ref} className={`day-cinema ${visible ? 'in' : ''}`} data-day={i}>
      <div className="day-cinema-bg">
        <div ref={imgRef} className="day-cinema-img" style={{ backgroundImage: `url("${d.img}")` }}></div>
      </div>
      <div className="day-cinema-veil"></div>
      <div className="day-cinema-grain"></div>
      <div className="day-cinema-content">
        <div className="day-cinema-numeral" aria-hidden="true">{d.n}</div>
        <div className="day-cinema-marker">
          <span>{d.label}</span>
          <span className="sep">·</span>
          <span className="time">{d.time}</span>
        </div>
        <h3 className="day-cinema-title" dangerouslySetInnerHTML={{ __html: d.title }} />
        <p className="day-cinema-body">{d.body}</p>
      </div>
    </section>
  );
}

function Arc() {
  const preludeRef = useRef(null);
  const [preludeVisible, setPreludeVisible] = useState(false);
  useEffect(() => {
    const el = preludeRef.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) { setPreludeVisible(true); io.disconnect(); } },
      { threshold: 0.4 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <section className="arc-section" id="arc">
      <div className="wrap">
        <div ref={preludeRef} className={`arc-prelude ${preludeVisible ? 'in' : ''}`}>
          <span className="eyebrow">— The arc —</span>
          <div className="prelude-line-1">Seven centuries of stone.</div>
          <ScrambleText
            as="div"
            className="prelude-line-2"
            html="<scramble>Five days</scramble> inside them."
            active={preludeVisible}
          />
          <p className="prelude-subline">
            What follows is not a schedule. It is the <em>shape of the week</em>
            as it has been told by everyone who has ever lived it.
          </p>
        </div>
      </div>

      <div className="arc-cinema">
        {DAYS.map((d, i) => <DayRow d={d} i={i} key={i} />)}
      </div>

      <div className="wrap">
        <div className="arc-coda">
          <span className="coda-rule"></span>
          <p className="coda-text">
            <em>And on the sixth morning,</em> the gate opens once more.
          </p>
        </div>
      </div>
    </section>
  );
}

/* ---------- WHO / WHO NOT ---------- */
const YES = [
  "You&rsquo;re tired of events where everyone is performing a version of themselves.",
  "You have a hunger for conversation that actually goes somewhere.",
  "You&rsquo;re ready to be surprised, by others and by yourself.",
  "You want to connect with people across disciplines, not just your own world.",
  "You&rsquo;re willing to contribute, not just consume.",
];

const NO = [
  "You need a stage or a logo to feel present.",
  "You&rsquo;re looking to pitch, sell, or network transactionally.",
  "You want a programme handed to you.",
  "You&rsquo;re not ready to be a little vulnerable.",
];

function Who() {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) { setVisible(true); io.disconnect(); } },
      { threshold: 0.25 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  // Interleave the two streams so they meet at the seam like a zipper.
  const ZIP = [];
  const max = Math.max(YES.length, NO.length);
  for (let i = 0; i < max; i++) {
    if (YES[i]) ZIP.push({ side: 'come', text: YES[i] });
    if (NO[i])  ZIP.push({ side: 'avoid', text: NO[i] });
  }

  return (
    <section className="who-bg section-pad" id="who">
      <div className="wrap">
        <div className="arc-header">
          <span className="eyebrow">— Who this is for —</span>
          <h2>Veld is not <span className="accent">for everyone.</span></h2>
          <p className="threshold-sub">
            Two streams meet at the gate. Read down the seam, and feel which side
            you fall on.
          </p>
        </div>

        <div ref={ref} className={`threshold ${visible ? 'in' : ''}`}>
          <div className="threshold-seam" aria-hidden="true"></div>
          <div className="threshold-heads">
            <span className="th-head come"><span className="legend" aria-hidden="true"></span>Come, if&hellip;</span>
            <span className="th-head avoid"><span className="legend" aria-hidden="true"></span>Don&rsquo;t, if&hellip;</span>
          </div>

          {ZIP.map((z, i) => (
            <div
              className={`zip ${z.side}`}
              key={i}
              style={{ '--d': `${(i * 0.07).toFixed(2)}s` }}
            >
              <span className="connector" aria-hidden="true"></span>
              <span
                className="node"
                aria-hidden="true"
                style={{ animationDelay: `${((i * 0.6) % 2.8).toFixed(2)}s` }}
              ></span>
              <span className="tooth" dangerouslySetInnerHTML={{ __html: z.text }} />
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- WHAT IT WILL MEAN ---------- */
const MEAN = [
  { num: "i.", text: "<em>Refresh, reset, reboot.</em> Time for your soul to catch up with your week." },
  { num: "ii.", text: "Freedom from everyday norms, and from the identity you wear at work." },
  { num: "iii.", text: "Authentic relating, open communication, <em>new vibrations</em>." },
  { num: "iv.", text: "Joy of being. The kind that arrives unannounced and stays a while." },
  { num: "v.", text: "Trust in yourself, and in humanity, restored a few degrees." },
  { num: "vi.", text: "A return to <em>innate humanness</em>, in an increasingly digital world." },
  { num: "vii.", text: "Care and responsibility, for yourself, for others, for the shared space." },
  { num: "viii.", text: "The need to replicate this more often. <em>Hosts welcome.</em>" },
];

/* ---------- MEAN — scrollytelling: each item big, then settles into the grid in place ---------- */
function Mean() {
  const wrapRef = useRef(null);
  const stageRefs = useRef([]);
  const settledRefs = useRef([]);
  const dotsRef = useRef([]);

  useEffect(() => {
    const wrap = wrapRef.current;
    if (!wrap) return;
    let raf = 0;
    let lastActive = -1;
    const N = MEAN.length;

    const update = () => {
      raf = 0;
      const rect = wrap.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = rect.height - vh;
      if (total <= 0) return;
      const scrolled = Math.max(0, -rect.top);
      const p = Math.max(0, Math.min(1, scrolled / total));
      const stagePos = p * N;

      const active = Math.min(N - 1, Math.max(0, Math.floor(stagePos)));

      for (let i = 0; i < N; i++) {
        const ip = stagePos - i;                  // 0 = stage start, 1 = stage end
        // STAGE (big, centered): fades in at -0.12, full at 0.05, holds until 0.7, fades out by 0.95
        let stageOp = 0;
        if (ip > -0.12 && ip < 0.05)       stageOp = (ip + 0.12) / 0.17;
        else if (ip >= 0.05 && ip <= 0.7)  stageOp = 1;
        else if (ip > 0.7 && ip < 0.95)    stageOp = 1 - (ip - 0.7) / 0.25;
        // SETTLED (small, grid position): starts appearing as stage fades, full by 1.1
        let settledOp = 0;
        if (ip > 0.7 && ip < 1.1)          settledOp = (ip - 0.7) / 0.4;
        else if (ip >= 1.1)                settledOp = 1;
        // settled also slides up a few px as it appears
        const ty = settledOp < 1 ? (1 - settledOp) * 12 : 0;

        const sEl = stageRefs.current[i];
        if (sEl) {
          sEl.style.opacity = stageOp.toFixed(3);
          // Subtle scale: big at peak, slight downsize at end of its window
          const scale = ip < 0.05 ? (0.97 + (1 - Math.max(0, (0.05 - ip) / 0.17)) * 0.03)
                       : ip > 0.7 ? (1 - Math.min(1, (ip - 0.7) / 0.25) * 0.12)
                       : 1;
          sEl.style.transform = `scale(${scale.toFixed(3)})`;
        }
        const gEl = settledRefs.current[i];
        if (gEl) {
          gEl.style.opacity = settledOp.toFixed(3);
          gEl.style.transform = `translateY(${ty.toFixed(1)}px)`;
        }
      }

      if (active !== lastActive) {
        lastActive = active;
        for (let i = 0; i < N; i++) {
          const d = dotsRef.current[i];
          if (!d) continue;
          d.className = `dot ${i === active ? 'active' : (i < active ? 'passed' : '')}`;
        }
      }
    };

    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll, { passive: true });
    update();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <section ref={wrapRef} className="mean-scrolly">
      <div className="mean-sticky">

        <div className="mean-header">
          <span className="eyebrow">— What it may mean —</span>
          <h2 className="mean-title">What it might mean for you <em>to attend</em>.</h2>
        </div>

        <div className="stage-zone">
          {MEAN.map((m, i) => (
            <div key={i} ref={el => stageRefs.current[i] = el} className="mean-stage">
              <span className="stage-num">{m.num}</span>
              <span className="stage-text" dangerouslySetInnerHTML={{ __html: m.text }} />
            </div>
          ))}
        </div>

        <div className="grid-zone">
          <div className="settled-grid">
            {MEAN.map((m, i) => (
              <div key={i} ref={el => settledRefs.current[i] = el} className="settled-item">
                <span className="settled-num">{m.num}</span>
                <span className="settled-text" dangerouslySetInnerHTML={{ __html: m.text }} />
              </div>
            ))}
          </div>
        </div>

        <div className="stage-dots">
          {MEAN.map((_, i) => (
            <span key={i} ref={el => dotsRef.current[i] = el} className="dot" />
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- APPLY (full form, POSTs to /api/apply — same shape as the original) ---------- */
const AFFILIATIONS = [
  // Codes preserved so the existing Resend handler keeps working.
  // Grouped visually — no group is privileged over another.
  { group: 'Lists you might recognise',
    items: [
      { value: 'forbes',     label: 'Forbes 30 Under 30' },
      { value: 'tedx',       label: 'TEDx / TED speaker' },
      { value: 'shapers',    label: 'WEF Global Shaper' },
      { value: 'davos',      label: 'Davos / WEF speaker' },
      { value: 'burningman', label: 'Burning Man / Edge / Summit community' },
    ],
  },
  { group: 'Other ways in (just as welcome)',
    items: [
      { value: 'vouch',     label: 'Someone in the room is vouching for me' },
      { value: 'artist',    label: 'Artist, creator, or storyteller' },
      { value: 'builder',   label: 'Builder, founder, or operator' },
      { value: 'activist',  label: 'Activist, organiser, or civic builder' },
      { value: 'thinker',   label: 'Researcher, healer, teacher, or therapist' },
      { value: 'other',     label: 'Many worlds — let me explain in the letter' },
    ],
  },
];

const TIER_OPTIONS = [
  { value: 'classic',   label: 'A bed in a shared chamber — €1,800 to €2,800' },
  { value: 'deluxe',    label: 'A larger shared room (2 per room) — €2,500 to €3,900' },
  { value: 'suite',     label: 'A named themed room — €4,750' },
  { value: 'artist',    label: 'Castle Artist Exchange — €500 (practising artists)' },
  { value: 'patronage', label: 'I want to support / contribute beyond a seat' },
  { value: 'undecided', label: 'I would like to talk it through first' },
];

function Apply() {
  const [affiliation, setAffiliation] = useState('');
  const [tier, setTier] = useState('');
  const [message, setMessage] = useState('');
  const [carrying, setCarrying] = useState('');
  const [state, setState] = useState('idle'); // idle | loading | success | error
  const [errorMsg, setErrorMsg] = useState('');

  async function handleSubmit(e) {
    e.preventDefault();
    setState('loading');
    setErrorMsg('');

    const formData = new FormData(e.currentTarget);
    const data = Object.fromEntries(formData);

    try {
      const resp = await fetch('/api/apply', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      const payload = await resp.json().catch(() => null);
      if (!resp.ok) {
        throw new Error(payload?.error?.message ?? payload?.message ?? 'Submission failed. Please try again.');
      }
      setTimeout(() => setState('success'), 900);
    } catch (err) {
      console.error('Apply failed:', err);
      setState('error');
      setErrorMsg(err.message || 'Something went wrong. Please try again or email us directly.');
    }
  }

  if (state === 'success') {
    return (
      <section className="apply" id="apply">
        <div className="form-success">
          <div className="sealed-letter" aria-hidden="true">
            <div className="envelope">
              <div className="fold"></div>
              <div className="seal">V</div>
            </div>
          </div>
          <h3>Your letter has been received.</h3>
          <p>
            A human will read it and write back within seven days, usually sooner.
            Until then, look at the moon now and then; we will be reading.
          </p>
        </div>
      </section>
    );
  }

  return (
    <section className="apply" id="apply">
      <div className="wrap apply-header">
        <span className="eyebrow">— Request a seat —</span>
        <h2>Tell us <span className="accent">who you are.</span></h2>
        <p>
          Veld is open to anyone who is honest about why they want to be here.
          Selection happens through the <em>letter</em>, not the résumé.
          A human reads every one.
        </p>
      </div>

      <form onSubmit={handleSubmit} className="apply-form-wrap">

        <div className="form-field">
          <label htmlFor="name">What should we call you?</label>
          <input type="text" id="name" name="name" required placeholder="Your name" />
        </div>

        <div className="form-field">
          <label htmlFor="email">Where shall we write back?</label>
          <input type="email" id="email" name="email" required placeholder="email@example.com" />
        </div>

        <div className="form-field">
          <label htmlFor="whatsapp">Where can we reach you quickly?</label>
          <input type="tel" id="whatsapp" name="whatsapp" required placeholder="+1 234 567 8900" />
          <div className="hint">WhatsApp or Signal, for last-minute logistics, not marketing.</div>
        </div>

        <div className="form-field">
          <label htmlFor="linkedin">A place where we can read about your journey</label>
          <input type="url" id="linkedin" name="linkedin" required placeholder="https://linkedin.com/in/..." />
          <div className="hint">LinkedIn, personal site, anywhere we can begin to know you.</div>
        </div>

        <div className="form-field">
          <label htmlFor="affiliation">What world do you come from?</label>
          <select
            id="affiliation"
            name="affiliation"
            required
            value={affiliation}
            onChange={(e) => setAffiliation(e.target.value)}
          >
            <option value="" disabled>Choose what fits best…</option>
            {AFFILIATIONS.map((group, gi) => (
              <optgroup key={gi} label={group.group}>
                {group.items.map(a => (
                  <option key={a.value} value={a.value}>{a.label}</option>
                ))}
              </optgroup>
            ))}
          </select>
          <div className="hint">A starting point only. None of these are gates; your letter is what we read.</div>
        </div>

        {(affiliation === 'vouch') && (
          <div className="form-field">
            <label htmlFor="voucher">Who is vouching for you?</label>
            <input
              type="text"
              id="voucher"
              name="voucher"
              required
              placeholder="Their full name"
            />
            <div className="hint">The person already in the room who knows you well.</div>
          </div>
        )}

        <div className="form-field">
          <label htmlFor="tier">Which room are you imagining?</label>
          <select
            id="tier"
            name="tier"
            required
            value={tier}
            onChange={(e) => setTier(e.target.value)}
          >
            <option value="" disabled>Choose a room…</option>
            {TIER_OPTIONS.map(t => (
              <option key={t.value} value={t.value}>{t.label}</option>
            ))}
          </select>
          <div className="hint">All rooms are shared. Three private rooms exist as a paid add-on.</div>
        </div>

        <div className="form-field">
          <label htmlFor="sleepSchedule">How do your nights tend to run?</label>
          <select id="sleepSchedule" name="sleepSchedule" required defaultValue="">
            <option value="" disabled>Choose what fits best…</option>
            <option value="early">Early to bed, early to rise</option>
            <option value="middle">Somewhere in between</option>
            <option value="owl">A night owl — I come alive late</option>
          </select>
          <div className="hint">Since rooms are shared, this helps us pair you with compatible roommates.</div>
        </div>

        <div className="form-field">
          <label htmlFor="roomWith">Travelling with someone you&rsquo;d like to room with?</label>
          <input type="text" id="roomWith" name="roomWith" placeholder="Their name — optional" />
          <div className="hint">We&rsquo;ll do our best to place you together. Both of you must be accepted.</div>
        </div>

        <div className="form-check">
          <label>
            <input type="checkbox" name="singleRoomWaitlist" />
            <span>
              Add me to the waitlist for a <strong>single room</strong> (+&euro;2,800,
              subject to availability). I understand most travellers share.
            </span>
          </label>
        </div>

        <div className="form-field">
          <label htmlFor="arrival">How are you planning to arrive?</label>
          <input type="text" id="arrival" name="arrival" placeholder="Flying from… / arrival city or airport — if you know yet" />
          <div className="hint">Berlin (≈2.5 hrs), Wrocław (≈1.5 hrs), and Prague (≈3 hrs) are the usual gateways. Flight details help us plan transport — rough is fine.</div>
        </div>

        <div className="form-check">
          <label>
            <input type="checkbox" name="extraNight" />
            <span>
              I&rsquo;d like the <strong>pre-event night (Oct 3)</strong> and the evening
              bus, so I arrive rested. Add me — exact cost confirmed on acceptance.
            </span>
          </label>
        </div>

        <div className="form-check">
          <label>
            <input type="checkbox" name="visaLetter" />
            <span>
              I&rsquo;ll need a <strong>visa invitation letter</strong> to travel. Please
              prepare one if I&rsquo;m accepted.
            </span>
          </label>
        </div>

        <div className="form-field">
          <label htmlFor="message">Tell us, in your own words, what draws you here.</label>
          <textarea
            id="message"
            name="message"
            rows={5}
            maxLength={500}
            placeholder="Not your résumé. The thing you&rsquo;re actually looking for."
            value={message}
            onChange={(e) => setMessage(e.target.value)}
          />
          <div className="char-count">{message.length} / 500</div>
        </div>

        <div className="form-field">
          <label htmlFor="carrying">What are you carrying that you&rsquo;d like to put down for five days?</label>
          <textarea
            id="carrying"
            name="carrying"
            rows={4}
            maxLength={500}
            placeholder="The weight you&rsquo;d set down at the gate."
            value={carrying}
            onChange={(e) => setCarrying(e.target.value)}
          />
          <div className="char-count">{carrying.length} / 500</div>
        </div>

        <div className="form-fit">
          <div className="title">Two small commitments</div>
          <label>
            <input type="checkbox" name="confirmAudienceFit" required />
            <span>
              I understand Veld is co-created. I am showing up as a participant,
              not a spectator.
            </span>
          </label>
          <label>
            <input type="checkbox" name="confirmParticipationMode" required />
            <span>
              I commit to bringing my full self, and to leaving the badge in the room.
            </span>
          </label>
        </div>

        {state === 'error' && (
          <div className="form-error">{errorMsg || 'The letter could not be sent. Try again, or write to baditaflorin@gmail.com directly.'}</div>
        )}

        <MagneticCTA strength={0.18} radius={80}>
          <button type="submit" className="form-submit" disabled={state === 'loading'}>
            {state === 'loading' ? 'Sending your letter…' : 'Send your letter'}
          </button>
        </MagneticCTA>

        <div className="apply-footnote">
          Eighty seats · The gate opens Oct 4, 2026 · No payment until you&rsquo;re accepted
        </div>
      </form>
    </section>
  );
}

/* ---------- FOOTER ---------- */
function useVersion() {
  const [v, setV] = useState(null);
  useEffect(() => {
    fetch('/version.json')
      .then(r => r.json())
      .then(setV)
      .catch(() => {});
  }, []);
  return v;
}

function Footer() {
  const v = useVersion();
  return (
    <footer>
      <div className="wrap">
        <p className="footer-closing">
          <span className="fc-rule"></span>
          The <em>Castle</em> is Temporary.<br/>
          The <em>Alliance</em> is Permanent.
        </p>
        <div className="mark">Veld</div>
        <div className="lines">
          <span>A community of communities</span>
          <span>·</span>
          <span>Zamek Czocha</span>
          <span>·</span>
          <span>Oct 4 — 8, 2026</span>
        </div>
        <FooterOrnament />
        <div className="sig">— Welcome home —</div>
        {v && (
          <div className="version-line">
            <a
              href={`https://github.com/baditaflorin/veld_global/commit/${v.sha}`}
              target="_blank"
              rel="noopener noreferrer"
              title={`Built ${v.built}`}
            >
              {v.sha?.slice(0, 8)}
            </a>
            <span>·</span>
            <span>{v.version}</span>
            <span>·</span>
            <span>{v.built?.slice(0, 10)}</span>
          </div>
        )}
      </div>
    </footer>
  );
}

/* ---------- ROUTER ---------- */
const PAGE_KEYS = ['home', 'castle', 'week', 'rooms', 'who', 'faq', 'apply'];
function usePage() {
  const read = () => {
    const h = (location.hash || '').replace(/^#\/?/, '').trim();
    return PAGE_KEYS.includes(h) ? h : 'home';
  };
  const [page, setPage] = useState(read());
  useEffect(() => {
    const on = () => { setPage(read()); window.scrollTo(0, 0); };
    window.addEventListener('hashchange', on);
    return () => window.removeEventListener('hashchange', on);
  }, []);
  return page;
}

/* ---------- PAGE HEADER + BREADCRUMB ---------- */
function PageHeader({ kicker, title, sub }) {
  return (
    <header className="page-header">
      <div className="wrap">
        <nav className="crumb" aria-label="Breadcrumb">
          <a href="#/">Veld</a>
          <span className="crumb-sep">/</span>
          <span className="crumb-here">{title}</span>
        </nav>
        <span className="eyebrow">{kicker}</span>
        <h1 className="page-title">{title}</h1>
        {sub && <p className="page-sub">{sub}</p>}
      </div>
    </header>
  );
}

/* ---------- KEY FACTS (home) — date / place / who / price, unmissable ---------- */
const FACTS = [
  { k: 'When',  v: 'Oct 4–8, 2026',  d: 'Five days, four nights' },
  { k: 'Where', v: 'Zamek Czocha',   d: 'A 13th-century castle on a forest lake' },
  { k: 'Who',   v: 'Eighty souls',   d: 'Builders, creators, thinkers' },
  { k: 'From',  v: '€1,800',         d: 'All nights, meals & rituals. Rooms shared; single by waitlist.' },
];
function KeyFacts() {
  return (
    <section className="keyfacts">
      <div className="wrap">
        <div className="keyfacts-grid">
          {FACTS.map((f, i) => (
            <div className="fact" key={i}>
              <div className="fact-k">{f.k}</div>
              <div className="fact-v">{f.v}</div>
              <div className="fact-d">{f.d}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- PRICING (home) — the decision pivot, stated plainly ---------- */
const INCLUDED = [
  'Four nights inside the castle',
  'Every meal — the long feasts and the coffee at dawn',
  'The entire five-day arc: every ritual, fire-circle and salon',
  'A bed in a shared chamber with like-hearted company',
];
function Pricing() {
  return (
    <section className="pricing">
      <div className="wrap pricing-inner">
        <div className="pricing-lead">
          <span className="eyebrow">— What it costs —</span>
          <div className="pricing-figure">
            <span className="from">From</span>
            <span className="amt">€1,800</span>
          </div>
          <p className="pricing-range">
            up to <strong>€4,750</strong> for a named room.
            <em> One price. Everything inside the gate is included.</em>
          </p>
        </div>

        <div className="pricing-detail">
          <div className="incl-k">What that covers</div>
          <ul className="incl">
            {INCLUDED.map((x, i) => <li key={i}>{x}</li>)}
          </ul>
          <p className="excl">
            Not included: your flight, and any extra nights. Every room is shared;
            a single room is <strong>+€2,800</strong>, by waitlist.
          </p>
          <p className="self-select">
            Veld is invitation-only; the price is not the door, it&rsquo;s simply
            an honest filter. We say it plainly so the money is never the awkward part.
            If the number gives you pause, this may not be your year, and that&rsquo;s
            an honest answer too.
          </p>
          <a href="#/rooms" className="pricing-cta">See the full breakdown →</a>
        </div>
      </div>
    </section>
  );
}

/* ---------- EXPLORE CARDS (home) — the "pages" ---------- */
const EXPLORE = [
  { key: 'castle', n: '01', title: 'The Castle',      desc: 'A 13th-century castle on a forest lake. Towers, vaults, a great hall, a library of leather.', img: 'images/castle-facade.jpg' },
  { key: 'week',   n: '02', title: 'The Week',        desc: 'Five days, four nights. The shape of the arc, evening by evening.', img: 'images/castle-hall.jpg' },
  { key: 'rooms',  n: '03', title: 'Rooms & Pricing', desc: 'Where you sleep, what it costs, and exactly what is included.', img: 'images/room-knight.jpg' },
  { key: 'who',    n: '04', title: 'Who Comes',       desc: 'Eighty undefended humans — and the small circle holding the room.', img: 'images/castle-vault.jpg' },
  { key: 'faq',    n: '05', title: 'FAQ & Logistics', desc: 'Arrival, what is included, the pact, and the practical things.', img: 'images/castle-library.jpg' },
  { key: 'apply',  n: '06', title: 'Apply',           desc: 'Tell us who you are. A human reads every letter.', img: 'images/castle-gate.jpg' },
];
function ExploreCards() {
  return (
    <section className="explore">
      <div className="wrap">
        <div className="explore-head">
          <span className="eyebrow">— Look closer —</span>
          <h2 className="explore-title">Step through, one room at a time.</h2>
        </div>
        <div className="explore-grid">
          {EXPLORE.map((c) => (
            <a className="ex-card" href={`#/${c.key}`} key={c.key}>
              <div className="ex-img" style={{ backgroundImage: `url("${c.img}")` }}></div>
              <div className="ex-body">
                <span className="ex-num">{c.n}</span>
                <h3 className="ex-name">{c.title}</h3>
                <p className="ex-desc">{c.desc}</p>
                <span className="ex-go">Enter →</span>
              </div>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- CLOSING CTA (foot of every page) ---------- */
function ClosingCTA() {
  return (
    <section className="closing-cta">
      <div className="wrap">
        <span className="eyebrow">— The gate opens Oct 4, 2026 —</span>
        <h2 className="cc-title">Eighty seats. <em>One letter</em> stands between you and the castle.</h2>
        <p className="cc-sub">From €1,800, all-inclusive. No payment until you&rsquo;re accepted.</p>
        <MagneticCTA strength={0.3} radius={100}>
          <a href="#/apply" className="hero-cta"><span>Request a seat</span></a>
        </MagneticCTA>
      </div>
    </section>
  );
}

/* ---------- GALLERY (castle page) ---------- */
const GALLERY = [
  { img: 'images/gx-forest.jpg',      cap: 'The castle in the forest' },
  { img: 'images/gx-aerial.jpg',      cap: 'The bridge, from the tower' },
  { img: 'images/gx-gate.jpg',        cap: 'The gate' },
  { img: 'images/gx-keep.jpg',        cap: 'The keep' },
  { img: 'images/gx-loggia.jpg',      cap: 'From the loggia' },
  { img: 'images/gx-cc19.jpg',        cap: 'The inner courtyard' },
  { img: 'images/gx-marelbu4.jpg',    cap: 'The outer ward' },
  { img: 'images/gx-vaulthall.jpg',   cap: 'The vaulted hall' },
  { img: 'images/gx-knightshall.jpg', cap: 'The hall of portraits' },
  { img: 'images/gx-hearth.jpg',      cap: 'The great hearth' },
  { img: 'images/gx-library.jpg',     cap: 'The library' },
  { img: 'images/gx-bedchamber.jpg',  cap: 'The chambers' },
  { img: 'images/gx-cellar.jpg',      cap: 'The cellar' },
  { img: 'images/gx-armoury.jpg',     cap: 'The armoury' },
  { img: 'images/gx-passage.jpg',     cap: 'The dungeons' },
  { img: 'images/gx-czocha058.jpg',   cap: 'The turret wing' },
  { img: 'images/gx-map1714.jpg',     cap: 'The castle, mapped in 1714' },
  { img: 'images/gx-1960.jpg',        cap: 'The old stair' },
];
function Gallery() {
  const [open, setOpen] = useState(-1);
  const close = () => setOpen(-1);
  const prev = () => setOpen((i) => (i - 1 + GALLERY.length) % GALLERY.length);
  const next = () => setOpen((i) => (i + 1) % GALLERY.length);

  useEffect(() => {
    if (open < 0) return;
    const onKey = (e) => {
      if (e.key === 'Escape') close();
      else if (e.key === 'ArrowLeft') prev();
      else if (e.key === 'ArrowRight') next();
    };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [open]);

  // editorial rhythm — a few feature tiles break the grid
  const FEATURE = { 0: 'big', 5: 'wide', 8: 'tall', 11: 'wide', 16: 'wide' };

  return (
    <section className="gallery section-pad-sm">
      <div className="wrap">
        <div className="sect-header header" style={{ textAlign: 'center', marginBottom: '48px' }}>
          <span className="eyebrow">— The gallery —</span>
          <h2 style={{ fontFamily: "'Cormorant', serif", fontWeight: 300, fontStyle: 'italic', fontSize: 'clamp(32px,4.5vw,52px)', margin: '12px 0 0', color: 'var(--ink)', letterSpacing: '-0.015em' }}>
            Eight centuries of stone.
          </h2>
        </div>
        <div className="gallery-grid">
          {GALLERY.map((g, i) => (
            <figure
              className={`g-item ${FEATURE[i] || ''}`}
              key={i}
              onClick={() => setOpen(i)}
              role="button"
              tabIndex={0}
              onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(i); } }}
            >
              <div className="g-img" style={{ backgroundImage: `url("${g.img}")` }}></div>
              <span className="g-expand" aria-hidden="true">
                <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M9 4H5a1 1 0 0 0-1 1v4M15 4h4a1 1 0 0 1 1 1v4M9 20H5a1 1 0 0 1-1-1v-4M15 20h4a1 1 0 0 0 1-1v-4" />
                </svg>
              </span>
              <figcaption>{g.cap}<span className="g-num">{String(i + 1).padStart(2, '0')}</span></figcaption>
            </figure>
          ))}
        </div>
        <p style={{ textAlign: 'center', marginTop: '22px', fontFamily: "'JetBrains Mono', monospace", fontSize: '10px', letterSpacing: '0.12em', color: 'var(--ink-mute)', textTransform: 'uppercase' }}>
          Photographs of Zamek Czocha · Wikimedia Commons, CC BY-SA · incl. Barbara Maliszewska
        </p>
      </div>

      {open >= 0 && (
        <div className="lightbox" onClick={close}>
          <button className="lb-close" onClick={close} aria-label="Close">✕</button>
          <button className="lb-nav lb-prev" onClick={(e) => { e.stopPropagation(); prev(); }} aria-label="Previous">‹</button>
          <figure className="lb-stage" onClick={(e) => e.stopPropagation()}>
            <div className="lb-img" key={open} style={{ backgroundImage: `url("${GALLERY[open].img}")` }}></div>
            <figcaption className="lb-cap">
              <span className="lb-cap-text">{GALLERY[open].cap}</span>
              <span className="lb-counter">{String(open + 1).padStart(2, '0')} <span className="lb-sl">/</span> {String(GALLERY.length).padStart(2, '0')}</span>
            </figcaption>
          </figure>
          <button className="lb-nav lb-next" onClick={(e) => { e.stopPropagation(); next(); }} aria-label="Next">›</button>
        </div>
      )}
    </section>
  );
}

/* ---------- LOGISTICS (faq page) ---------- */
const LOGI = [
  { k: 'Getting there', v: 'Berlin, Prague, or Wrocław', d: 'Berlin is the easiest international hub (≈2.5 hrs by road). Wrocław is closest (≈1.5 hrs); Prague is ≈3 hrs. A shared bus brings the room in together from each.' },
  { k: 'Arriving early', v: 'The night before', d: 'Travelling far? Add the pre-event night (Oct 3) and take the evening bus from Berlin, so you arrive rested rather than rushed. Opt in on your application.' },
  { k: 'What’s included', v: 'Everything but your flight', d: 'All four nights, every meal, every ritual and fire-circle. One price, no upsells inside the gate.' },
  { k: 'Where you sleep', v: 'Shared chambers', d: 'Every room is shared — that is part of how the week works. A few single rooms exist by waitlist, at extra cost.' },
  { k: 'Visas & letters', v: 'We’ll write the letter', d: 'Travelling on a visa that needs an invitation letter? Tell us once you’re accepted and we’ll provide the documentation you need.' },
  { k: 'The pact', v: 'What is shared here, stays here', d: 'No cameras, no press, no logos, no audience. The week belongs to the people inside it.' },
];
function Logistics() {
  return (
    <section className="logistics section-pad-sm">
      <div className="wrap">
        <div className="logi-grid">
          {LOGI.map((l, i) => (
            <div className="logi" key={i}>
              <div className="logi-k">{l.k}</div>
              <div className="logi-v">{l.v}</div>
              <p className="logi-d">{l.d}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- PAGES ---------- */
function HomePage({ hookIndex }) {
  return (
    <>
      <Hero hookIndex={hookIndex} />
      <WhyExists />
      <Movements />
      <KeyFacts />
      <Pricing />
      <ExploreCards />
      <ClosingCTA />
      <HiddenFirefly top="14%" left="6%" whisper="You found one. Most people don’t look up." />
      <HiddenFirefly top="74%" left="93%" whisper="Some of the fireflies are imaginary. Some are not." />
    </>
  );
}
function CastlePage() {
  return (
    <>
      <PageHeader kicker="— A place out of time —" title="The Castle"
        sub="Zamek Czocha — a 13th-century castle on a forest lake. For five days, it is yours." />
      <Place />
      <KingsHall />
      <Gallery />
      <ClosingCTA />
    </>
  );
}
function WeekPage() {
  return (
    <>
      <PageHeader kicker="— Five days, four nights —" title="The Week"
        sub="Not a schedule. The shape of the week, as everyone who has lived it tells it." />
      <Arc />
      <Fireflies />
      <Chatham />
      <Mean />
      <ClosingCTA />
    </>
  );
}
function RoomsPage() {
  return (
    <>
      <PageHeader kicker="— Where you’ll sleep —" title="Rooms & Pricing"
        sub="Every room is shared; that is part of how the magic works. Single occupancy is by waitlist, at extra cost." />
      <Tiers />
      <NamedRooms />
      <Patronage />
      <ClosingCTA />
    </>
  );
}
function WhoPage() {
  return (
    <>
      <PageHeader kicker="— The room —" title="Who Comes"
        sub="Eighty builders, creators, thinkers and visionaries — and the small circle who hold the room." />
      <Who />
      <Travelers />
      <Architects />
      <Crew />
      <ClosingCTA />
    </>
  );
}
function FaqPage() {
  return (
    <>
      <PageHeader kicker="— Practical things —" title="FAQ & Logistics"
        sub="Arrival, what’s included, the pact, and the questions we’re asked most." />
      <Logistics />
      <FAQ />
      <ClosingCTA />
    </>
  );
}
function ApplyPage() {
  return (
    <>
      <PageHeader kicker="— Request a seat —" title="Apply"
        sub="Selection happens through the letter, not the résumé. A human reads every one." />
      <Apply />
    </>
  );
}

/* ---------- TWEAKS ---------- */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "dusk",
  "wordmark": "serif",
  "hookIndex": 0
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const page = usePage();

  useEffect(() => { document.body.dataset.palette = t.palette; }, [t.palette]);
  useEffect(() => { document.body.dataset.wordmark = t.wordmark; }, [t.wordmark]);

  return (
    <>
      <DoorsOpen />
      <FireflyCursor />
      <IdleWhisper />
      <Nav page={page} />
      <main key={page}>
        {page === 'home'   && <HomePage hookIndex={t.hookIndex} />}
        {page === 'castle' && <CastlePage />}
        {page === 'week'   && <WeekPage />}
        {page === 'rooms'  && <RoomsPage />}
        {page === 'who'    && <WhoPage />}
        {page === 'faq'    && <FaqPage />}
        {page === 'apply'  && <ApplyPage />}
      </main>
      <Footer />

      <TweaksPanel title="Tweaks">
        <TweakSection title="Palette">
          <TweakColor
            label="Mood"
            value={t.palette}
            onChange={(v) => setTweak('palette', v)}
            options={[
              ['#0e0a1c', '#d8a86a', '#f1e7d4'],
              ['#160e08', '#e08a4a', '#f3e5cd'],
              ['#0e161a', '#c9b387', '#ecf2f2'],
            ]}
          />
          <TweakRadio
            label=" "
            value={t.palette}
            onChange={(v) => setTweak('palette', v)}
            options={[
              { value: 'dusk',   label: 'Dusk'   },
              { value: 'embers', label: 'Embers' },
              { value: 'mist',   label: 'Mist'   },
            ]}
          />
        </TweakSection>

        <TweakSection title="Wordmark style">
          <TweakRadio
            value={t.wordmark}
            onChange={(v) => setTweak('wordmark', v)}
            options={[
              { value: 'serif',    label: 'Serif'   },
              { value: 'engraved', label: 'Stone'   },
              { value: 'hand',     label: 'Italic'  },
            ]}
          />
        </TweakSection>

        <TweakSection title="Hero hook">
          <TweakSelect
            label="Variation"
            value={t.hookIndex}
            onChange={(v) => setTweak('hookIndex', Number(v))}
            options={[
              { value: 0, label: 'Spent years becoming someone' },
              { value: 1, label: 'Builders, creators, thinkers' },
              { value: 2, label: 'Every field, every background' },
              { value: 3, label: 'Stop being your title' },
            ]}
          />
        </TweakSection>
      </TweaksPanel>
    </>
  );
}

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