// sw-pagekit.jsx — Draws every inner page from the copy in pages-copy.js

const GROUNDS = {
  black: { bg: C.black, text: C.white, body: C.grey4, label: C.ember, line: C.line },
  slate: { bg: C.slate, text: C.white, body: C.grey4, label: C.ember, line: C.line },
  parchment: { bg: C.parchment, text: C.ink, body: C.ink, label: C.emberDeep, line: 'rgba(28,30,26,0.14)' },
};
const ground = (s) => GROUNDS[s.bg] || GROUNDS.black;

function Section({ s, children, style = {} }) {
  const g = ground(s);
  return (
    <section id={s.id} className="pad" style={{ background: g.bg, padding: '88px 0', ...(s.underlay ? { position: 'relative', overflow: 'hidden' } : {}), ...style }}>
      {s.underlay === 'wheel' && <div className={s.underlayRight ? 'underlay-right' : ''} style={{ position: 'absolute', top: '50%', left: s.underlayRight ? '74%' : '50%', transform: 'translate(-50%,-50%)', pointerEvents: 'none' }}><Wheel size={860} opacity={0.13} color={g.text} /></div>}
      {s.underlay === 'triangles' && <div style={{ position: 'absolute', top: '50%', right: '-3%', transform: 'translateY(-50%)', opacity: 0.13, pointerEvents: 'none' }}><Logomark size={560} color={g.text} weight={1.6} /></div>}
      <div style={{ ...maxW, position: 'relative' }}>{children}</div>
    </section>
  );
}

function Heading({ s, children, center }) {
  const g = ground(s);
  return (
    <div style={{ marginBottom: 32, textAlign: center ? 'center' : 'left' }}>
      {s.label && <SectionLabel color={s.accent === 'lichen' ? C.greenForest : g.label}>{s.label}</SectionLabel>}
      {children && <SectionHeading color={g.text}>{children}</SectionHeading>}
    </div>
  );
}

function Paras({ s, paras, size = 18 }) {
  const g = ground(s);
  return paras.map((p, i) => (
    <p key={i} style={{ fontSize: size, lineHeight: 1.7, color: g.body, fontFamily: T.font, marginBottom: 18, maxWidth: '31em' }}>{p}</p>
  ));
}

// TidyCal's booking calendar. Its script swaps a div for the calendar, and only scans the page on first load,
// so the div is added by hand here and started with TidyCal.init when you come back to the page.
// Opens a TidyCal booking page in a new tab (used instead of the embed since 6 Nov 2026)
const openTidyCal = (path) => window.open(`https://tidycal.com/${path}`, '_blank', 'noopener');

// TidyCal's booking calendar in a modal window, opened from a button (Readings and the free call, 6 Nov 2026).
// Midnight blue with a gold edge; Esc, the close button or a click outside closes it; the page behind doesn't scroll.
// TidyCal's own branding stays inside, untouched.
function TidyCalModal({ path, label, title }) {
  const [open, setOpen] = React.useState(false);
  const closeBtn = React.useRef(null);
  const opener = React.useRef(null);
  const scroller = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    const key = (e) => { if (e.key === 'Escape') setOpen(false); };
    window.addEventListener('keydown', key);
    // When a time is picked, TidyCal asks to scroll to the top of its calendar, and its script scrolls the page
    // behind instead. Bring the window back to its top, where the booking summary is, and keep the page still (9 Nov 2026)
    const keepY = window.scrollY;
    const msg = (e) => {
      if (!/^https:\/\/([a-z0-9-]+\.)?tidycal\.com$/.test(e.origin) || typeof e.data !== 'string' || !e.data.includes(':scrollToOffset')) return;
      scroller.current && scroller.current.scrollTo({ top: 0, behavior: 'smooth' });
      setTimeout(() => window.scrollTo(0, keepY), 0);
    };
    window.addEventListener('message', msg);
    setTimeout(() => closeBtn.current && closeBtn.current.focus(), 30);
    return () => { document.body.style.overflow = prev; window.removeEventListener('keydown', key); window.removeEventListener('message', msg); opener.current && opener.current.focus(); };
  }, [open]);
  return (
    <>
      <span ref={opener} tabIndex={-1} style={{ outline: 'none' }}><PillBtn onClick={() => setOpen(true)}>{label}</PillBtn></span>
      {/* Drawn at the end of the page (a portal), so a card's hover lift or a section's layering can't trap it */}
      {open && ReactDOM.createPortal(
        <div onClick={() => setOpen(false)} className="cal-modal" style={{ position: 'fixed', inset: 0, zIndex: 500, background: 'rgba(5,8,13,0.82)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 'clamp(0px, 3vw, 40px)' }}>
          <div role="dialog" aria-modal="true" aria-label={title || label} onClick={e => e.stopPropagation()} className="cal-panel"
            style={{ position: 'relative', width: 'min(760px, 100%)', maxHeight: '92vh', display: 'flex', flexDirection: 'column', background: C.black, border: `1px solid ${C.lineStrong}`, borderTop: `2px solid ${C.ember}`, boxShadow: '0 30px 80px rgba(0,0,0,0.6)' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, padding: '14px 16px 14px clamp(20px,3vw,28px)', borderBottom: `1px solid ${C.line}` }}>
              <span style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 22, color: C.white }}>{title || label}</span>
              <button ref={closeBtn} onClick={() => setOpen(false)} aria-label="Close" style={{ minWidth: 44, minHeight: 44, background: 'transparent', border: `1px solid ${C.lineStrong}`, borderRadius: 2, color: C.white, fontSize: 22, lineHeight: 1, cursor: 'pointer' }}>×</button>
            </div>
            <div ref={scroller} style={{ overflowY: 'auto', WebkitOverflowScrolling: 'touch', padding: 'clamp(8px,1.6vw,16px)' }}>
              <TidyCalEmbed path={path} />
            </div>
          </div>
        </div>,
        document.body
      )}
    </>
  );
}

function TidyCalEmbed({ path }) {
  const host = React.useRef(null);
  React.useEffect(() => {
    const box = host.current;
    const el = document.createElement('div');
    el.className = 'tidycal-embed';
    el.dataset.path = path;
    box.appendChild(el);
    if (window.TidyCal) {
      window.TidyCal.init(el);
    } else if (!document.getElementById('tidycal-script')) {
      const script = document.createElement('script');
      script.id = 'tidycal-script';
      script.src = 'https://asset-tidycal.b-cdn.net/js/embed.js';
      script.async = true;
      document.body.appendChild(script);
    }
    return () => { box.innerHTML = ''; };
  }, [path]);
  return <div ref={host} />;
}

function Quote({ q, who, s }) {
  const g = ground(s || {});
  return (
    <figure style={{ borderLeft: `2px solid ${C.ember}`, padding: '4px 0 4px 24px', margin: '28px 0 0' }}>
      <p style={{ fontFamily: T.serif, fontStyle: q.length > 150 ? 'normal' : T.serifStyle, fontSize: 19, lineHeight: 1.6, color: g.text, marginBottom: 10 }}>“{q}”</p>
      <figcaption style={{ fontSize: 12, letterSpacing: '0.18em', textTransform: 'uppercase', color: s && s.bg === 'parchment' ? C.driftwood : C.grey3, fontFamily: T.font }}>{who}</figcaption>
    </figure>
  );
}

function Buttons({ buttons, navigate, align = 'center' }) {
  return (
    <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap', justifyContent: align }}>
      {buttons.map(b => {
        const act = () => { if (b.href) { window.location.href = b.href; } else { navigate(b.to); window.scrollTo(0, 0); } };
        return b.outline ? <OutlineBtn key={b.label} onClick={act}>{b.label}</OutlineBtn> : <PillBtn key={b.label} onClick={act}>{b.label}</PillBtn>;
      })}
    </div>
  );
}

// ── Section types ─────────────────────────────────────────────
const KIT = {
  hero: ({ s }) => (
    <section className={s.deep ? 'page-hero wash-deep' : 'page-hero'} style={{ position: 'relative', overflow: 'hidden', background: C.black, paddingTop: 68, minHeight: 'min(66vh, 620px)', display: 'flex', alignItems: 'flex-end' }}>
      <div className="hero-photo" style={{ position: 'absolute', inset: 0 }}>
        <img src={s.image} alt="" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: s.pos || 'center', filter: 'grayscale(0.15) brightness(0.92)' }} />
        <div style={{ position: 'absolute', inset: 0, background: C.blueGrade, mixBlendMode: 'multiply', opacity: 0.25 }} />
        <div className="hero-fade" style={{ position: 'absolute', inset: 0, background: 'linear-gradient(90deg, rgba(14,21,32,0.92) 0%, rgba(14,21,32,0.8) 45%, rgba(14,21,32,0.15) 75%, rgba(14,21,32,0) 100%), linear-gradient(to top, rgba(14,21,32,0.7) 0%, rgba(14,21,32,0) 40%)' }} />
      </div>
      {s.wheel && (
        <div className="hero-wheel" style={{ position: 'absolute', top: '52%', right: '-6%', transform: 'translateY(-50%)', pointerEvents: 'none' }}>
          <Wheel size={560} opacity={0.3} />
        </div>
      )}
      <div className="hero-words" style={{ ...maxW, position: 'relative', zIndex: 2, width: '100%', padding: '64px clamp(24px, 8vw, 120px) clamp(48px, 8vh, 88px)' }}>
        <div style={{ maxWidth: 640 }}>
          <SectionLabel color={s.accent === 'lichen' ? C.greenForest : undefined}>{s.label}</SectionLabel>
          <h1 className="fade-up-2" style={{ fontSize: 'clamp(40px, 5.4vw, 80px)', letterSpacing: '-0.01em', color: C.white, lineHeight: 1.05, marginBottom: 22, fontFamily: T.font, fontWeight: 300 }}>{s.title}</h1>
          <p className="fade-up-3" style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 'clamp(19px, 1.8vw, 24px)', lineHeight: 1.45, color: 'rgba(234,231,223,0.88)' }}>{s.sub}</p>
        </div>
      </div>
    </section>
  ),

  facts: ({ s }) => (
    <section style={{ background: C.slate, borderBottom: `1px solid ${C.line}` }}>
      <div className="g4" style={{ ...maxW, display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)' }}>
        {s.items.map(([k, v]) => (
          <div key={k} style={{ padding: '26px 16px 26px 0' }}>
            <span style={{ display: 'block', fontSize: 11, letterSpacing: '0.2em', textTransform: 'uppercase', color: C.ember, fontFamily: T.font, marginBottom: 6 }}>{k}</span>
            <span style={{ fontFamily: T.font, fontWeight: 300, fontSize: 20, color: C.white }}>{v}</span>
          </div>
        ))}
      </div>
    </section>
  ),

  text: ({ s }) => {
    const g = ground(s);
    const words = (
      <div>
        <Heading s={s} center={s.center}>{s.heading}</Heading>
        <Paras s={s} paras={s.paras} />
        {s.quote && <Quote {...s.quote} s={s} />}
        {s.draft && <DraftNote color={s.bg === 'parchment' ? C.emberDeep : undefined}>{s.draft}</DraftNote>}
      </div>
    );
    // Photo running to the page edge, with no stripe of padding around it
    if (s.bleed) return (
      <section className="g2 text-first edge-photo" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', background: g.bg }}>
        <GreenPhoto warm src={s.image} style={{ height: 'clamp(360px, 46vw, 680px)' }} imgStyle={{ objectPosition: s.imagePos || 'center', '--pos-m': s.imagePosMobile || s.imagePos || 'center' }} />
        <div style={{ alignSelf: 'center', maxWidth: 660, padding: 'clamp(48px,6vw,96px) clamp(24px,6vw,96px)' }}>{words}</div>
      </section>
    );
    // Words and portrait over a landscape running the full width
    if (s.backdrop) return (
      <section className="pad" style={{ position: 'relative', overflow: 'hidden', background: g.bg, padding: 'clamp(88px,9vw,136px) 0' }}>
        <div style={{ position: 'absolute', inset: 0, backgroundImage: `url('${s.backdrop}')`, backgroundSize: 'cover', backgroundPosition: 'center 55%' }} />
        <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(90deg, rgba(14,21,32,0.94) 0%, rgba(14,21,32,0.86) 55%, rgba(14,21,32,0.45) 100%)' }} />
        <div style={{ ...maxW, position: 'relative' }}>
          <div className="g2" style={{ display: 'grid', gridTemplateColumns: '7fr 5fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'center' }}>
            {words}
            <GreenPhoto warm src={s.image} style={{ height: 'clamp(320px, 38vw, 520px)' }} imgStyle={{ objectPosition: s.imagePos || 'center', '--pos-m': s.imagePosMobile || s.imagePos || 'center' }} />
          </div>
        </div>
      </section>
    );
    if (s.manifesto) return (
      <Section s={s}>
        <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'clamp(40px,6vw,96px)', alignItems: 'start' }}>
          {words}
          <div><Heading s={s}>Manifesto</Heading><ManifestoList light={s.bg === 'parchment'} /></div>
        </div>
      </Section>
    );
    return (
      <Section s={s}>
        {s.image ? (
          <div className={s.flip ? 'g2 text-first' : 'g2'} style={{ display: 'grid', gridTemplateColumns: s.flip ? '5fr 7fr' : '7fr 5fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'center' }}>
            {!s.flip && words}
            <GreenPhoto warm src={s.image} style={{ height: 'clamp(320px, 38vw, 520px)' }} imgStyle={{ objectPosition: s.imagePos || 'center', '--pos-m': s.imagePosMobile || s.imagePos || 'center' }} />
            {s.flip && words}
          </div>
        ) : <div style={{ maxWidth: s.center ? 660 : 760, margin: s.center ? '0 auto' : 0 }}>{words}</div>}
      </Section>
    );
  },

  story: ({ s }) => (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '5fr 7fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'start' }}>
        <GreenPhoto warm src={s.image} style={{ height: 'clamp(380px, 46vw, 620px)' }} imgStyle={{ objectPosition: 'center 30%' }} />
        <div>
          <p style={{ fontFamily: T.serif, fontStyle: 'normal', fontSize: 'clamp(22px, 2.2vw, 28px)', lineHeight: 1.5, color: C.ink, marginBottom: 24 }}>{s.paras[0]}</p>
          <Paras s={s} paras={s.paras.slice(1)} />
        </div>
      </div>
    </Section>
  ),

  fit: ({ s }) => {
    const col = (title, items, mark, colour) => (
      <div style={{ background: C.black, border: `1px solid ${C.line}`, padding: 'clamp(24px,3vw,40px)' }}>
        <h3 style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 23, color: colour, marginBottom: 18 }}>{title}</h3>
        <ul style={{ listStyle: 'none' }}>
          {items.map(i => (
            <li key={i} style={{ display: 'flex', gap: 14, fontSize: 17, lineHeight: 1.55, color: C.grey4, fontFamily: T.font, padding: '10px 0', borderTop: `1px solid ${C.line}` }}>
              <span style={{ color: colour, flexShrink: 0 }}>{mark}</span>{i}
            </li>
          ))}
        </ul>
      </div>
    );
    const inner = (
      <>
        <Heading s={s} center>{s.heading}</Heading>
        <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
          {col(s.yesTitle, s.yes, '✦', C.ember)}
          {col(s.noTitle, s.no, '·', C.grey3)}
        </div>
      </>
    );
    if (s.backdrop) return (
      <section className="pad" style={{ position: 'relative', overflow: 'hidden', background: C.black, padding: 'clamp(88px,9vw,136px) 0' }}>
        <div style={{ position: 'absolute', inset: 0, backgroundImage: `url('${s.backdrop}')`, backgroundSize: 'cover', backgroundPosition: 'center 55%' }} />
        <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(14,21,32,0.72) 0%, rgba(14,21,32,0.38) 35%, rgba(14,21,32,0.38) 75%, rgba(14,21,32,0.62) 100%)' }} />
        <div style={{ ...maxW, position: 'relative' }}>{inner}</div>
      </section>
    );
    return <Section s={s}>{inner}</Section>;
  },

  // Drawn like the home page's NatureBand: the photo on a taller layer inside the strip, so the photo drift reaches it (9 Nov 2026)
  band: ({ s }) => (
    <div data-band="" role="img" aria-label="" style={{ position: 'relative', overflow: 'hidden', height: 'clamp(200px, 24vw, 340px)', background: C.black }}>
      <div style={{ position: 'absolute', top: '-25%', bottom: '-25%', left: 0, right: 0, backgroundImage: `url('${s.image}')`, backgroundSize: 'cover', backgroundPosition: s.pos || 'center' }} />
    </div>
  ),

  cards: ({ s, navigate }) => (
    <Section s={s}>
      {s.heading && <Heading s={s}>{s.heading}</Heading>}
      <div className="g3" style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(s.items.length, 3)}, 1fr)`, gap: 20 }}>
        {s.items.map(w => (
          <WayCard key={w.to} navigate={navigate} way={{ id: w.to, tag: w.tag, title: w.title, body: w.body, cta: w.cta, image: w.image, imagePos: w.imagePos, zoom: w.zoom, zoomOrigin: w.zoomOrigin, accent: w.accent === 'ember' ? C.ember : C.greenForest }} />
        ))}
      </div>
    </Section>
  ),

  doors: ({ s }) => (
    <Section s={s}>
      <div style={{ maxWidth: 760 }}>
        <Heading s={s}>{s.heading}</Heading>
        <Paras s={s} paras={[s.intro]} />
      </div>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20, margin: '20px 0 32px' }}>
        {s.doors.map(d => (
          <div key={d.title} style={{ background: C.slate, border: `1px solid ${C.line}`, padding: 'clamp(24px,3vw,40px)' }}>
            <SectionLabel>{d.label}</SectionLabel>
            <h3 style={{ fontFamily: T.font, fontWeight: 300, fontSize: 26, color: C.white, marginBottom: 14 }}>{d.title}</h3>
            <BodyText style={{ fontSize: 17 }}>{d.body}</BodyText>
          </div>
        ))}
      </div>
      <div style={{ maxWidth: 760 }}><Paras s={s} paras={[s.outro]} /></div>
    </Section>
  ),

  gates: ({ s }) => (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '5fr 7fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'center', marginBottom: 48 }}>
        <div>
          <Heading s={s}>{s.heading}</Heading>
          <Paras s={s} paras={[s.intro]} />
          <div style={{ borderTop: `1px solid ${C.line}`, paddingTop: 18, marginTop: 8 }}>
            <SectionLabel color={C.grey3}>{s.before.when}</SectionLabel>
            <h3 style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 22, color: C.white, marginBottom: 8 }}>{s.before.name}</h3>
            <BodyText style={{ fontSize: 16 }}>{s.before.body}</BodyText>
          </div>
        </div>
        <div className="wheel-fit" style={{ width: '100%', maxWidth: 420, margin: '0 auto' }}><Wheel size={420} opacity={0.55} /></div>
      </div>
      <div className="g4" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 14 }}>
        {s.gates.map(gt => (
          <div key={gt.name} style={{ background: C.black, border: `1px solid ${C.line}`, borderTop: `2px solid ${C.ember}`, padding: 22 }}>
            <span style={{ display: 'block', fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: C.grey3, fontFamily: T.font, marginBottom: 8 }}>{gt.n} · {gt.when}</span>
            <h3 style={{ fontFamily: T.font, fontWeight: 300, fontSize: 24, color: C.white, marginBottom: 2 }}>{gt.name}</h3>
            <span style={{ display: 'block', fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 18, color: C.ember, marginBottom: 12 }}>{gt.theme}</span>
            <p style={{ fontSize: 15, lineHeight: 1.6, color: C.grey4, fontFamily: T.font }}>{gt.body}</p>
          </div>
        ))}
      </div>
    </Section>
  ),

  movements: ({ s }) => (
    <Section s={s}>
      <div style={{ maxWidth: 760 }}>
        <Heading s={s}>{s.heading}</Heading>
        <Paras s={s} paras={[s.intro]} />
      </div>
      <div className="g3" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16, margin: '24px 0 40px' }}>
        {s.items.map(m => (
          <div key={m.n} style={{ background: C.ember, padding: 'clamp(24px,2.6vw,36px)' }}>
            <span style={{ fontFamily: T.serif, fontSize: 40, color: C.ink, display: 'block', marginBottom: 6 }}>{m.n}</span>
            <h3 style={{ fontFamily: T.font, fontWeight: 400, fontSize: 22, color: C.ink, marginBottom: 10 }}>{m.title}</h3>
            <p style={{ fontSize: 16, lineHeight: 1.6, color: C.ink, fontFamily: T.font }}>{m.body}</p>
          </div>
        ))}
      </div>
      <DetailRows rows={s.details} />
    </Section>
  ),

  details: ({ s }) => (
    <Section s={s}>
      <Heading s={s}>{s.heading}</Heading>
      <DetailRows rows={s.rows} />
      {s.credit && <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 18, color: C.grey4, marginTop: 28 }}>{s.credit}</p>}
    </Section>
  ),

  list: ({ s }) => (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '7fr 5fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'start' }}>
        <div>
          <Heading s={s}>{s.heading}</Heading>
          <ul style={{ listStyle: 'none' }}>
            {s.items.map(i => (
              <li key={i} style={{ display: 'flex', gap: 14, fontSize: 18, lineHeight: 1.55, color: C.grey4, fontFamily: T.font, padding: '12px 0', borderTop: `1px solid ${C.line}` }}>
                <span style={{ color: C.ember }}>✦</span>{i}
              </li>
            ))}
          </ul>
        </div>
        {s.quote && <div style={{ paddingTop: 72 }}><Quote {...s.quote} s={s} /></div>}
      </div>
    </Section>
  ),

  four: ({ s }) => (
    <Section s={s}>
      <div style={{ maxWidth: 760 }}>
        <Heading s={s}>{s.heading}</Heading>
        <Paras s={s} paras={[s.intro]} />
      </div>
      <div className="g4" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 14, marginTop: 20 }}>
        {s.items.map(it => (
          <div key={it.title} className="belief" style={{ background: C.ember, padding: 28, display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center', gap: 14 }}>
            <AlmanacShape kind={it.kind} color={C.ink} size={64} />
            <h3 style={{ fontFamily: T.font, fontWeight: 400, fontSize: 22, color: C.ink }}>{it.title}</h3>
            <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 18, lineHeight: 1.45, color: C.ink }}>{it.body}</p>
          </div>
        ))}
      </div>
    </Section>
  ),

  covenant: ({ s }) => {
    const li = (i) => <li key={i} style={{ fontSize: 17, lineHeight: 1.55, color: C.ink, fontFamily: T.font, padding: '9px 0', borderTop: '1px solid rgba(28,30,26,0.14)' }}>{i}</li>;
    return (
      <Section s={s}>
        <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'clamp(32px,5vw,80px)' }}>
          <div>
            <Heading s={s}>{s.heading}</Heading>
            <Paras s={s} paras={[s.intro]} />
            <h3 style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 21, color: C.emberDeep, margin: '16px 0 8px' }}>To the men at the table</h3>
            <ul style={{ listStyle: 'none' }}>{s.toMen.map(li)}</ul>
            <h3 style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 21, color: C.emberDeep, margin: '22px 0 8px' }}>For yourself</h3>
            <ul style={{ listStyle: 'none', marginBottom: 18 }}>{s.forSelf.map(li)}</ul>
            <Paras s={s} paras={[s.outro]} size={16} />
          </div>
          <div style={{ background: C.black, padding: 'clamp(24px,3vw,40px)', alignSelf: 'start' }}>
            <SectionLabel>What it asks of you</SectionLabel>
            <ul style={{ listStyle: 'none' }}>
              {s.asks.map(a => <li key={a} style={{ fontSize: 17, lineHeight: 1.55, color: C.grey4, fontFamily: T.font, padding: '12px 0', borderTop: `1px solid ${C.line}` }}>{a}</li>)}
            </ul>
          </div>
        </div>
      </Section>
    );
  },

  changes: ({ s }) => (
    <Section s={s}>
      <div style={{ maxWidth: 760 }}>
        <Heading s={s}>{s.heading}</Heading>
        <Paras s={s} paras={[s.intro]} />
      </div>
      <div className="g3" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16, marginTop: 20 }}>
        {s.items.map(([soul, plain]) => (
          <div key={soul} style={{ background: C.slate, border: `1px solid ${C.line}`, padding: 26 }}>
            <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 20, lineHeight: 1.45, color: C.white, marginBottom: 14 }}>{soul}</p>
            <span style={{ display: 'block', fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: C.ember, fontFamily: T.font, marginBottom: 6 }}>In practice</span>
            <p style={{ fontSize: 15, lineHeight: 1.6, color: C.grey4, fontFamily: T.font }}>{plain}</p>
          </div>
        ))}
      </div>
    </Section>
  ),

  who: ({ s, navigate }) => (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'start' }}>
        <div>
          <Heading s={s}>{s.heading}</Heading>
          <Paras s={s} paras={s.paras} />
          {s.buttons && <Buttons buttons={s.buttons} navigate={navigate} align="flex-start" />}
        </div>
        <div>{s.quotes.map(q => <Quote key={q.who} {...q} s={s} />)}</div>
      </div>
    </Section>
  ),

  holder: ({ s }) => (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '5fr 7fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'center' }}>
        <GreenPhoto warm src={s.image} style={{ height: 'clamp(320px, 38vw, 500px)' }} imgStyle={{ objectPosition: 'center 30%' }} />
        <div>
          <Heading s={s}>{s.heading}</Heading>
          <Paras s={s} paras={s.paras} />
        </div>
      </div>
    </Section>
  ),

  price: ({ s }) => (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: s.steps ? '5fr 7fr' : '1fr', gap: 'clamp(32px,5vw,80px)', alignItems: 'start' }}>
        <div>
          <SectionLabel color={C.emberDeep}>{s.label}</SectionLabel>
          <span style={{ display: 'block', fontFamily: T.font, fontWeight: 300, fontSize: 'clamp(48px, 6vw, 80px)', color: C.ink, lineHeight: 1, marginBottom: 12 }}>{s.amount}</span>
          <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 21, color: C.ink, marginBottom: 16 }}>{s.note}</p>
          <p style={{ fontSize: 16, lineHeight: 1.6, color: C.driftwood, fontFamily: T.font, marginBottom: 16, maxWidth: '32em' }}>{s.extra}</p>
          {s.draft && <DraftNote color={C.emberDeep}>{s.draft}</DraftNote>}
        </div>
        {s.steps && (
          <div>
            <SectionLabel color={C.emberDeep}>The path in</SectionLabel>
            {s.steps.map(st => (
              <div key={st.n} style={{ display: 'flex', gap: 20, padding: '16px 0', borderTop: '1px solid rgba(28,30,26,0.14)' }}>
                <span style={{ fontFamily: T.serif, fontSize: 34, color: C.emberDeep, lineHeight: 1 }}>{st.n}</span>
                <div>
                  <h3 style={{ fontFamily: T.font, fontWeight: 400, fontSize: 20, color: C.ink, marginBottom: 4 }}>{st.label}: {st.title}</h3>
                  <p style={{ fontSize: 16, lineHeight: 1.6, color: C.driftwood, fontFamily: T.font }}>{st.body}</p>
                </div>
              </div>
            ))}
            <p style={{ fontFamily: T.serif, fontStyle: 'normal', fontSize: 18, lineHeight: 1.55, color: C.ink, marginTop: 20 }}>{s.after}</p>
          </div>
        )}
      </div>
    </Section>
  ),

  credentials: ({ s }) => {
    const col = (title, items) => (
      <div>
        <span style={{ display: 'block', fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 21, color: C.ember, marginBottom: 12 }}>{title}</span>
        <ul style={{ listStyle: 'none' }}>
          {items.map(i => <li key={i} style={{ fontSize: 15, lineHeight: 1.5, color: C.grey4, fontFamily: T.font, padding: '9px 0', borderTop: `1px solid ${C.line}` }}>{i}</li>)}
        </ul>
      </div>
    );
    return (
      <Section s={s}>
        <Heading s={s}>{s.heading}</Heading>
        {s.worlds ? <BothWorldsMeet sides={['boardroom', 'grove'].map(k => ({ ...s.worlds[k], rows: s.worlds[k].rows.filter(r => !(s.drop || []).includes(r)).concat((s.extra && s.extra[k]) || []) }))} /> : (
        <div className="g3" style={{ display: 'grid', gridTemplateColumns: `repeat(${s.education ? 3 : 2}, 1fr)`, gap: 32, marginBottom: 24 }}>
          {col('The boardroom', s.boardroom)}
          {col('The grove', s.grove)}
          {s.education && col('Education', s.education)}
        </div>
        )}
        {s.draft && <DraftNote>{s.draft}</DraftNote>}
      </Section>
    );
  },

  press: () => <PressSection />,

  ways: ({ navigate }) => <WaysSection navigate={navigate} plain />,

  application: ({ s, navigate }) => <ApplicationForm s={s} navigate={navigate} />,

  videos: ({ s }) => {
    const videos = useVideos();
    return (
    <Section s={s}>
      <div style={{ maxWidth: 760, marginBottom: 32 }}><Paras s={s} paras={[s.intro]} /></div>
      <div className="g3" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
        {videos.map(v => (
          <div key={v.id}>
            <VideoPlayer video={v} />
            <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 19, color: C.white, marginTop: 12 }}>{v.title}</p>
          </div>
        ))}
      </div>
    </Section>
    );
  },

  booking: ({ s }) => {
    const g = ground(s);
    return (
      <Section s={s}>
        {s.heading && <Heading s={s}>{s.heading}</Heading>}
        {s.body && <Paras s={s} paras={[s.body]} />}
        {s.terms && <p style={{ fontSize: 18, lineHeight: 1.7, color: g.body, fontFamily: T.font, marginBottom: 18, maxWidth: '31em' }}>
          Booking means you’re agreeing to my <a href="/terms" style={{ color: g.label }}>Terms</a>. A reading missed or cancelled with less than 72 hours’ notice can’t be moved or refunded.
        </p>}
        {/* A button to the TidyCal page, not the embedded calendar: its white "Powered by" bar looked messy on the page (Krish, 6 Nov 2026) */}
        <div style={{ marginTop: 16 }}>{s.modal ? <TidyCalModal path={s.path} label={s.button || 'Book a reading'} /> : <PillBtn onClick={() => openTidyCal(s.path)}>{s.button || 'Book a reading'}</PillBtn>}</div>
      </Section>
    );
  },

  conversation: ({ s, navigate }) => <Conversation s={s} navigate={navigate} />,

  newsletter: ({ s }) => <SignUp s={s} />,

  placeholder: ({ s }) => (
    <Section s={{ bg: 'black' }}>
      <div style={{ border: `1px dashed ${C.lineStrong}`, padding: 'clamp(32px,5vw,64px)', textAlign: 'center' }}>
        <SectionHeading style={{ marginBottom: 16 }}>{s.heading}</SectionHeading>
        <BodyText style={{ margin: '0 auto 20px' }}>{s.body}</BodyText>
        <DraftNote>{s.draft}</DraftNote>
      </div>
    </Section>
  ),

  contact: ({ s, navigate }) => <ContactForm s={s} navigate={navigate} />,

  legal: ({ s }) => {
    const g = ground(s);
    const items = (list) => (
      <ul style={{ margin: '0 0 18px', paddingLeft: 22, maxWidth: '31em' }}>
        {list.map((t, i) => <li key={i} style={{ fontSize: 17, lineHeight: 1.65, color: g.body, fontFamily: T.font, marginBottom: 8 }}>{t}</li>)}
      </ul>
    );
    return (
      <Section s={s}>
        <div style={{ maxWidth: 760 }}>
          <Paras s={s} paras={s.intro} />
          {s.draft && <div style={{ marginBottom: 12 }}><DraftNote color={C.emberDeep}>{s.draft}</DraftNote></div>}
          {s.clauses.map((c, i) => (
            <div key={c.h} style={{ borderTop: `1px solid ${g.line}`, paddingTop: 28, marginTop: 28 }}>
              <h2 style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontWeight: 400, fontSize: 24, color: g.text, marginBottom: 14 }}>{i + 1}. {c.h}</h2>
              {c.items && !c.paras && items(c.items)}
              {c.paras && <Paras s={s} paras={c.paras} size={17} />}
              {c.items && c.paras && items(c.items)}
              {c.draft && <div style={{ marginBottom: 18 }}><DraftNote color={C.emberDeep}>{c.draft}</DraftNote></div>}
              {c.after && <Paras s={s} paras={[c.after]} size={17} />}
            </div>
          ))}
          <p style={{ fontSize: 13, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.driftwood, fontFamily: T.font, marginTop: 40 }}>Last updated: {s.updated}</p>
        </div>
      </Section>
    );
  },

  cta: ({ s, navigate }) => (
    <section style={{ position: 'relative', overflow: 'hidden', padding: 'clamp(88px, 10vw, 128px) 0', textAlign: 'center', background: C.black }}>
      {s.image && <>
        <div style={{ position: 'absolute', inset: 0, backgroundImage: `url('${s.image}')`, backgroundSize: 'cover', backgroundPosition: 'center' }} />
        <div style={{ position: 'absolute', inset: 0, background: 'rgba(14,21,32,0.76)' }} />
      </>}
      <div style={{ position: 'relative', ...maxW }}>
        <h2 style={{ fontSize: 'clamp(34px, 4.4vw, 60px)', color: C.white, lineHeight: 1.1, marginBottom: 20, fontFamily: T.font, fontWeight: 300 }}>{s.heading}</h2>
        <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 'clamp(19px, 2vw, 23px)', color: 'rgba(234,231,223,0.88)', marginBottom: 36, lineHeight: 1.55, maxWidth: 620, marginLeft: 'auto', marginRight: 'auto' }}>{s.body}</p>
        {s.buttons && <Buttons buttons={s.buttons} navigate={navigate} />}
        {s.form && <SignUp inline />}
        {s.draft && <div style={{ marginTop: 22 }}><DraftNote>{s.draft}</DraftNote></div>}
      </div>
    </section>
  ),
};

function DetailRows({ rows, light }) {
  const line = light ? GROUNDS.parchment.line : C.line;
  return (
    <div style={{ borderTop: `1px solid ${line}` }}>
      {rows.map(([k, v]) => (
        <div key={k} className="g2" style={{ display: 'grid', gridTemplateColumns: '220px 1fr', gap: 20, padding: '14px 0', borderBottom: `1px solid ${line}` }}>
          <span style={{ fontSize: 12, letterSpacing: '0.18em', textTransform: 'uppercase', color: light ? C.emberDeep : C.ember, fontFamily: T.font, paddingTop: 3 }}>{k}</span>
          <span style={{ fontSize: 17, lineHeight: 1.55, color: light ? C.ink : C.grey4, fontFamily: T.font }}>{v}</span>
        </div>
      ))}
    </div>
  );
}

// Two ways in, side by side. Only the one you choose opens, so neither hides the other.
function Conversation({ s, navigate }) {
  const fromHash = () => (window.location.pathname.replace(/\/+$/, '') === '/contact' ? 'write' : null);
  const [open, setOpen] = React.useState(fromHash);
  const panel = React.useRef(null);
  const choose = (id) => {
    setOpen(id);
    setTimeout(() => {
      if (panel.current) window.scrollTo({ top: panel.current.getBoundingClientRect().top + window.scrollY - 68, behavior: 'smooth' });
    }, 60);
  };
  React.useEffect(() => {
    const h = () => { const o = fromHash(); if (o) setOpen(o); };
    window.addEventListener('popstate', h);
    return () => window.removeEventListener('popstate', h);
  }, []);
  return (
    <>
      <Section s={s}>
        <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
          {s.choices.map(c => (
            <div key={c.id} style={{ background: C.black, border: `1px solid ${C.line}`, borderTop: `2px solid ${open === c.id ? C.ember : C.line}`, padding: 'clamp(24px,3vw,40px)', display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
              <h2 style={{ fontFamily: T.font, fontWeight: 300, fontSize: 'clamp(24px, 2.2vw, 30px)', color: C.white, marginBottom: 12 }}>{c.title}</h2>
              <p style={{ fontSize: 16, lineHeight: 1.6, color: C.grey4, fontFamily: T.font, marginBottom: 24, flexGrow: 1 }}>{c.body}</p>
              {c.id === 'call' && s.booking ? <TidyCalModal path={s.booking.path} label={c.cta} title={c.title} /> : <PillBtn onClick={() => choose(c.id)}>{c.cta}</PillBtn>}
            </div>
          ))}
        </div>
      </Section>
      <div ref={panel}>
        {open === 'call' && <KIT.booking s={s.booking} />}
        {open === 'write' && <ContactForm s={s.contact} navigate={navigate} />}
      </div>
    </>
  );
}

function ContactForm({ s, navigate }) {
  const light = s.bg === 'parchment';
  const { status, submit, holder } = useSiteForm('message', light ? 'light' : 'dark');
  const g = ground(s);
  const go = (id) => () => { navigate(id); window.scrollTo(0, 0); };
  const inp = { background: light ? '#fff' : C.slate, border: `1px solid ${light ? 'rgba(28,30,26,0.2)' : C.line}`, color: light ? C.ink : C.white, padding: '16px 18px', fontSize: 17, fontFamily: T.font, outline: 'none', width: '100%', boxSizing: 'border-box', borderRadius: 2 };
  return (
    <Section s={s}>
      <div className="g2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'clamp(40px,6vw,96px)', alignItems: 'start' }}>
        <div>
          {s.heading && <Heading s={s}>{s.heading}</Heading>}
          <Paras s={s} paras={s.paras} />
          <div style={{ marginTop: 12 }}><DetailRows rows={s.details} light={light} /></div>
        </div>
        <div>
          {status === 'done' ? (
            <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 22, color: g.text, lineHeight: 1.6 }}>Thank you. Your message is on its way to me.{LOCAL ? ' (In the mock-up, nothing is sent.)' : ''}</p>
          ) : (
            <form onSubmit={submit} style={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: 14 }}>
              <HoneypotField />
              <input type="text" name="name" placeholder="Your name" required autoComplete="name" className={light ? 'field-light' : undefined} style={inp} />
              <input type="email" name="email" placeholder="Your email" required autoComplete="email" className={light ? 'field-light' : undefined} style={inp} />
              <textarea name="message" placeholder="Your message" required rows={7} className={light ? 'field-light' : undefined} style={{ ...inp, resize: 'vertical' }} />
              <label style={{ display: 'flex', gap: 12, alignItems: 'flex-start', fontSize: 16, lineHeight: 1.5, color: g.body, fontFamily: T.font, cursor: 'pointer' }}>
                <input type="checkbox" name="newsletter" value="yes" style={{ width: 18, height: 18, marginTop: 3, flexShrink: 0, accentColor: light ? C.emberDeep : C.ember }} />
                <span>{s.newsletter}</span>
              </label>
              <p style={{ fontSize: 15, lineHeight: 1.6, color: light ? C.driftwood : C.grey3, fontFamily: T.font, margin: '4px 0 8px' }}>
                {s.privacy} <a href="/privacy" onClick={e => { e.preventDefault(); go('privacy')(); }} style={{ color: g.label }}>More on the Privacy page</a>.
              </p>
              <div ref={holder} />
              <div><PillBtn>{status === 'sending' ? 'Sending…' : 'Send message'}</PillBtn></div>
              {status === 'error' && <FormError color={g.body} />}
            </form>
          )}
        </div>
      </div>
    </Section>
  );
}

function ApplicationForm({ s, navigate }) {
  const { status, submit, holder } = useSiteForm('application', 'light');
  const g = ground(s);
  const inp = { background: '#fff', border: '1px solid rgba(28,30,26,0.2)', color: C.ink, padding: '16px 18px', fontSize: 17, fontFamily: T.font, borderRadius: 2, width: '100%', boxSizing: 'border-box', outline: 'none' };
  const go = (id) => () => { navigate(id); window.scrollTo(0, 0); };
  return (
    <Section s={s}>
      <div style={{ maxWidth: 720, margin: '0 auto' }}>
        {status === 'done' ? (
          <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 24, color: g.text, lineHeight: 1.6 }}>{s.thanks}{LOCAL ? ' (In the mock-up, nothing is sent.)' : ''}</p>
        ) : (
          <form onSubmit={submit} style={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: 14 }}>
            <Paras s={s} paras={s.paras} />
            <HoneypotField />
            <input type="text" name="name" placeholder="Your name" required autoComplete="name" className="field-light" style={inp} />
            <input type="email" name="email" placeholder="Your email" required autoComplete="email" className="field-light" style={inp} />
            {s.questions.map((q, i) => (
              <label key={i} style={{ display: 'block', marginTop: 28 }}>
                <span style={{ display: 'block', fontFamily: T.font, fontWeight: 300, fontSize: 'clamp(22px, 2.2vw, 28px)', lineHeight: 1.25, color: g.text, marginBottom: 10 }}>{q.q}</span>
                <span style={{ display: 'block', fontSize: 17, lineHeight: 1.6, color: g.body, fontFamily: T.font, marginBottom: 14 }}>{q.hint}</span>
                <textarea name={`q${i + 1}`} required={!q.optional} rows={5} style={{ ...inp, resize: 'vertical' }} />
              </label>
            ))}
            <p style={{ fontSize: 15, lineHeight: 1.6, color: C.driftwood, fontFamily: T.font, margin: '20px 0 8px' }}>
              {s.privacy} <a href="/privacy" onClick={e => { e.preventDefault(); go('privacy')(); }} style={{ color: g.label }}>More on the Privacy page</a>.
            </p>
            <div ref={holder} />
            <div><PillBtn>{status === 'sending' ? 'Sending…' : 'Send my application'}</PillBtn></div>
            {status === 'error' && <FormError color={g.body} />}
          </form>
        )}
      </div>
    </Section>
  );
}

function SignUp({ s, inline }) {
  const [done, setDone] = React.useState(false);
  const form = done
    ? <p style={{ fontFamily: T.serif, fontStyle: T.serifStyle, fontSize: 20, color: inline ? C.white : C.ink }}>Thank you. (In the mock-up, nothing is sent.)</p>
    : (
      <form onSubmit={e => { e.preventDefault(); setDone(true); }} style={{ display: 'flex', gap: 10, flexWrap: 'wrap', justifyContent: inline ? 'center' : 'flex-start', maxWidth: 620, margin: inline ? '0 auto' : 0 }}>
        <input type="text" required placeholder="First name" autoComplete="given-name" className={inline ? undefined : 'field-light'} style={{ background: inline ? C.slate : '#fff', border: `1px solid ${inline ? C.line : 'rgba(28,30,26,0.2)'}`, color: inline ? C.white : C.ink, padding: '14px 16px', fontSize: 16, fontFamily: T.font, outline: 'none', borderRadius: 2, minWidth: 0, flex: '1 1 150px' }} />
        <input type="email" required placeholder="Your email" className={inline ? undefined : 'field-light'} style={{ background: inline ? C.slate : '#fff', border: `1px solid ${inline ? C.line : 'rgba(28,30,26,0.2)'}`, color: inline ? C.white : C.ink, padding: '14px 16px', fontSize: 16, fontFamily: T.font, outline: 'none', borderRadius: 2, minWidth: 0, flex: '1 1 220px' }} />
        <PillBtn>Sign up</PillBtn>
      </form>
    );
  if (inline) return form;
  return (
    <Section s={s}>
      <div style={{ maxWidth: 640 }}>
        <SectionHeading color={C.ink} style={{ marginBottom: 16 }}>{s.heading}</SectionHeading>
        <p style={{ fontSize: 18, lineHeight: 1.7, color: C.ink, fontFamily: T.font, marginBottom: 24 }}>{s.body}</p>
        <NewsletterForm light />
      </div>
    </Section>
  );
}

function PageView({ id, navigate }) {
  const page = window.PAGES[id];
  return page.sections.map((s, i) => {
    const Comp = KIT[s.type];
    return Comp ? <Comp key={i} s={s} navigate={navigate} /> : null;
  });
}

Object.assign(window, { GROUNDS, KIT, PageView, DetailRows, SignUp, Quote });
