// sections.jsx — all landing sections + small UI atoms.
// Mounted to window so app.jsx can use them.

const { useState, useEffect, useRef, useMemo } = React;

/* -------------------- atoms -------------------- */

function Eyebrow({ children }) {
  return (
    <div className="eyebrow" style={{
      fontFamily: 'var(--mono)',
      fontSize: 11,
      letterSpacing: '0.14em',
      textTransform: 'uppercase',
      color: 'var(--ink-faint)'
    }}>
      {children}
    </div>);

}

function Display({ children, size = 'xl', as = 'h2', style }) {
  const Tag = as;
  const sizes = {
    xxl: 'clamp(56px, 12vw, 140px)',
    xl: 'clamp(40px, 7vw, 84px)',
    l: 'clamp(32px, 5vw, 56px)',
    m: 'clamp(26px, 3.4vw, 38px)'
  };
  return (
    <Tag className={`display display--${size}`} style={{
      fontFamily: 'var(--serif)',
      fontWeight: 400,
      fontStyle: 'normal',
      fontSize: sizes[size],
      lineHeight: 1.02,
      letterSpacing: '-0.02em',
      color: 'var(--ink)',
      textWrap: 'pretty',
      ...style
    }}>
      {children}
    </Tag>);

}

function Body({ children, soft, style }) {
  return (
    <p className="body-copy" style={{
      fontFamily: 'var(--sans)',
      fontSize: 17,
      lineHeight: 1.55,
      color: soft ? 'var(--ink-soft)' : 'var(--ink)',
      textWrap: 'pretty',
      ...style
    }}>
      {children}
    </p>);

}

function Rule({ style }) {
  return <div style={{ height: 1, background: 'var(--rule)', width: '100%', ...style }} />;
}

function Reveal({ children, delay = 0, as = 'div', style }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    // Immediate above-the-fold check — don't wait for IntersectionObserver,
    // which may not fire its initial callback in some embedded iframes.
    const checkInView = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      return r.top < vh * 0.94 && r.bottom > 0;
    };

    let done = false;
    const reveal = () => {
      if (done) return;
      done = true;
      setTimeout(() => el.classList.add('is-in'), delay);
    };

    if (checkInView()) {
      reveal();
      return;
    }

    const obs = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {reveal();obs.unobserve(el);}
      });
    }, { threshold: 0.08, rootMargin: '0px 0px -6% 0px' });
    obs.observe(el);

    // Fallback: scroll listener in case IO never fires
    const onScroll = () => {
      if (checkInView()) {reveal();window.removeEventListener('scroll', onScroll);obs.disconnect();}
    };
    window.addEventListener('scroll', onScroll, { passive: true });

    return () => {obs.disconnect();window.removeEventListener('scroll', onScroll);};
  }, [delay]);
  const Tag = as;
  return <Tag ref={ref} className="reveal" style={style}>{children}</Tag>;
}

function CardSurface({ children, variant, style, onClick }) {
  // variant from tweak: 'border' | 'filled' | 'none'
  const base = {
    padding: '28px 26px',
    borderRadius: 4,
    background: 'transparent',
    border: 'none',
    transition: 'background 200ms, border-color 200ms'
  };
  const variants = {
    border: {
      border: '1px solid var(--rule)',
      background: 'transparent'
    },
    filled: {
      background: 'var(--bg-alt)',
      border: '1px solid transparent'
    },
    none: {
      background: 'transparent',
      border: 'none',
      padding: 0
    }
  };
  return (
    <div onClick={onClick} style={{ ...base, ...variants[variant], ...style }}>
      {children}
    </div>);

}

/* -------------------- HEADER -------------------- */

function Header({ t, lang, setLang, onPreOrder, route }) {
  const onPartner = route === 'partners';
  return (
    <header style={{
      position: 'sticky',
      top: 0,
      zIndex: 30,
      background: 'color-mix(in oklab, var(--bg) 88%, transparent)',
      backdropFilter: 'saturate(140%) blur(8px)',
      WebkitBackdropFilter: 'saturate(140%) blur(8px)',
      borderBottom: '1px solid color-mix(in oklab, var(--rule) 60%, transparent)'
    }}>
      <div className="col-wide" style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        gap: 16,
        padding: '14px 0'
      }}>
        <a href="#" onClick={(e) => { e.preventDefault(); history.pushState(null, '', location.pathname + location.search); window.dispatchEvent(new HashChangeEvent('hashchange')); window.scrollTo(0, 0); }}
          style={{ display: 'flex', alignItems: 'baseline', gap: 12, minWidth: 0, textDecoration: 'none' }}>
          <div style={{
            fontFamily: 'var(--serif)', fontSize: 17, fontWeight: 500, letterSpacing: '-0.01em',
            color: 'var(--ink)', whiteSpace: 'nowrap'
          }}>{t.brand.mark}</div>
        </a>

        <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
          <nav style={{ display: 'none', gap: 22 }} className="hdr-nav">
            {[['inside', '#inside'], ['sample', '#stops'], ['price', '#price'], ['faq', '#faq']].map(([k, href]) =>
            <a key={k} href={href} style={{
              fontFamily: 'var(--sans)', fontSize: 14, color: 'var(--ink-soft)',
              textDecoration: 'none', letterSpacing: '-0.005em'
            }}>{t.nav[k]}</a>
            )}
          </nav>

          <a href="#partners" style={{
            fontFamily: 'var(--sans)', fontSize: 14, fontWeight: 500,
            letterSpacing: '-0.005em',
            textDecoration: 'none',
            padding: '7px 14px',
            borderRadius: 999,
            border: '1px solid var(--accent)',
            color: onPartner ? 'var(--accent-ink)' : 'var(--accent)',
            background: onPartner ? 'var(--accent)' : 'transparent',
            whiteSpace: 'nowrap',
            transition: 'background 180ms, color 180ms'
          }}
            onMouseOver={(e) => { e.currentTarget.style.background = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent-ink)'; }}
            onMouseOut={(e) => { if (!onPartner) { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--accent)'; } }}
          >{t.partner.nav}</a>

          <div style={{
            display: 'flex',
            border: '1px solid var(--rule)',
            borderRadius: 999,
            padding: 2,
            background: 'transparent'
          }}>
            {['en', 'ru', 'es', 'id'].map((l) =>
            <button key={l} onClick={() => setLang(l)} style={{
              fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.08em',
              textTransform: 'uppercase',
              padding: '6px 10px',
              border: 'none',
              background: lang === l ? 'var(--ink)' : 'transparent',
              color: lang === l ? 'var(--bg)' : 'var(--ink-soft)',
              borderRadius: 999,
              cursor: 'pointer',
              transition: 'none'
            }}>{l}</button>
            )}
          </div>
        </div>
      </div>

      <style>{`
        @media (min-width: 860px) {
          .hdr-nav { display: flex !important; }
        }
      `}</style>
    </header>);

}

/* -------------------- HERO -------------------- */

function Hero({ t, onPreOrder }) {
  return (
    <section style={{ position: 'relative', overflow: 'hidden', paddingTop: 'clamp(48px, 9vw, 100px)' }}>
      {/* temple video bleeding from the right, dissolving into the background */}
      <div className="hero-photo" aria-hidden="true" style={{
        position: 'absolute', top: 0, right: 0, bottom: 0,
        width: 'clamp(320px, 56vw, 880px)', zIndex: 0, pointerEvents: 'none', overflow: 'hidden'
      }}>
        <video src="assets/hero.mp4" autoPlay muted loop playsInline
        ref={(el) => {if (el) {el.muted = true;el.defaultMuted = true;el.volume = 0;}}}
        style={{
          width: '100%', height: '100%', objectFit: 'cover', objectPosition: '42% 45%', display: 'block'
        }}></video>
        <div style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(270deg, transparent 0%, color-mix(in oklab, var(--bg) 38%, transparent) 40%, var(--bg) 90%)'
        }}></div>
        <div style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(0deg, var(--bg) 0%, transparent 18%, transparent 80%, var(--bg) 100%)'
        }}></div>
      </div>

      <div className="col-wide" style={{ position: 'relative', zIndex: 1 }}>
        <div style={{ maxWidth: 640 }}>
          <Reveal>
            <Eyebrow>{t.hero.eyebrow}</Eyebrow>
          </Reveal>

          <Reveal delay={80} style={{ marginTop: 28 }}>
            <Display as="h1" size="xxl" style={{ fontStyle: 'normal' }}>
              {t.hero.title[0]}<br />
              <span style={{ fontStyle: 'italic', fontWeight: 300 }}>{t.hero.title[1]}</span><br />
              {t.hero.title[2]}
            </Display>
          </Reveal>

          <Reveal delay={180} style={{ marginTop: 32, maxWidth: 520 }}>
            <Body soft style={{ fontSize: 18 }}>
              {t.hero.sub}
            </Body>
          </Reveal>

          <Reveal delay={260} style={{ marginTop: 36 }}>
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 18, flexWrap: 'wrap' }}>
              <PrimaryButton onClick={onPreOrder}>{t.hero.cta}</PrimaryButton>
              <div style={{
                fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.08em',
                textTransform: 'uppercase', color: 'var(--ink-faint)'
              }}>{t.hero.ctaNote}</div>
            </div>
          </Reveal>
        </div>

        <Reveal delay={420} style={{ marginTop: 'clamp(64px, 10vw, 120px)' }}>
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
            gap: 0,
            borderTop: '1px solid var(--rule)',
            borderBottom: '1px solid var(--rule)',
            background: 'color-mix(in oklab, var(--bg) 70%, transparent)'
          }}>
            {t.hero.meta.map(([h, sub], i) =>
            <div key={i} style={{
              padding: '22px 24px 22px 0',
              borderRight: i < t.hero.meta.length - 1 ? '1px solid var(--rule)' : 'none',
              paddingLeft: i === 0 ? 0 : 24
            }} className={`meta-cell meta-cell-${i}`}>
                <div style={{
                fontFamily: 'var(--serif)', fontSize: 24, lineHeight: 1.1,
                letterSpacing: '-0.01em', color: 'var(--ink)'
              }}>{h}</div>
                <div style={{
                fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.1em',
                textTransform: 'uppercase', color: 'var(--ink-faint)',
                marginTop: 8
              }}>{sub}</div>
              </div>
            )}
          </div>
        </Reveal>
      </div>

      <style>{`
        @media (max-width: 720px) {
          .hero-photo { width: 78% !important; opacity: 0.5; }
        }
      `}</style>
    </section>);

}

function PrimaryButton({ children, onClick, variant = 'solid' }) {
  const base = {
    fontFamily: 'var(--sans)',
    fontSize: 15,
    fontWeight: 500,
    letterSpacing: '-0.005em',
    padding: '14px 22px 15px',
    borderRadius: 2,
    cursor: 'pointer',
    border: '1px solid var(--accent)',
    background: 'var(--accent)',
    color: 'var(--accent-ink)',
    transition: 'transform 180ms cubic-bezier(.2,.6,.2,1), background 180ms, color 180ms',
    display: 'inline-flex',
    alignItems: 'center',
    gap: 12
  };
  const ghost = {
    background: 'transparent',
    color: 'var(--ink)'
  };
  return (
    <button onClick={onClick} style={{ ...base, ...(variant === 'ghost' ? ghost : {}) }}
    onMouseOver={(e) => {e.currentTarget.style.transform = 'translateY(-1px)';e.currentTarget.style.background = 'var(--accent-deep)';e.currentTarget.style.borderColor = 'var(--accent-deep)';}}
    onMouseOut={(e) => {e.currentTarget.style.transform = 'translateY(0)';if (variant !== 'ghost') {e.currentTarget.style.background = 'var(--accent)';e.currentTarget.style.borderColor = 'var(--accent)';}}}>
      
      {children}
      <span style={{ fontFamily: 'var(--serif)', fontSize: 18, lineHeight: 1, transform: 'translateY(-1px)' }}>→</span>
    </button>);

}

/* -------------------- SECTION SHELL -------------------- */

function Section({ id, children, alt, top = true, bottom = true, numeral, numeralSide = 'right', foliage }) {
  return (
    <section id={id} style={{
      paddingTop: top ? 'var(--gap-section)' : 0,
      paddingBottom: bottom ? 'var(--gap-section)' : 0,
      background: alt ? 'var(--bg-alt)' : 'transparent',
      position: 'relative',
      overflow: 'hidden'
    }}>
      {numeral && <BigNumeral side={numeralSide} top="var(--gap-section)">{numeral}</BigNumeral>}
      {foliage &&
      <div aria-hidden="true" style={{
        position: 'absolute', top: 0, bottom: 0, [foliage]: 0,
        width: 'clamp(160px, 30vw, 420px)', zIndex: 0, pointerEvents: 'none',
        overflow: 'hidden'
      }}>
          <img src="assets/foliage.jpg" alt="" style={{
          width: '100%', height: '100%', objectFit: 'cover', display: 'block', opacity: 0.5
        }} />
          <div style={{
          position: 'absolute', inset: 0,
          background: foliage === 'right' ?
          'linear-gradient(270deg, transparent 0%, color-mix(in oklab, var(--bg-alt) 55%, transparent) 45%, var(--bg-alt) 100%)' :
          'linear-gradient(90deg, transparent 0%, color-mix(in oklab, var(--bg-alt) 55%, transparent) 45%, var(--bg-alt) 100%)'
        }}></div>
        </div>
      }
      <div style={{ position: 'relative', zIndex: 1 }}>
        {children}
      </div>
    </section>);

}

/* -------------------- 02 More than -------------------- */

function MoreThan({ t, cardVariant }) {
  return (
    <Section id="more" numeral="01" numeralSide="right">
      <div className="col-wide">
        <Reveal>
          <Eyebrow>{t.more.eyebrow}</Eyebrow>
        </Reveal>

        <div className="more-grid" style={{
          display: 'grid',
          gridTemplateColumns: '1fr',
          gap: 40,
          marginTop: 28
        }}>
          <Reveal delay={80}>
            <Display size="xl">{t.more.title}</Display>
          </Reveal>
          <Reveal delay={140} style={{ maxWidth: 520, alignSelf: 'end' }}>
            <Body soft>{t.more.intro}</Body>
          </Reveal>
        </div>

        <div style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))',
          gap: cardVariant === 'none' ? 56 : 24,
          marginTop: 'clamp(56px, 8vw, 96px)'
        }}>
          {t.more.cards.map((c, i) =>
          <Reveal key={i} delay={i * 100}>
              <CardSurface variant={cardVariant} style={{ height: '100%' }}>
                <div style={{
                fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.12em',
                textTransform: 'uppercase', color: 'var(--ink-faint)',
                marginBottom: 22
              }}>· {String(i + 1).padStart(2, '0')} &nbsp;&nbsp;{c.tag}</div>
                <Display size="m" as="h3" style={{ marginBottom: 16 }}>{c.title}</Display>
                <Body soft>{c.body}</Body>
              </CardSurface>
            </Reveal>
          )}
        </div>
      </div>

      <style>{`
        @media (min-width: 880px) {
          .more-grid {
            grid-template-columns: 1.4fr 1fr !important;
            gap: 64px !important;
            align-items: end !important;
          }
        }
      `}</style>
    </Section>);

}

/* -------------------- 03 Inside -------------------- */

function Inside({ t, cardVariant }) {
  return (
    <Section id="inside" alt numeral="02" numeralSide="left">
      <div className="col-wide">
        <Reveal>
          <Eyebrow>{t.inside.eyebrow}</Eyebrow>
        </Reveal>
        <Reveal delay={80} style={{ marginTop: 24 }}>
          <Display size="xl" style={{ maxWidth: 720 }}>{t.inside.title}</Display>
        </Reveal>

        <div style={{
          marginTop: 'clamp(48px, 7vw, 80px)',
          borderTop: '1px solid var(--rule)'
        }}>
          {t.inside.items.map((it, i) =>
          <Reveal key={i} delay={i * 60}>
              <article className="inside-row" style={{
              display: 'grid',
              gridTemplateColumns: '72px 1fr',
              gap: 24,
              padding: '28px 0',
              borderBottom: '1px solid var(--rule)',
              alignItems: 'start'
            }}>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginTop: -4 }}>
                  <ItemGlyph name={['route', 'story', 'chapter', 'cafe', 'phone', 'forever'][i % 6]} size={44} />
                </div>
                <div className="inside-content" style={{ display: 'grid', gap: 10 }}>
                  <div style={{
                  fontFamily: 'var(--serif)', fontSize: 'clamp(20px, 2.1vw, 24px)',
                  lineHeight: 1.25, letterSpacing: '-0.01em', color: 'var(--ink)'
                }}>{it.h}</div>
                  <Body soft style={{ maxWidth: 560 }}>{it.b}</Body>
                </div>
              </article>
            </Reveal>
          )}
        </div>

        <Reveal style={{ marginTop: 28 }}>
          <div style={{
            fontFamily: 'var(--mono)', fontSize: 12, letterSpacing: '0.06em',
            color: 'var(--ink-soft)'
          }}>{t.inside.timing}</div>
        </Reveal>
      </div>

      <style>{`
        @media (min-width: 720px) {
          .inside-row { grid-template-columns: 80px 1fr 1fr !important; }
          .inside-row .inside-content { display: contents !important; }
          .inside-row .inside-content > div:first-child { padding-right: 24px; }
        }
      `}</style>
    </Section>);

}

/* -------------------- STOPS -------------------- */

function Stops({ t, lang, cardVariant }) {
  const [open, setOpen] = useState(1); // Lempuyang open by default
  return (
    <Section id="stops" bottom={false} numeral="03" numeralSide="right">
      <div className="col-wide">
        <Reveal><Eyebrow>{t.stops.eyebrow}</Eyebrow></Reveal>
        <Reveal delay={80} style={{ marginTop: 24 }}>
          <Display size="xl" style={{ maxWidth: 720 }}>{t.stops.title}</Display>
        </Reveal>
        <Reveal delay={160} style={{ marginTop: 24, maxWidth: 560 }}>
          <Body soft>{t.stops.sub}</Body>
        </Reveal>

        <Reveal delay={220} style={{ marginTop: 'clamp(48px, 7vw, 80px)' }}>
          <GuidePlayerPreview lang={lang} />
        </Reveal>
      </div>
    </Section>);

}

/* The landing now borrows the application’s actual information architecture:
   chapter cards first, then only a couple of deliberately open free windows. */
function GuidePlayerPreview({ lang }) {
  const [section, setSection] = useState('stops');
  const [voice, setVoice] = useState('female');
  const [openId, setOpenId] = useState('lempuyang');
  const copy = {
    ru: { title: 'Записи по точкам', stops: 'Основные точки маршрута', bali: 'Дополнительно о Бали', male: 'Мужской голос', female: 'Женский голос', prompt: 'Выберите точку, голос и запись. Один-два фрагмента в каждой подборке можно послушать бесплатно.', tracks: 'записей', culture: 'Круг жизни на Бали', tirta: 'Тирта Гангга', taman: 'Таман Уджунг', goa: 'Гоа Лавах', free: 'Бесплатный фрагмент', full: 'Полная версия' },
    en: { title: 'Stories for each stop', stops: 'Main route stops', bali: 'More about Bali', male: 'Male voice', female: 'Female voice', prompt: 'Choose a stop, a voice and a story. One or two excerpts in each collection are free to hear.', tracks: 'tracks', culture: 'The circle of life in Bali', tirta: 'Tirta Gangga', taman: 'Taman Ujung', goa: 'Goa Lawah', free: 'Free excerpt', full: 'Full version' },
    es: { title: 'Historias por cada parada', stops: 'Paradas principales de la ruta', bali: 'Más sobre Bali', male: 'Voz masculina', female: 'Voz femenina', prompt: 'Elige una parada, una voz y una historia. Uno o dos fragmentos de cada selección se pueden escuchar gratis.', tracks: 'pistas', culture: 'El círculo de la vida en Bali', tirta: 'Tirta Gangga', taman: 'Taman Ujung', goa: 'Goa Lawah', free: 'Fragmento gratuito', full: 'Versión completa' },
    id: { title: 'Cerita untuk setiap pemberhentian', stops: 'Pemberhentian utama', bali: 'Lebih banyak tentang Bali', male: 'Suara pria', female: 'Suara wanita', prompt: 'Pilih pemberhentian, suara, dan cerita. Rekaman bahasa Indonesia sedang disiapkan.', tracks: 'rekaman', culture: 'Siklus hidup di Bali', tirta: 'Tirta Gangga', taman: 'Taman Ujung', goa: 'Goa Lawah', free: 'Segera hadir', full: 'Rekaman bahasa Indonesia segera hadir' },
  }[lang];
  const items = section === 'stops' ? [{id:'lempuyang',title:'Pura Lempuyang',count:8},{id:'tirta',title:copy.tirta,count:8},{id:'taman',title:copy.taman,count:7},{id:'goa',title:copy.goa,count:8}] : [{id:'life',title:copy.culture,count:5}];
  const changeSection = (next) => { setSection(next); setOpenId(next === 'stops' ? 'lempuyang' : 'life'); };
  return <div className="guide-player-preview">
    <div className="guide-preview-switches"><button className={section === 'stops' ? 'is-active' : ''} onClick={() => changeSection('stops')}>{copy.stops}</button><button className={section === 'bali' ? 'is-active' : ''} onClick={() => changeSection('bali')}>{copy.bali}</button></div>
    <div className="guide-preview-switches guide-preview-voice-switches"><button className={voice === 'male' ? 'is-active' : ''} onClick={() => setVoice('male')}>{copy.male}</button><button className={voice === 'female' ? 'is-active' : ''} onClick={() => setVoice('female')}>{copy.female}</button></div>
    <div className="guide-preview-catalog">{items.map((item) => <GuidePreviewCard key={item.id} item={item} isOpen={openId === item.id} onToggle={() => setOpenId(openId === item.id ? '' : item.id)} copy={copy} lang={lang} voice={voice} />)}</div>
  </div>;
}

const AUDIO_PREVIEW_ORIGIN = 'https://guide.baliaudioguide.com';

function GuidePreviewCard({ item, isOpen, onToggle, copy, lang, voice }) {
  const audioRef = useRef(null);
  const [playing, setPlaying] = useState(false);
  const supportsSample = lang !== 'id' && (['lempuyang', 'tirta'].includes(item.id) || (item.id === 'taman' && lang === 'ru') || (item.id === 'life' && lang === 'ru'));
  const isFree = supportsSample && !(voice === 'male' && lang !== 'ru');
  const source = voice === 'male' && lang === 'ru'
    ? item.id === 'tirta' ? `${AUDIO_PREVIEW_ORIGIN}/route-app/audio/tirta_gangga_v2_full_george_2026-07-24/01_vstuplenie.mp3` : item.id === 'taman' ? `${AUDIO_PREVIEW_ORIGIN}/route-app/audio/taman_ujung_final_george_2026-06-17/01_vstuplenie.mp3` : item.id === 'life' ? `${AUDIO_PREVIEW_ORIGIN}/route-app/audio/o_bali_kultura_v2_krug_zhizni_george_2026-07-24/02_rozhdenie.mp3` : `${AUDIO_PREVIEW_ORIGIN}/route-app/audio/pura_lempuyang_v3_full_george_2026-07-17/01_vstuplenie.mp3`
    : item.id === 'life'
    ? `${AUDIO_PREVIEW_ORIGIN}/route-app/audio/o_bali_kultura_v2_krug_zhizni_mariia_2026-07-24/02_rozhdenie.mp3`
    : item.id === 'tirta'
      ? lang === 'ru' ? `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/tirta_gangga/01_tirta_gangga_pervoe_vpechatlenie/audio_music/01_tirta_gangga_pervoe_vpechatlenie_mariia_r_javanese_vibes_fullbed.m4a` : lang === 'es' ? `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/tirta_gangga_es/01_tirta_gangga_primera_impresion/audio_music/01_tirta_gangga_primera_impresion_lucy_v1_with_javanese_vibes_fullbed.m4a` : `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/tirta_gangga_en/01_tirta_gangga_first_impression/audio_music/01_tirta_gangga_first_impression_alice_v1_with_javanese_vibes_fullbed.m4a`
      : item.id === 'taman'
        ? `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/taman_ujung_v3/01_taman_ujung_vstuplenie/audio_music/01_taman_ujung_vstuplenie_mariia_r_repro_javanese_fullbed.m4a`
    : lang === 'ru'
        ? `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/pura_lempuyang/01_lempuyang_pervoe_vpechatlenie/audio_music/01_mariia_r_recommended_with_epic_nusantara_fullbed.m4a`
        : lang === 'es'
          ? `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/pura_lempuyang_es/01_lempuyang_primera_impresion/audio_music/01_lempuyang_primera_impresion_lucy_v1_with_epic_nusantara_fullbed.m4a`
          : `${AUDIO_PREVIEW_ORIGIN}/elevenlabs/FINAL_APPROVED/pura_lempuyang_en/01_lempuyang_first_impression/audio_music/01_lempuyang_first_impression_alice_v1_with_epic_nusantara_fullbed.m4a`;
  const names = item.id === 'life' ? (lang === 'ru' ? ['Вступление', 'Рождение', 'Взросление', 'Замужество', 'Смерть'] : lang === 'es' ? ['Introducción', 'Nacimiento', 'Crecimiento', 'Matrimonio', 'Muerte'] : ['Introduction', 'Birth', 'Growing up', 'Marriage', 'Death']) : item.id === 'tirta' ? (lang === 'ru' ? ['Король, который строил сад сам', 'Вход и смысл названия', 'Три уровня водного сада', 'Остров демонов', 'Фонтан Нава Санга', 'Священный источник', 'Тихий сад', 'Что забрать с собой'] : lang === 'es' ? ['El rey que construyó el jardín', 'Entrada y significado', 'Los tres niveles del jardín', 'La isla de los demonios', 'Fuente Nava Sanga', 'Manantial sagrado', 'Jardín tranquilo', 'Lo que te llevas contigo'] : ['The king who built the garden', 'Entry and meaning', 'Three levels of the garden', 'The demon island', 'Nava Sanga fountain', 'Sacred spring', 'Quiet garden', 'What to take with you']) : item.id === 'taman' ? (lang === 'ru' ? ['Дворец как послание', 'Ориентиры и смысл названия', 'История и эпоха', 'Гили Бале и мосты', 'Бале Бенгонг и Бале Капал', 'Пруд Дира', 'Что уже унёс с собой этот день'] : lang === 'es' ? ['El palacio como mensaje', 'Referencias y significado', 'Historia y época', 'Gili Bale y los puentes', 'Bale Bengong y Bale Kapal', 'Estanque Dira', 'Lo que este día ya se ha llevado'] : ['A palace as a message', 'Landmarks and meaning', 'History and its era', 'Gili Bale and bridges', 'Bale Bengong and Bale Kapal', 'Dira pond', 'What this day has already taken away']) : (lang === 'ru' ? ['Первое впечатление', 'Подъём и смысл названия', 'Один из древнейших храмов Бали', 'Ворота и подношения', 'Наги и лестницы', 'Агунг и знаменитое отражение', 'Легенда Лемпуянга', 'Что забрать с собой'] : lang === 'es' ? ['Primera impresión', 'La subida y el nombre', 'Uno de los templos más antiguos', 'Puertas y ofrendas', 'Nagas y escalinatas', 'El Agung y el reflejo', 'La leyenda de Lempuyang', 'Lo que te llevas contigo'] : ['First impression', 'The climb and the name', 'One of Bali’s oldest temples', 'Gates and offerings', 'Nagas and stairways', 'Mount Agung and the reflection', 'The legend of Lempuyang', 'What to take with you']);
  const toggle = () => { const audio = audioRef.current; if (!audio) return; audio.paused ? audio.play().catch(() => {}) : audio.pause(); };
  return <div className={`guide-preview-card ${isOpen ? 'is-open' : ''}`}><audio ref={audioRef} src={source} preload="metadata" onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onTimeUpdate={() => { if (audioRef.current?.currentTime >= 55) { audioRef.current.pause(); audioRef.current.currentTime = 0; } }} /><button className="guide-preview-card-head" onClick={onToggle}><span><strong>{item.title}</strong><small>{item.count} {copy.tracks}</small></span><i>{isOpen ? '−' : '+'}</i></button>{isOpen && <div className="guide-preview-card-body">{names.map((name, index) => { const available = item.id === 'life' ? isFree && index === 1 : isFree && index === 0; return available ? <button key={name} className="guide-preview-story is-free" onClick={toggle} aria-label={playing ? 'Pause' : 'Play'}><span className="guide-preview-play">{playing ? 'Ⅱ' : '▶'}</span><b>{name}</b><em>{copy.free} · 0:55</em></button> : <div key={name} className="guide-preview-story"><span>{String(index + 1).padStart(2, '0')}</span><b>{name}</b><em>⌕ {copy.full}</em></div>; })}</div>}</div>;
}

function StopCard({ stop, index, isOpen, onToggle, cardVariant, lockedLabel, sampleLabel, sampleData, lang }) {
  const base = {
    cursor: 'pointer',
    transition: 'background 220ms ease',
    padding: '22px 24px',
    borderRadius: 2
  };
  const variants = {
    border: { border: '1px solid var(--rule)', background: isOpen ? 'var(--bg-alt)' : 'transparent' },
    filled: { background: isOpen ? 'var(--bg-deep)' : 'var(--bg-alt)', border: '1px solid transparent' },
    none: { background: 'transparent', borderBottom: '1px solid var(--rule)', padding: '22px 0', borderRadius: 0 }
  };
  return (
    <div onClick={onToggle} style={{ position: 'relative', ...base, ...variants[cardVariant] }}>
      <div className="sticky-cta-shell" style={{
        display: 'grid',
        gridTemplateColumns: '72px 1fr auto',
        gap: 18,
        alignItems: 'baseline'
      }}>
        <div style={{
          fontFamily: 'var(--mono)', fontSize: 12, letterSpacing: '0.06em',
          color: 'var(--ink-faint)'
        }}>{stop.time}</div>

        <div style={{
          fontFamily: 'var(--serif)', fontSize: 'clamp(20px, 2.3vw, 26px)',
          lineHeight: 1.2, letterSpacing: '-0.01em', color: 'var(--ink)'
        }}>{stop.name}</div>

        <div style={{
          display: 'flex', alignItems: 'center', gap: 12,
          fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.12em',
          textTransform: 'uppercase', color: stop.sample ? 'var(--ink)' : 'var(--ink-faint)',
          whiteSpace: 'nowrap'
        }}>
          {stop.sample ?
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            padding: '4px 8px', border: '1px solid var(--ink)', borderRadius: 2
          }}>● {sampleLabel}</span> :

          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
              <LockIcon /> {lockedLabel}
            </span>
          }
          <span style={{
            fontSize: 14, fontFamily: 'var(--serif)',
            transform: isOpen ? 'rotate(90deg)' : 'rotate(0)',
            transition: 'transform 220ms',
            color: 'var(--ink-soft)'
          }}>→</span>
        </div>
      </div>

      <div style={{
        display: 'grid',
        gridTemplateRows: isOpen ? '1fr' : '0fr',
        transition: 'grid-template-rows 280ms ease',
        marginTop: isOpen ? 16 : 0
      }}>
        <div style={{ overflow: 'hidden' }}>
          <Body soft style={{
            maxWidth: 620,
            paddingLeft: 90,
            fontSize: 16
          }}>{stop.preview}</Body>

          {stop.sample && sampleData &&
          <div style={{ paddingLeft: 90, marginTop: 24 }}>
            <RoutePreviewLibrary lang={lang} isOpen={isOpen} />
          </div>
          }
        </div>
      </div>
    </div>);

}

function LockIcon() {
  return (
    <svg width="11" height="11" viewBox="0 0 12 12" fill="none">
      <rect x="2.5" y="5.5" width="7" height="5" stroke="currentColor" strokeWidth="1" />
      <path d="M4 5.5V4a2 2 0 1 1 4 0v1.5" stroke="currentColor" strokeWidth="1" fill="none" />
    </svg>);

}

/* -------------------- SAMPLE PLAYER (embedded in a route stop) -------------------- */

function SamplePlayer({ sample, isOpen }) {
  const audioRef = useRef(null);
  const waveRef = useRef(null);
  const [playing, setPlaying] = useState(false);
  const [pos, setPos] = useState(0); // 0..1
  const [cur, setCur] = useState(0); // seconds elapsed
  const [dur, setDur] = useState(0); // real duration once known

  const bars = useMemo(() => {
    const N = 80;
    return Array.from({ length: N }, (_, i) => {
      const x = i / N;
      // pseudo-random but stable
      const n = Math.sin(i * 1.7) * Math.cos(i * 0.3) + Math.sin(i * 0.6) * 0.5;
      const env = Math.sin(x * Math.PI);
      return 0.25 + 0.7 * Math.abs(n) * env;
    });
  }, []);

  // Pause when the stop card collapses
  useEffect(() => {
    if (!isOpen && audioRef.current) audioRef.current.pause();
  }, [isOpen]);

  const toggle = () => {
    const a = audioRef.current;
    if (!a) return;
    if (a.paused) {a.play().catch(() => {});} else {a.pause();}
  };

  const onLoaded = () => {
    const a = audioRef.current;
    if (a && isFinite(a.duration)) setDur(a.duration);
  };
  const onTime = () => {
    const a = audioRef.current;
    if (!a) return;
    setCur(a.currentTime);
    setPos(a.duration ? a.currentTime / a.duration : 0);
  };
  const onEnded = () => {setPlaying(false);setPos(0);setCur(0);};

  const seek = (clientX) => {
    const a = audioRef.current,el = waveRef.current;
    if (!a || !el || !a.duration) return;
    const r = el.getBoundingClientRect();
    const x = Math.min(1, Math.max(0, (clientX - r.left) / r.width));
    a.currentTime = x * a.duration;
    setPos(x);setCur(x * a.duration);
  };

  const durLabel = dur ? formatTime(dur) : sample.duration;

  return (
    <div style={{ position: 'relative', paddingTop: 4 }} onClick={(e) => e.stopPropagation()}>
      <audio
        ref={audioRef}
        src={sample.audioSrc}
        preload="metadata"
        onLoadedMetadata={onLoaded}
        onDurationChange={onLoaded}
        onTimeUpdate={onTime}
        onPlay={() => setPlaying(true)}
        onPause={() => setPlaying(false)}
        onEnded={onEnded} />

      <div style={{
        display: 'grid',
        gridTemplateColumns: '1fr',
        gap: 28
      }} className="sample-grid">
        <div style={{ display: 'flex', alignItems: 'center', gap: 22 }}>
          <button onClick={toggle} aria-label={playing ? 'Pause' : 'Play'} style={{
            width: 64, height: 64,
            border: '1px solid var(--accent)',
            background: 'var(--accent)',
            color: 'var(--accent-ink)',
            borderRadius: 999,
            cursor: 'pointer',
            display: 'grid', placeItems: 'center',
            flexShrink: 0,
            transition: 'transform 180ms'
          }}
          onMouseOver={(e) => e.currentTarget.style.transform = 'scale(1.04)'}
          onMouseOut={(e) => e.currentTarget.style.transform = 'scale(1)'}>
            
            {playing ?
            <svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor"><rect x="0" y="0" width="5" height="16" /><rect x="9" y="0" width="5" height="16" /></svg> :

            <svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor" style={{ transform: 'translateX(1.5px)' }}><path d="M0 0 L14 8 L0 16 Z" /></svg>
            }
          </button>
          <div style={{ minWidth: 0 }}>
            <div style={{
              fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.12em',
              textTransform: 'uppercase', color: 'var(--ink-faint)',
              marginBottom: 6
            }}>{sample.audioLabel}</div>
            <div style={{
              fontFamily: 'var(--serif)', fontSize: 22, lineHeight: 1.2,
              letterSpacing: '-0.01em', color: 'var(--ink)'
            }}>{sample.track}</div>
          </div>
        </div>

        <div>
          <div
            ref={waveRef}
            onClick={(e) => seek(e.clientX)}
            style={{
              display: 'flex', alignItems: 'flex-end', gap: 2,
              height: 56,
              width: '100%',
              cursor: 'pointer'
            }}>
            {bars.map((h, i) => {
              const passed = i / bars.length < pos;
              return (
                <div key={i} style={{
                  flex: 1,
                  height: `${h * 100}%`,
                  background: passed ? 'var(--accent)' : 'var(--ink-faint)',
                  opacity: passed ? 1 : 0.45,
                  transition: 'background 100ms, opacity 100ms',
                  minHeight: 2,
                  borderRadius: 1,
                  pointerEvents: 'none'
                }} />);

            })}
          </div>
          <div style={{
            display: 'flex', justifyContent: 'space-between',
            marginTop: 10,
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.08em',
            color: 'var(--ink-faint)'
          }}>
            <span>{formatTime(cur)}</span>
            <span>{durLabel}</span>
          </div>
        </div>
      </div>

      <details style={{ marginTop: 24, borderTop: '1px solid var(--rule)', paddingTop: 18 }}>
        <summary style={{
          cursor: 'pointer',
          listStyle: 'none',
          fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.12em',
          textTransform: 'uppercase', color: 'var(--ink-soft)',
          display: 'inline-flex', alignItems: 'center', gap: 8
        }}>
          <span style={{ fontSize: 14 }}>＋</span> {sample.transcriptLabel}
        </summary>
        <Body soft style={{ marginTop: 14, fontStyle: 'italic', maxWidth: 620, fontFamily: 'var(--serif)', fontSize: 19, lineHeight: 1.45 }}>
          {sample.transcript}
        </Body>
      </details>

      <style>{`
        @media (min-width: 720px) {
          .sample-grid {
            grid-template-columns: minmax(280px, 0.9fr) 1.4fr !important;
            align-items: center !important;
            gap: 48px !important;
          }
        }
        details summary::-webkit-details-marker { display: none; }
      `}</style>
    </div>);

}

function formatTime(s) {
  const m = Math.floor(s / 60);
  const r = Math.floor(s % 60).toString().padStart(2, '0');
  return `${m}:${r}`;
}

/* A public, deliberately limited window into the same chapter-and-voice pattern
   used by the private route companion. */
function RoutePreviewLibrary({ lang, isOpen }) {
  const audioRef = useRef(null);
  const [voice, setVoice] = useState('female');
  const [selected, setSelected] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [current, setCurrent] = useState(0);
  const PREVIEW_SECONDS = 55;
  const copy = {
    ru: { kicker: 'Открытый фрагмент аудиогида', title: 'Пура Лемпуянг', chapters: '8 глав по точке', female: 'Женский голос', male: 'Мужской голос', free: 'Бесплатный фрагмент', full: 'В полной версии', now: 'Сейчас играет', listen: 'Слушать 55 сек', more: 'Откройте полный гид, чтобы продолжить маршрут' },
    en: { kicker: 'An open window into the audio guide', title: 'Pura Lempuyang', chapters: '8 chapters for this stop', female: 'Female voice', male: 'Male voice', free: 'Free excerpt', full: 'In the full guide', now: 'Now playing', listen: 'Listen — 55 sec', more: 'Open the full guide to continue the route' },
    es: { kicker: 'Un fragmento abierto de la audioguía', title: 'Pura Lempuyang', chapters: '8 capítulos de esta parada', female: 'Voz femenina', male: 'Voz masculina', free: 'Fragmento gratuito', full: 'En la guía completa', now: 'Reproduciendo ahora', listen: 'Escuchar — 55 s', more: 'Abre la guía completa para continuar la ruta' },
  }[lang] || {};
  const chapterTitles = {
    ru: ['Первое впечатление', 'Подъём и смысл названия', 'Один из древнейших храмов Бали', 'Ворота и подношения', 'Наги и лестницы', 'Агунг и знаменитое отражение', 'Легенда Лемпуянга', 'Что забрать с собой'],
    en: ['First impression', 'The climb and the name', 'One of Bali’s oldest temples', 'Gates and offerings', 'Nagas and stairways', 'Mount Agung and the reflection', 'The legend of Lempuyang', 'What to take with you'],
    es: ['Primera impresión', 'La subida y el nombre', 'Uno de los templos más antiguos', 'Puertas y ofrendas', 'Nagas y escalinatas', 'El Agung y el reflejo', 'La leyenda de Lempuyang', 'Lo que te llevas contigo'],
  }[lang] || [];
  const sources = {
    female: {
      ru: '../elevenlabs/FINAL_APPROVED/pura_lempuyang/01_lempuyang_pervoe_vpechatlenie/audio_music/01_mariia_r_recommended_with_epic_nusantara_fullbed.m4a',
      en: '../elevenlabs/FINAL_APPROVED/pura_lempuyang_en/01_lempuyang_first_impression/audio_music/01_lempuyang_first_impression_alice_v1_with_epic_nusantara_fullbed.m4a',
      es: '../elevenlabs/FINAL_APPROVED/pura_lempuyang_es/01_lempuyang_primera_impresion/audio_music/01_lempuyang_primera_impresion_lucy_v1_with_epic_nusantara_fullbed.m4a',
    },
    male: { ru: '../route-app/audio/pura_lempuyang_v3_full_george_2026-07-17/01_vstuplenie.mp3' },
  };
  const canUseMale = lang === 'ru';
  const source = sources[voice]?.[lang] || sources.female[lang];
  const play = () => audioRef.current?.play().catch(() => {});

  useEffect(() => {
    if (!isOpen) audioRef.current?.pause();
  }, [isOpen]);
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    audio.pause(); audio.currentTime = 0; setCurrent(0); setPlaying(false);
  }, [voice, selected, lang]);

  const onTime = () => {
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.currentTime >= PREVIEW_SECONDS) { audio.pause(); audio.currentTime = 0; setCurrent(0); return; }
    setCurrent(audio.currentTime);
  };
  return <div className="route-preview-library" onClick={(e) => e.stopPropagation()}>
    <audio ref={audioRef} src={source} preload="metadata" onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onTimeUpdate={onTime} />
    <div className="route-preview-head">
      <div><div className="route-preview-kicker">{copy.kicker}</div><strong>{copy.title}</strong><span>{copy.chapters}</span></div>
      <div className="route-preview-voices" aria-label="Voice selection">
        <button className={voice === 'female' ? 'is-active' : ''} onClick={() => setVoice('female')}>{copy.female}</button>
        <button disabled={!canUseMale} className={voice === 'male' ? 'is-active' : ''} onClick={() => canUseMale && setVoice('male')}>{copy.male}</button>
      </div>
    </div>
    <div className="route-preview-now">
      <button className="route-preview-play" onClick={() => playing ? audioRef.current?.pause() : play()} aria-label={playing ? 'Pause' : 'Play'}>{playing ? 'Ⅱ' : '▶'}</button>
      <div><small>{copy.now} · {copy.free}</small><b>01 · {chapterTitles[0]}</b></div>
      <span>{formatTime(current)} / 0:55</span>
    </div>
    <div className="route-preview-tracks">
      {chapterTitles.map((title, i) => <button key={title} className={`route-preview-track ${i === selected ? 'is-current' : ''} ${i > 0 ? 'is-locked' : ''}`} onClick={() => { if (i === 0) { setSelected(0); play(); } }}>
        <span>{String(i + 1).padStart(2, '0')}</span><b>{title}</b><em>{i === 0 ? copy.free : '⌕ ' + copy.full}</em>
      </button>)}
    </div>
    <div className="route-preview-more">{copy.more} <span>→</span></div>
  </div>;
}

/* -------------------- NOT FOR -------------------- */

function NotFor({ t }) {
  return (
    <Section id="notfor" numeral="04" numeralSide="right">
      <div className="col-wide">
        <Reveal><Eyebrow>{t.notfor.eyebrow}</Eyebrow></Reveal>
        <Reveal delay={80} style={{ marginTop: 24 }}>
          <Display size="xl" style={{ maxWidth: 820 }}>{t.notfor.title}</Display>
        </Reveal>
        <Reveal delay={160} style={{ marginTop: 28, maxWidth: 680 }}>
          <Body soft style={{ fontSize: 18 }}>{t.notfor.body}</Body>
        </Reveal>

        <div className="notfor-grid" style={{
          display: 'grid',
          gridTemplateColumns: '1fr',
          gap: 40,
          marginTop: 'clamp(56px, 8vw, 96px)'
        }}>
          <Reveal delay={240}>
            <FitColumn h={t.notfor.forH} list={t.notfor.forL} kind="for" />
          </Reveal>
          <Reveal delay={320}>
            <FitColumn h={t.notfor.notH} list={t.notfor.notL} kind="not" />
          </Reveal>
        </div>
      </div>

      <style>{`
        @media (min-width: 720px) {
          .notfor-grid { grid-template-columns: 1fr 1fr !important; gap: 56px !important; }
        }
      `}</style>
    </Section>);

}

function FitColumn({ h, list, kind }) {
  return (
    <div>
      <div style={{
        fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
        textTransform: 'uppercase', color: 'var(--ink-faint)',
        marginBottom: 22,
        paddingBottom: 18, borderBottom: '1px solid var(--rule)'
      }}>{h}</div>
      <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 18 }}>
        {list.map((l, i) =>
        <li key={i} style={{
          display: 'grid', gridTemplateColumns: '24px 1fr', gap: 12,
          alignItems: 'baseline',
          fontFamily: 'var(--serif)', fontSize: 'clamp(18px, 2vw, 22px)',
          lineHeight: 1.35, letterSpacing: '-0.005em',
          color: kind === 'for' ? 'var(--ink)' : 'var(--ink-soft)'
        }}>
            <span style={{
            fontFamily: 'var(--mono)', fontSize: 12,
            color: kind === 'for' ? 'var(--ink)' : 'var(--ink-faint)'
          }}>{kind === 'for' ? '+' : '−'}</span>
            <span>{l}</span>
          </li>
        )}
      </ul>
    </div>);

}

/* -------------------- TRUST -------------------- */

function Trust({ t }) {
  return (
    <Section id="trust" alt>
      <Doodle icon={IconTemple} size={92} opacity={0.55} rotate={-3} style={{ top: 'clamp(36px, 6vw, 80px)', right: '5%' }} />
      <div className="col">
        <Reveal><Eyebrow>{t.trust.eyebrow}</Eyebrow></Reveal>
        <Reveal delay={80} style={{ marginTop: 24 }}>
          <Display size="xl">{t.trust.title}</Display>
        </Reveal>

        <div style={{
          marginTop: 'clamp(40px, 6vw, 56px)',
          display: 'grid', gap: 24,
          fontSize: 18
        }}>
          {t.trust.body.map((p, i) =>
          <Reveal key={i} delay={i * 100}>
              <Body soft style={{ fontSize: 18, lineHeight: 1.6 }}>{p}</Body>
            </Reveal>
          )}
        </div>

        <Reveal delay={120} style={{ marginTop: 'clamp(48px, 7vw, 72px)' }}>
          <BigQuote>{t.trust.pull}</BigQuote>
        </Reveal>
      </div>
    </Section>);

}

/* -------------------- PRICE -------------------- */

function Price({ t, cardVariant, onPreOrder }) {
  return (
    <Section id="price" numeral="06" numeralSide="right">
      <div className="col-wide">
        <Reveal><Eyebrow>{t.price.eyebrow}</Eyebrow></Reveal>
        <Reveal delay={80} style={{ marginTop: 24 }}>
          <Display size="xl" style={{ maxWidth: 820 }}>{t.price.title}</Display>
        </Reveal>
        <Reveal delay={160} style={{ marginTop: 24, maxWidth: 600 }}>
          <Body soft>{t.price.lead}</Body>
        </Reveal>

        <Reveal delay={240} style={{ marginTop: 'clamp(48px, 7vw, 72px)' }}>
          <PriceCard t={t} cardVariant={cardVariant} onPreOrder={onPreOrder} />
        </Reveal>
      </div>
    </Section>);

}

function PriceCard({ t, cardVariant, onPreOrder }) {
  const variants = {
    border: { border: '1px solid var(--rule)', background: 'transparent' },
    filled: { border: '1px solid transparent', background: 'var(--bg-alt)' },
    none: { border: 'none', borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)', background: 'transparent', borderRadius: 0 }
  };
  return (
    <div style={{
      padding: 'clamp(28px, 4vw, 48px)',
      borderRadius: 2,
      ...variants[cardVariant]
    }}>
      <div className="price-grid" style={{
        display: 'grid',
        gridTemplateColumns: '1fr',
        gap: 40,
        alignItems: 'start'
      }}>
        <div>
          <div style={{
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
            textTransform: 'uppercase', color: 'var(--ink-faint)',
            marginBottom: 18
          }}>Pre-order · Vol. 01</div>
          <div style={{
              fontFamily: 'var(--serif)',
              fontSize: 'clamp(72px, 11vw, 120px)',
              lineHeight: 0.95,
              letterSpacing: '-0.04em',
              color: 'var(--ink)'
            }}>{t.price.priceLine}</div>
          <div style={{
            marginTop: 14,
            fontFamily: 'var(--serif)', fontStyle: 'italic',
            fontSize: 18, color: 'var(--ink-soft)'
          }}>{t.price.compare}</div>
          <div style={{
            marginTop: 12,
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.06em',
            color: 'var(--ink-faint)'
          }}>{t.price.priceNote}</div>
        </div>

        <div>
          <Body soft style={{ fontSize: 17 }}>{t.price.includes}</Body>

          <ul style={{
            listStyle: 'none', padding: 0,
            margin: '28px 0 0',
            display: 'grid', gap: 14,
            borderTop: '1px solid var(--rule)',
            paddingTop: 22
          }}>
            {t.price.bullets.map((b, i) =>
            <li key={i} style={{
              display: 'grid', gridTemplateColumns: '24px 1fr', gap: 10,
              fontFamily: 'var(--sans)', fontSize: 15, color: 'var(--ink)'
            }}>
                <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--ink-faint)' }}>
                  {String(i + 1).padStart(2, '0')}
                </span>
                <span>{b}</span>
              </li>
            )}
          </ul>

          <div style={{ marginTop: 32, display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-start' }}>
            <PrimaryButton onClick={onPreOrder}>{t.price.cta}</PrimaryButton>
            <div style={{
              fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.08em',
              color: 'var(--ink-faint)'
            }}>{t.price.ctaSub}</div>
          </div>
        </div>
      </div>

      <style>{`
        @media (min-width: 880px) {
          .price-grid { grid-template-columns: 1fr 1.2fr !important; gap: 72px !important; }
        }
      `}</style>
    </div>);

}

/* -------------------- FAQ -------------------- */

function Faq({ t }) {
  const [open, setOpen] = useState(0);
  return (
    <Section id="faq" alt numeral="07" numeralSide="left">
      <div className="col-wide">
        <Reveal><Eyebrow>{t.faq.eyebrow}</Eyebrow></Reveal>
        <Reveal delay={80} style={{ marginTop: 24 }}>
          <Display size="xl" style={{ maxWidth: 720 }}>{t.faq.title}</Display>
        </Reveal>

        <div style={{
          marginTop: 'clamp(48px, 7vw, 72px)',
          borderTop: '1px solid var(--rule)'
        }}>
          {t.faq.items.map((it, i) => {
            const isOpen = open === i;
            return (
              <Reveal key={i} delay={i * 40}>
                <div style={{ borderBottom: '1px solid var(--rule)' }}>
                  <button onClick={() => setOpen(isOpen ? -1 : i)} style={{
                    width: '100%',
                    display: 'grid', gridTemplateColumns: '40px 1fr 30px', gap: 18,
                    padding: '22px 0',
                    background: 'transparent', border: 'none', textAlign: 'left',
                    cursor: 'pointer', alignItems: 'baseline',
                    color: 'var(--ink)'
                  }}>
                    <span style={{
                      fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.06em',
                      color: 'var(--ink-faint)'
                    }}>{String(i + 1).padStart(2, '0')}</span>
                    <span style={{
                      fontFamily: 'var(--serif)', fontSize: 'clamp(19px, 2.2vw, 24px)',
                      lineHeight: 1.3, letterSpacing: '-0.01em'
                    }}>{it.q}</span>
                    <span style={{
                      fontFamily: 'var(--mono)', fontSize: 18,
                      color: 'var(--ink-soft)',
                      transform: isOpen ? 'rotate(45deg)' : 'rotate(0)',
                      transition: 'transform 240ms',
                      lineHeight: 1
                    }}>+</span>
                  </button>
                  <div style={{
                    display: 'grid',
                    gridTemplateRows: isOpen ? '1fr' : '0fr',
                    transition: 'grid-template-rows 320ms ease'
                  }}>
                    <div style={{ overflow: 'hidden' }}>
                      <div style={{
                        padding: '0 30px 26px 58px'
                      }}>
                        <Body soft style={{ maxWidth: 680, fontSize: 16 }}>{it.a}</Body>
                      </div>
                    </div>
                  </div>
                </div>
              </Reveal>);

          })}
        </div>
      </div>
    </Section>);

}

/* -------------------- FOOTER -------------------- */

function Footer({ t }) {
  return (
    <footer style={{
      padding: '64px 0 120px',
      borderTop: '1px solid var(--rule)'
    }}>
      <div className="col-wide" style={{
        display: 'flex',
        justifyContent: 'space-between',
        gap: 24,
        flexWrap: 'wrap'
      }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{
              fontFamily: 'var(--serif)', fontSize: 22, letterSpacing: '-0.01em',
              color: 'var(--ink)'
            }}>{t.brand.mark}</div>
          </div>
          <div style={{
            marginTop: 6,
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.1em',
            textTransform: 'uppercase', color: 'var(--ink-faint)'
          }}>{t.brand.subMark}</div>
        </div>
        <div style={{ maxWidth: 360, textAlign: 'right' }}>
          <Body soft style={{ fontSize: 14 }}>{t.footer.line}</Body>
          <div style={{
            marginTop: 8,
            fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--ink-soft)'
          }}>{t.footer.contact}</div>
        </div>
      </div>
    </footer>);

}

/* -------------------- STICKY CTA -------------------- */

function StickyCta({ t, visible, onPreOrder }) {
  return (
    <div style={{
      position: 'fixed',
      left: 0, right: 0, bottom: 0,
      zIndex: 40,
      padding: '12px 16px calc(12px + env(safe-area-inset-bottom))',
      pointerEvents: visible ? 'auto' : 'none',
      opacity: visible ? 1 : 0,
      transform: visible ? 'translateY(0)' : 'translateY(12px)',
      transition: 'opacity 280ms, transform 280ms'
    }}>
      <div style={{
        maxWidth: 520,
        margin: '0 auto',
        background: 'var(--ink)',
        color: 'var(--bg)',
        borderRadius: 999,
        padding: '10px 12px 10px 22px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        gap: 16,
        boxShadow: '0 18px 40px -16px color-mix(in oklab, var(--ink) 50%, transparent)'
      }}>
        <div className="sticky-cta-price" style={{ display: 'flex', alignItems: 'baseline', gap: 10, minWidth: 0 }}>
          <span style={{
            fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.12em',
            textTransform: 'uppercase', opacity: 0.7
          }}>{t.sticky.label}</span>
          <span style={{
            fontFamily: 'var(--serif)', fontSize: 20, letterSpacing: '-0.01em'
          }}>{t.sticky.price}</span>
        </div>
        <button className="sticky-cta-button" onClick={onPreOrder} style={{
          background: 'var(--bg)', color: 'var(--ink)',
          border: 'none', borderRadius: 999,
          padding: '10px 18px',
          fontFamily: 'var(--sans)', fontSize: 14, fontWeight: 500,
          cursor: 'pointer',
          display: 'inline-flex', alignItems: 'center', gap: 8,
          whiteSpace: 'nowrap'
        }}>
          {t.sticky.cta}
          <span style={{ fontFamily: 'var(--serif)', fontSize: 16, lineHeight: 1 }}>→</span>
        </button>
      </div>
    </div>);

}

/* -------------------- MODAL -------------------- */

function PreOrderModal({ t, lang, open, onClose }) {
  const [submitted, setSubmitted] = useState(false);
  const [email, setEmail] = useState('');

  useEffect(() => {
    if (!open) {setSubmitted(false);setEmail('');}
  }, [open]);

  if (!open) return null;

  const L = lang === 'ru' ? {
    title: 'Предзаказ · $15',
    sub: 'Оставь email — пришлём ссылку на оплату и приватный доступ после релиза.',
    placeholder: 'твой email',
    cta: 'Зарезервировать место',
    note: 'Цена зафиксирована за тобой до релиза.',
    thanksH: 'Готово.',
    thanksB: 'Мы прислали подтверждение на email. Доступ откроется первым ранним подписчикам.',
    close: 'Закрыть'
  } : lang === 'es' ? {
    title: 'Acceso anticipado · $15',
    sub: 'Déjanos tu email y te enviaremos el enlace de pago y acceso privado cuando se lance.',
    placeholder: 'tu email', cta: 'Reservar mi plaza', note: 'Tu precio queda fijado hasta el lanzamiento.',
    thanksH: 'Reservado.', thanksB: 'La confirmación está en camino a tu correo. El acceso anticipado se abre primero para la lista de reserva.', close: 'Cerrar'
  } : {
    title: 'Pre-order · $15',
    sub: 'Leave your email — we’ll send a payment link and private access on release.',
    placeholder: 'your email',
    cta: 'Reserve a spot',
    note: 'Your price is locked until release.',
    thanksH: 'Reserved.',
    thanksB: 'A confirmation is on its way to your inbox. Early access opens first to the pre-order list.',
    close: 'Close'
  };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 100,
      background: 'color-mix(in oklab, var(--ink) 35%, transparent)',
      backdropFilter: 'blur(4px)', WebkitBackdropFilter: 'blur(4px)',
      display: 'grid', placeItems: 'center',
      padding: 20
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: 'var(--bg)',
        border: '1px solid var(--rule)',
        borderRadius: 4,
        maxWidth: 480,
        width: '100%',
        padding: '36px 32px 32px',
        position: 'relative'
      }}>
        <button onClick={onClose} aria-label="Close" style={{
          position: 'absolute', top: 14, right: 14,
          background: 'transparent', border: 'none',
          fontFamily: 'var(--mono)', fontSize: 18, color: 'var(--ink-soft)',
          cursor: 'pointer', padding: 6, lineHeight: 1
        }}>×</button>

        {!submitted ?
        <>
            <div style={{
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
            textTransform: 'uppercase', color: 'var(--ink-faint)',
            marginBottom: 14
          }}>Vol. 01 — East Bali</div>
            <Display size="m" as="h3" style={{ marginBottom: 14 }}>{L.title}</Display>
            <Body soft style={{ marginBottom: 24, fontSize: 16 }}>{L.sub}</Body>

            <form onSubmit={(e) => {e.preventDefault();if (email) setSubmitted(true);}}>
              <input
              type="email"
              required
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder={L.placeholder}
              style={{
                width: '100%',
                padding: '14px 16px',
                fontFamily: 'var(--sans)', fontSize: 15,
                border: '1px solid var(--rule)',
                background: 'var(--bg-alt)',
                color: 'var(--ink)',
                borderRadius: 2,
                outline: 'none'
              }}
              onFocus={(e) => e.target.style.borderColor = 'var(--ink)'}
              onBlur={(e) => e.target.style.borderColor = 'var(--rule)'} />
            
              <button type="submit" style={{
              marginTop: 14,
              width: '100%',
              padding: '14px 22px',
              background: 'var(--ink)', color: 'var(--bg)',
              border: '1px solid var(--ink)',
              fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 500,
              cursor: 'pointer',
              borderRadius: 2,
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 10
            }}>
                {L.cta}
                <span style={{ fontFamily: 'var(--serif)', fontSize: 18, lineHeight: 1 }}>→</span>
              </button>
            </form>
            <div style={{
            marginTop: 14,
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.08em',
            color: 'var(--ink-faint)', textAlign: 'center'
          }}>{L.note}</div>
          </> :

        <>
            <div style={{
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
            textTransform: 'uppercase', color: 'var(--ink-faint)',
            marginBottom: 14
          }}>· · ·</div>
            <Display size="m" as="h3" style={{ marginBottom: 14, fontStyle: 'italic', fontWeight: 300 }}>{L.thanksH}</Display>
            <Body soft style={{ marginBottom: 28, fontSize: 16 }}>{L.thanksB}</Body>
            <button onClick={onClose} style={{
            padding: '12px 18px',
            background: 'transparent', color: 'var(--ink)',
            border: '1px solid var(--ink)',
            fontFamily: 'var(--sans)', fontSize: 14,
            cursor: 'pointer', borderRadius: 2
          }}>{L.close}</button>
          </>
        }
      </div>
    </div>);

}

/* -------------------- TEMPLE BAND (full-bleed photo) -------------------- */

function TempleBand({ t }) {
  return (
    <section style={{ position: 'relative', background: 'var(--ink)' }}>
      <div style={{
        position: 'relative',
        width: '100%',
        height: 'clamp(420px, 72vh, 760px)',
        overflow: 'hidden',
        background: 'var(--accent-deep)'
      }}>
        <img src="assets/foliage.jpg" alt="Tropical palm leaves after rain"
        style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: '50% 50%', display: 'block' }} />

        {/* legibility gradient */}
        <div style={{
          position: 'absolute', inset: 0, pointerEvents: 'none',
          background: 'linear-gradient(to top, color-mix(in oklab, var(--accent-deep) 90%, #000 10%) 2%, color-mix(in oklab, var(--accent-deep) 55%, transparent) 44%, color-mix(in oklab, var(--accent-deep) 20%, transparent) 80%)'
        }}></div>

        {/* corner ticks */}
        <div style={{ position: 'absolute', top: 22, left: 22, opacity: 0.7, pointerEvents: 'none' }}>
          <Crosshair size={16} />
        </div>
        <div style={{
          position: 'absolute', top: 22, right: 24, pointerEvents: 'none',
          fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.14em',
          textTransform: 'uppercase', color: 'rgba(255,255,255,0.8)'
        }}>fig. 02</div>

        <div className="col-wide" style={{
          position: 'absolute', left: '50%', bottom: 'clamp(36px, 6vw, 64px)',
          transform: 'translateX(-50%)',
          pointerEvents: 'none'
        }}>
          <Reveal>
            <div style={{
              fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
              textTransform: 'uppercase', color: 'rgba(255,255,255,0.85)',
              marginBottom: 18
            }}>
              {t.band.eyebrow}
            </div>
          </Reveal>
          <Reveal delay={120}>
            <div style={{
              fontFamily: 'var(--serif)', fontStyle: 'italic', fontWeight: 300,
              fontSize: 'clamp(32px, 6vw, 72px)', lineHeight: 1.05,
              letterSpacing: '-0.02em', color: '#fff', maxWidth: 900,
              textWrap: 'pretty'
            }}>{t.band.quote}</div>
          </Reveal>
          <Reveal delay={200}>
            <div style={{
              marginTop: 20, maxWidth: 460,
              fontFamily: 'var(--sans)', fontSize: 16, lineHeight: 1.5,
              color: 'rgba(255,255,255,0.82)'
            }}>{t.band.sub}</div>
          </Reveal>
        </div>
      </div>
    </section>);

}

/* -------------------- FOLIAGE BLEED (palm leaf off-edge) -------------------- */

/* -------------------- PHOTO STRIP (editorial divider) -------------------- */

function PhotoStrip({ src, alt, caption, credit, objectPosition = '50% 50%' }) {
  return (
    <section style={{ position: 'relative', background: 'var(--ink)' }}>
      <div style={{ position: 'relative', width: '100%', height: 'clamp(240px, 38vw, 460px)', overflow: 'hidden' }}>
        <img src={src} alt={alt} style={{
          position: 'absolute', inset: 0, width: '100%', height: '100%',
          objectFit: 'cover', objectPosition, display: 'block'
        }} />
        <div style={{
          position: 'absolute', inset: 0, pointerEvents: 'none',
          background: 'linear-gradient(to top, rgba(20,18,14,0.55) 0%, transparent 38%)'
        }}></div>
        <div className="col-wide" style={{
          position: 'absolute', left: '50%', bottom: 22, transform: 'translateX(-50%)',
          display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 16,
          pointerEvents: 'none'
        }}>
          <div style={{
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.12em',
            textTransform: 'uppercase', color: 'rgba(255,255,255,0.92)',
            display: 'flex', alignItems: 'center', gap: 10
          }}>
            <span style={{ display: 'inline-flex', alignItems: 'center' }}>{caption}</span>
          </div>
          {credit &&
          <div style={{
            fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.1em',
            color: 'rgba(255,255,255,0.6)'
          }}>{credit}</div>
          }
        </div>
      </div>
    </section>);

}

/* -------------------- ROUTE COLLAGE (4-panel mood band) -------------------- */

function RouteCollage({ t }) {
  const panels = t.strip.panels || [];

  // If this language has a prepared collage image (captions baked in), use it whole.
  if (t.strip.image) {
    return (
      <section style={{ position: 'relative', background: 'var(--ink)' }}>
        <img src={t.strip.image} alt={t.strip.caption} style={{
          display: 'block', width: '100%', height: 'auto'
        }} />
      </section>);

  }

  // Otherwise: build the 4-panel band with live (switchable) handwritten captions.
  // caption placement mirrors the original collage:
  // p1 upper-left, p2 upper-left, p3 lower-left, p4 mid
  const placement = [
    { top: '14%', heart: true },
    { top: '17%', heart: false },
    { bottom: '11%', heart: true },
    { top: '24%', heart: false }];


  return (
    <section style={{ position: 'relative', background: 'var(--ink)' }}>
      <div className="collage-grid" style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(4, 1fr)',
        gap: 0
      }}>
        {panels.map((cap, i) => {
          const p = placement[i] || placement[0];
          return (
            <div key={i} className="collage-panel" style={{
              position: 'relative',
              height: 'clamp(440px, 64vh, 760px)',
              overflow: 'hidden'
            }}>
              <img src={`assets/route-p${i + 1}.jpg`} alt="" style={{
                position: 'absolute', inset: 0,
                width: '100%', height: '100%',
                objectFit: 'cover', objectPosition: '50% 50%',
                display: 'block'
              }} />
              <div style={{
                position: 'absolute', inset: 0, pointerEvents: 'none',
                background: 'linear-gradient(to bottom, rgba(20,18,14,0.42) 0%, rgba(20,18,14,0.04) 26%, transparent 50%, transparent 70%, rgba(20,18,14,0.38) 100%)'
              }}></div>
              <div style={{
                position: 'absolute',
                top: p.top, bottom: p.bottom,
                left: 'clamp(18px, 2vw, 30px)', right: 'clamp(18px, 2vw, 30px)',
                color: '#fff'
              }}>
                <span style={{
                  fontFamily: 'var(--hand)',
                  fontSize: 'clamp(26px, 2.1vw, 38px)',
                  fontWeight: 500,
                  lineHeight: 1.1,
                  letterSpacing: '0.01em',
                  textShadow: '0 1px 16px rgba(0,0,0,0.5)',
                  textWrap: 'balance'
                }}>{cap}{p.heart &&
                  <span style={{ marginLeft: 8, opacity: 0.92 }}>♥</span>
                  }</span>
              </div>
            </div>);

        })}
      </div>

      <style>{`
        @media (max-width: 720px) {
          .collage-grid { grid-template-columns: repeat(2, 1fr) !important; }
          .collage-panel { height: clamp(300px, 42vh, 420px) !important; }
        }
      `}</style>
    </section>);

}

/* -------------------- DECORATIVE DOODLE -------------------- */

function Doodle({ icon: Icon, size = 70, color = 'var(--ink-faint)', opacity = 0.5, rotate = 0, style }) {
  return (
    <div aria-hidden="true" style={{
      position: 'absolute', zIndex: 0, pointerEvents: 'none',
      color, opacity, transform: `rotate(${rotate}deg)`, ...style,
    }}>
      <Icon size={size} />
    </div>);

}

/* -------------------- exports -------------------- */

Object.assign(window, {
  Eyebrow, Display, Body, Rule, Reveal, CardSurface,
  Header, Hero, Section, MoreThan, Inside, Stops,
  NotFor, Trust, Price, Faq, Footer, StickyCta, PreOrderModal,
  PrimaryButton, TempleBand, PhotoStrip, RouteCollage
});
