// Laulude Sild — Main app

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "light",
  "accent": "amber"
}/*EDITMODE-END*/;

const PROGRAM = [
  {
    num: "01",
    titleJa: "清き流れの五十鈴の川",
    sub: "〜無伴奏女声合唱による伊勢の木遣り唄〜",
    titleEn: "The Pure Stream of the Isuzu River",
    composer: "土田豊貴",
    composerEn: "Toyotaka Tsuchida",
    lang: "JP",
    by: "Sorcière",
  },
  {
    num: "02",
    titleJa: "Three Little Japanese Lyrics",
    sub: "三つの小さな日本の詩",
    titleEn: "Three Little Japanese Lyrics",
    composer: "相澤直人",
    composerEn: "Naoto Aizawa",
    lang: "JP",
    by: "Sorcière jr",
  },
  {
    num: "03",
    titleJa: "Üle heliseva ilma",
    sub: "鳴り響く世界を越えて",
    titleEn: "Across the Resounding World",
    composer: "Airi Liiva",
    composerEn: "アイリ・リーヴァ",
    lang: "EE",
    by: "Laulupesa",
  },
  {
    num: "04",
    titleJa: "Pidupäev",
    sub: "祝祭の日",
    titleEn: "Festival Day",
    composer: "Airi Liiva",
    composerEn: "アイリ・リーヴァ",
    lang: "EE",
    by: "Laulupesa",
  },
];

const ENSEMBLES = [
  {
    flag: "jp",
    country: "Japan / Mie",
    name: "合唱団 Sorcière & Sorcière jr",
    nameEn: "Choir Sorcière & Sorcière jr",
    desc: "三重を拠点とする女声合唱団。日本の風土に根ざした作品を中心に、緻密なアンサンブルと透明感のある響きで国内外の舞台に立つ。",
    stats: [
      { val: "2008", lab: "Founded" },
      { val: "三重", lab: "Mie, Japan" },
    ],
  },
  {
    flag: "ee",
    country: "Estonia / Tallinn",
    name: "合唱団 Laulupesa",
    nameEn: "Choir Laulupesa",
    desc: "「歌の巣」を意味するエストニアの合唱団。22世紀へと歌い継がれる民族の伝統と現代音楽を架橋し、北欧の透き通る響きを届ける。",
    stats: [
      { val: "EST", lab: "Tallinn" },
      { val: "22C", lab: "Toward 22nd" },
    ],
  },
  {
    flag: "jp",
    country: "Japan / Ise",
    name: "伊勢古市奉曳団",
    nameEn: "Ise Furuichi Hōeidan",
    desc: "伊勢の伝統を継ぐ奉曳団。地域の祈りと祭りに息づく木遣り唄を、合唱の架け橋へ重ね合わせる。",
    stats: [
      { val: "伊勢", lab: "Ise, Mie" },
      { val: "傳統", lab: "Tradition" },
    ],
  },
];

const GALLERY = [
  { src: "uploads/IMG_6930.jpg", pin: "ISE TOPIA", cap: "A shared afternoon after arrival" },
  { src: "uploads/IMG_6922.jpg", pin: "RECEPTION", cap: "Hospitality prepared for the singers" },
  { src: "uploads/IMG_7047.jpg", pin: "ISE JINGU", cap: "A gathering beneath the torii" },
  { src: "uploads/IMG_6940.jpg", pin: "RECEPTION", cap: "Tables filled with new conversations" },
  { src: "uploads/IMG_7089.JPG", pin: "PRESS", cap: "The exchange carried into the local paper" },
  { src: "uploads/IMG_7108.jpg", pin: "WELCOME", cap: "A warm welcome from Ise" },
  { src: "uploads/IMG_7251.jpg", pin: "TOBA", cap: "A day together by the aquarium" },
];

// ============ NAV ============
function ThemeToggle({ theme, onToggle }) {
  const isDark = theme === "dark";
  return (
    <button
      className="theme-toggle"
      type="button"
      aria-label={isDark ? "ライトモードに切り替え" : "ダークモードに切り替え"}
      aria-pressed={isDark}
      title={isDark ? "Light mode" : "Dark mode"}
      onClick={onToggle}
    >
      <span className="theme-toggle-icon" aria-hidden="true"></span>
    </button>
  );
}

function Nav({ theme, onThemeToggle }) {
  const [scrolled, setScrolled] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 24);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <nav className={`nav ${scrolled ? "scrolled" : ""}`}>
      <a href="#top" className="nav-brand">
        <span className="ja">歌の架け橋</span>
        <span className="en">Laulude Sild</span>
      </a>
      <div className="nav-links">
        <a href="#concept">趣旨</a>
        <a href="#program">プログラム</a>
        <a href="#ensembles">出演</a>
        <a href="#shows">公演情報</a>
      </div>
      <div className="nav-actions">
        <ThemeToggle theme={theme} onToggle={onThemeToggle} />
        <a href="#tickets" className="nav-cta">
          電子チケット <span className="arrow">→</span>
        </a>
      </div>
    </nav>
  );
}

// ============ HERO ============
function HeroScore() {
  // 5 staff lines that span the hero, with notes drawn at varying y positions
  const STAFF_Y = [40, 70, 100, 130, 160];
  const NOTES = [
    { x: 80, y: 130, delay: 0.6 },
    { x: 180, y: 100, delay: 0.8 },
    { x: 280, y: 70, delay: 1.0 },
    { x: 380, y: 100, delay: 1.2 },
    { x: 480, y: 130, delay: 1.4 },
    { x: 600, y: 70, delay: 1.6 },
    { x: 720, y: 40, delay: 1.8 },
    { x: 840, y: 70, delay: 2.0 },
    { x: 960, y: 100, delay: 2.2 },
    { x: 1080, y: 130, delay: 2.4 },
    { x: 1200, y: 100, delay: 2.6 },
    { x: 1320, y: 70, delay: 2.8 },
  ];
  return (
    <div className="hero-score">
      <svg viewBox="0 0 1440 220" preserveAspectRatio="none">
        <defs>
          <linearGradient id="staffGrad" x1="0" x2="1">
            <stop offset="0" stopColor="var(--amber)" stopOpacity="0.1" />
            <stop offset="0.2" stopColor="var(--amber)" stopOpacity="0.7" />
            <stop offset="0.8" stopColor="var(--amber)" stopOpacity="0.7" />
            <stop offset="1" stopColor="var(--amber)" stopOpacity="0.1" />
          </linearGradient>
        </defs>
        {/* Wavy staff lines for poetic flow */}
        {STAFF_Y.map((y, i) => (
          <path
            key={i}
            className="staff"
            d={`M -50 ${y} Q 360 ${y - 18 + i * 6}, 720 ${y} T 1490 ${y}`}
            stroke="url(#staffGrad)"
          />
        ))}
        {/* Treble clef */}
        <text x="0" y="135" fontFamily="serif" fontSize="120" fill="var(--amber-deep)" opacity="0.45" style={{ animation: "noteIn 1s ease-out 0.4s forwards", opacity: 0 }}>
          𝄞
        </text>
        {/* Notes */}
        {NOTES.map((n, i) => (
          <g key={i} className="note-hover" style={{ animationDelay: `${n.delay}s`, transformOrigin: `${n.x}px ${n.y}px` }}>
            <ellipse
              className="note"
              cx={n.x} cy={n.y} rx="9" ry="7"
              transform={`rotate(-20 ${n.x} ${n.y})`}
              style={{ animationDelay: `${n.delay}s` }}
            />
            <line
              className="note-stem"
              x1={n.x + 8} y1={n.y - 2}
              x2={n.x + 8} y2={n.y - 38}
              style={{ animation: `noteIn 0.6s ease-out ${n.delay}s forwards`, opacity: 0 }}
            />
          </g>
        ))}
      </svg>
    </div>
  );
}

function Hero() {
  const heroRef = React.useRef(null);
  const [parY, setParY] = React.useState(0);
  React.useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY;
      setParY(y * 0.15);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <section className="hero" id="top" ref={heroRef} data-screen-label="01 Hero">
      <HeroScore />
      <div style={{ transform: `translateY(${-parY}px)` }}>
        <div className="hero-meta">
          <span>2026 — A Musical Bridge</span>
          <span className="dot"></span>
          <span>Ise ⇄ Estonia</span>
          <span className="dot"></span>
          <span>One Night, Two Worlds</span>
        </div>
        <div className="hero-supertitle">
          合唱団 Sorcière & Sorcière jr ✕ 合唱団 Laulupesa（エストニア）合同演奏会
        </div>
        <h1 className="hero-title">
          <span className="word">Laulude</span>{" "}
          <span className="word">Sild</span>
        </h1>
        <div className="hero-subtitle">
          歌の<span className="accent">架け</span>橋
        </div>
      </div>

      <div className="hero-grid">
        <div className="hero-info">
          <div className="hero-tagline">
            <div>
              伊勢⇄エストニア。<br />
              <span className="em">22世紀へと続く</span>、<br />
              歌を通じた<span className="em">相互的文化交流</span>プロジェクト。
            </div>
            <div className="hero-tagline-en">
              A reciprocal cultural exchange — sung from Ise to Tallinn, carried into the next century.
            </div>
          </div>
          <div className="hero-cta-row">
            <a href="#tickets" className="btn-primary">
              電子チケットを購入 <span className="arrow">→</span>
            </a>
            <a href="#program" className="btn-ghost">
              プログラムを見る
            </a>
          </div>
        </div>

        <aside className="hero-card">
          <div className="label">Concert Date</div>
          <div className="date-big">
            26 <span className="month">.iii</span>
          </div>
          <div className="date-ja">2026年 3月26日（木）</div>
          <div className="venue">
            いせトピア 多目的ホール
            <div className="venue-en">Ise Topia Multi-Purpose Hall</div>
          </div>
        </aside>
      </div>
    </section>
  );
}

// ============ MARQUEE ============
function Marquee() {
  const items = [
    "歌の架け橋", "Laulude Sild", "Ise ⇄ Estonia", "22世紀へ続く相互文化交流",
    "Sorcière & Sorcière jr", "Laulupesa", "伊勢古市奉曳団",
    "A Bridge of Songs", "March 26, 2026", "いせトピア多目的ホール",
  ];
  const track = [...items, ...items];
  return (
    <div className="marquee" aria-hidden="true">
      <div className="marquee-track">
        {track.map((t, i) => (
          <span key={i}>
            {t} <span className="star">✦</span>
          </span>
        ))}
      </div>
    </div>
  );
}

// ============ CONCEPT ============
function Concept() {
  return (
    <section className="block concept" id="concept" data-screen-label="02 Concept">
      <div className="concept-grid">
        <div className="concept-body reveal">
          <div className="eyebrow">
            <span className="num">— 02</span>
            <span className="bar"></span>
            <span>Concept / 趣旨</span>
          </div>
          <h2 className="section-title">
            一つの河は、海へ。<br />
            一つの歌は、世界へ。
            <span className="en">One river to the sea — one song to the world.</span>
          </h2>
          <p>
            五十鈴の清き流れに耳を澄ませてきた伊勢の唄と、バルト海の風に育まれた
            エストニアの歌声。遠く離れた二つの土地は、それぞれの祈りと祝祭を
            「歌」というかたちで永く守り続けてきました。
          </p>
          <p>
            この演奏会では、伊勢の木遣り唄からエストニアの現代合唱作品まで、
            女声合唱を通じて二つの文化を一つの舞台で響き合わせます。
            22世紀へと続く、人と人をつなぐ歌の架け橋を——。
          </p>
          <div className="signature">
            ― Laulude Sild, the bridge of songs.
          </div>
        </div>

        <BridgeMap />
      </div>
    </section>
  );
}

function BridgeMap() {
  return (
    <div className="bridge-map reveal">
      <div className="bridge-pin top">
        <div className="coord">59.4370° N · 24.7536° E</div>
        <div className="name-en">Tallinn</div>
        <div className="name-ja">タリン — エストニア</div>
        <div className="meta">Laulupesa — Choir of Songs</div>
      </div>

      <div className="bridge-line">
        <svg width="240" height="320" viewBox="0 0 240 320">
          <path
            className="path"
            d="M 200 20 Q 80 80, 140 160 T 40 300"
          />
          <circle className="marker" cx="200" cy="20" r="5" />
          <circle className="marker" cx="40" cy="300" r="5" />
          <text x="125" y="135" textAnchor="middle" className="label">
            8,400 km
          </text>
          <text x="125" y="155" textAnchor="middle" className="label-distance">
            of singing distance
          </text>
        </svg>
      </div>

      <div className="bridge-pin bot">
        <div className="coord">34.4900° N · 136.7100° E</div>
        <div className="name-en">Ise</div>
        <div className="name-ja">伊勢 — 三重</div>
        <div className="meta">Sorcière · 古市奉曳団</div>
      </div>
    </div>
  );
}

// ============ PROGRAM ============
function Program() {
  return (
    <section className="block program" id="program" data-screen-label="03 Program">
      <div className="eyebrow reveal">
        <span className="num">— 03</span>
        <span className="bar"></span>
        <span>Program / 演奏曲目</span>
      </div>
      <h2 className="section-title reveal">
        二つの国の、四つの祈り。
        <span className="en">Four prayers, two homelands.</span>
      </h2>
      <p className="lede reveal">
        伊勢の木遣り、相澤直人による日本の小品、そしてエストニアの作曲家
        Airi Liiva の現代合唱曲——女声合唱のための四曲を、ひと夜にて。
      </p>

      <div className="program-list">
        {PROGRAM.map((p, i) => (
          <div className="program-item reveal" key={i}>
            <div className="program-num">{p.num}</div>
            <div className="program-titles">
              <div className="program-title-ja">
                {p.titleJa}
                {p.sub && <span style={{ display: "block", fontSize: "0.7em", color: "var(--fg-3)", marginTop: 4, fontWeight: 400 }}>{p.sub}</span>}
              </div>
              <div className="program-title-en">
                {p.titleEn}
                <span className="lang-tag">{p.lang}</span>
              </div>
            </div>
            <div className="program-meta">
              <span className="composer">{p.composer}</span>
              <span>{p.composerEn}</span>
            </div>
          </div>
        ))}
      </div>

      <div className="program-note reveal">
        曲目は変更の可能性があります。昼公演と夜公演では異なる曲目をお届けする予定です。
      </div>
    </section>
  );
}

// ============ ENSEMBLES ============
function Ensembles() {
  return (
    <section className="block ensembles" id="ensembles" data-screen-label="04 Ensembles">
      <div className="eyebrow reveal">
        <span className="num">— 04</span>
        <span className="bar"></span>
        <span>Ensembles / 出演団体</span>
      </div>
      <h2 className="section-title reveal">
        三つの声、ひとつの舞台。
        <span className="en">Three voices, one stage.</span>
      </h2>

      <div className="ensemble-grid">
        {ENSEMBLES.map((e, i) => (
          <article className="ensemble-card reveal" key={i}>
            <div className="corner"></div>
            <div className="country">
              <span className={`flag ${e.flag}`}></span>
              {e.country}
            </div>
            <div>
              <div className="name">{e.name}</div>
              <div className="name-en">{e.nameEn}</div>
            </div>
            <p className="desc">{e.desc}</p>
            <div className="stat-row">
              {e.stats.map((s, j) => (
                <div key={j}>
                  <strong>{s.val}</strong>
                  {s.lab}
                </div>
              ))}
            </div>
          </article>
        ))}
      </div>
    </section>
  );
}

// ============ SHOWS ============
function Shows() {
  return (
    <section className="block shows" id="shows" data-screen-label="05 Shows">
      <div className="eyebrow reveal">
        <span className="num">— 05</span>
        <span className="bar"></span>
        <span>Performance / 公演情報</span>
      </div>
      <h2 className="section-title reveal">
        2026 年 3 月 26 日（木）<br />
        昼の部・夜の部、二公演。
        <span className="en">Two shows, one evening of song.</span>
      </h2>

      <div className="shows-grid">
        <article className="show-card reveal">
          <div className="show-tag">Matinée / 昼公演</div>
          <div className="show-name">
            昼の部
            <span className="en">Afternoon Programme</span>
          </div>
          <div className="show-times">
            <div className="show-time">
              <span className="label">開場 / Doors</span>
              <span className="time">14:30</span>
            </div>
            <div className="show-time">
              <span className="label">開演 / Curtain</span>
              <span className="time">15:00</span>
            </div>
          </div>
          <div className="show-bottom">
            <div style={{ fontFamily: "var(--serif-ja)", color: "var(--fg-2)", fontSize: 14, lineHeight: 1.7 }}>
              いせトピア 多目的ホール<br />
              <span style={{ fontFamily: "var(--serif-en)", fontStyle: "italic", color: "var(--fg-3)", fontSize: 13 }}>
                Ise Topia Multi-Purpose Hall
              </span>
            </div>
            <div className="show-icon">☼</div>
          </div>
        </article>

        <article className="show-card reveal">
          <div className="show-tag">Soirée / 夜公演</div>
          <div className="show-name">
            夜の部
            <span className="en">Evening Programme</span>
          </div>
          <div className="show-times">
            <div className="show-time">
              <span className="label">開場 / Doors</span>
              <span className="time">17:00</span>
            </div>
            <div className="show-time">
              <span className="label">開演 / Curtain</span>
              <span className="time">17:30</span>
            </div>
          </div>
          <div className="show-bottom">
            <div style={{ fontFamily: "var(--serif-ja)", color: "var(--fg-2)", fontSize: 14, lineHeight: 1.7 }}>
              いせトピア 多目的ホール<br />
              <span style={{ fontFamily: "var(--serif-en)", fontStyle: "italic", color: "var(--fg-3)", fontSize: 13 }}>
                Ise Topia Multi-Purpose Hall
              </span>
            </div>
            <div className="show-icon">☾</div>
          </div>
        </article>
      </div>

      <div className="pricing reveal">
        <div className="pricing-cell">
          <div className="label">大人 / Adult</div>
          <div className="amount">1,000<span className="yen">円</span></div>
          <div className="sub">両公演同一料金</div>
        </div>
        <div className="pricing-cell">
          <div className="label">学生 / Student</div>
          <div className="amount">500<span className="yen">円</span></div>
          <div className="sub">両公演同一料金</div>
        </div>
        <div className="pricing-cell">
          <div className="label">小学生以下 / Children</div>
          <div className="amount" style={{ color: "var(--amber-deep)", fontStyle: "italic" }}>無料</div>
          <div className="sub">Free admission</div>
        </div>
      </div>
    </section>
  );
}

// ============ GALLERY ============
function Gallery() {
  return (
    <section className="gallery-block" data-screen-label="06 Gallery">
      <div className="section-head">
        <div className="reveal">
          <div className="eyebrow">
            <span className="num">— 06</span>
            <span className="bar"></span>
            <span>From the bridge / 橋のたもとから</span>
          </div>
          <h2 className="section-title">
            風景もまた、<br />ひとつの歌である。
            <span className="en">Landscapes that also sing.</span>
          </h2>
        </div>
      </div>
      <div className="gallery-strip">
        {GALLERY.map((g, i) => (
          <div className={`gallery-card ${i % 3 === 1 ? "tall" : ""}`} key={i}>
            <img src={g.src} alt={g.cap} loading="lazy" />
            <div className="cap">
              <span className="pin">{g.pin}</span>
              {g.cap}
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

// ============ CTA + FOOTER ============
function CtaAndFooter() {
  return (
    <>
      <section className="cta-block" id="tickets" data-screen-label="07 Tickets">
        <div className="eyebrow">
          <span className="bar" style={{ background: "#d4a04a" }}></span>
          <span>Tickets / 電子チケット</span>
          <span className="bar" style={{ background: "#d4a04a" }}></span>
        </div>
        <h2 className="cta-title">
          一夜限りの「歌の架け橋」へ。
          <span className="en">Cross the bridge with us.</span>
        </h2>
        <p className="cta-sub">
          電子チケットでご来場をお待ちしております。<br />
          会場にて、伊勢とエストニアの声をひとつに——。
        </p>
        <div style={{ display: "flex", gap: 14, justifyContent: "center", flexWrap: "wrap" }}>
          <a href="#" className="btn-primary">
            電子チケットを購入 <span className="arrow">→</span>
          </a>
          <a href="#" className="btn-ghost">
            お問い合わせ
          </a>
        </div>
      </section>

      <footer className="footer">
        <div className="footer-top">
          <div className="footer-brand">
            <div className="ja">Laulude Sild — 歌の架け橋</div>
            <div className="en">A Bridge of Songs · Ise ⇄ Estonia · 2026</div>
            <p style={{ fontFamily: "var(--serif-ja)", color: "rgba(245,230,208,0.6)", fontSize: 13, marginTop: 16, lineHeight: 1.9, maxWidth: "32ch" }}>
              合唱団 Sorcière & Sorcière jr × 合唱団 Laulupesa（エストニア）<br />
              合同演奏会
            </p>
          </div>
          <div className="footer-col">
            <h5>後援 / Patrons</h5>
            <p>伊勢市</p>
            <p>伊勢市教育委員会</p>
            <p>三重県合唱連盟</p>
          </div>
          <div className="footer-col">
            <h5>お問い合わせ / Contact</h5>
            <a href="mailto:contact@choirsorciere.com">合唱団 Sorcière & Sorcière jr</a>
            <a href="mailto:contact@choirsorciere.com">contact@choirsorciere.com</a>
          </div>
          <div className="footer-col">
            <h5>Follow</h5>
            <a href="#">@sorciere_chorus</a>
            <a href="#">Instagram</a>
            <a href="#">Press kit</a>
          </div>
        </div>
        <div className="footer-bottom">
          <span>© 2026 Choir Sorcière. All songs reserved.</span>
          <span>Laulude Sild — 歌の架け橋</span>
        </div>
      </footer>
    </>
  );
}

// ============ TWEAKS ============
function Tweaks({ tweaks, setTweak }) {
  return (
    <TweaksPanel>
      <TweakSection label="Theme / テーマ">
        <TweakRadio
          label="Mode"
          value={tweaks.theme}
          options={[
            { value: "light", label: "Light" },
            { value: "dark", label: "Dark" },
          ]}
          onChange={(v) => setTweak("theme", v)}
        />
      </TweakSection>
      <TweakSection label="Accent / 主色">
        <TweakRadio
          label="Color"
          value={tweaks.accent}
          options={[
            { value: "amber", label: "琥珀" },
            { value: "vermillion", label: "朱" },
            { value: "indigo", label: "藍" },
          ]}
          onChange={(v) => setTweak("accent", v)}
        />
      </TweakSection>
    </TweaksPanel>
  );
}

const ACCENT_PRESETS = {
  amber: {
    light: { amber: "#c08442", amber2: "#a86530", amberDeep: "#8a4a1f", gold: "#d4a04a" },
    dark:  { amber: "#d99a55", amber2: "#e2a866", amberDeep: "#f0bc7e", gold: "#e6b85a" },
  },
  vermillion: {
    light: { amber: "#c74a3a", amber2: "#a83830", amberDeep: "#7a261c", gold: "#d4724a" },
    dark:  { amber: "#e26350", amber2: "#ef806f", amberDeep: "#ffb2a5", gold: "#e7966d" },
  },
  indigo: {
    light: { amber: "#3a5a8a", amber2: "#2c4870", amberDeep: "#1f3358", gold: "#6a8aaa" },
    dark:  { amber: "#7fa5e8", amber2: "#9dbcf1", amberDeep: "#c4d7ff", gold: "#91aee0" },
  },
};

function applyTheme(theme, accent) {
  document.documentElement.dataset.theme = theme;
  const preset = ACCENT_PRESETS[accent] || ACCENT_PRESETS.amber;
  const a = preset[theme] || preset.light;
  const root = document.documentElement.style;
  root.setProperty("--amber", a.amber);
  root.setProperty("--amber-2", a.amber2);
  root.setProperty("--amber-deep", a.amberDeep);
  root.setProperty("--gold", a.gold);
}

function getPreferredTheme() {
  try {
    const stored = window.localStorage.getItem("laulude-theme");
    if (stored === "light" || stored === "dark") return stored;
  } catch (e) {}
  if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
    return "dark";
  }
  return TWEAK_DEFAULTS.theme || "light";
}

function storeTheme(theme) {
  try {
    window.localStorage.setItem("laulude-theme", theme);
  } catch (e) {}
}

// ============ APP ============
function App() {
  const [tweaks, setBaseTweak] = useTweaks({
    ...TWEAK_DEFAULTS,
    theme: getPreferredTheme(),
  });

  const setTweak = React.useCallback((keyOrEdits, val) => {
    const edits = typeof keyOrEdits === "object" && keyOrEdits !== null
      ? keyOrEdits
      : { [keyOrEdits]: val };
    if (edits.theme === "light" || edits.theme === "dark") storeTheme(edits.theme);
    setBaseTweak(edits);
  }, [setBaseTweak]);

  const toggleTheme = React.useCallback(() => {
    setTweak("theme", tweaks.theme === "dark" ? "light" : "dark");
  }, [setTweak, tweaks.theme]);

  React.useEffect(() => {
    applyTheme(tweaks.theme, tweaks.accent);
  }, [tweaks.theme, tweaks.accent]);

  // Scroll reveal
  React.useEffect(() => {
    const els = document.querySelectorAll(".reveal");
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("in");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.12 }
    );
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  }, []);

  return (
    <>
      <Nav theme={tweaks.theme} onThemeToggle={toggleTheme} />
      <Hero />
      <Marquee />
      <Concept />
      <Program />
      <Ensembles />
      <Shows />
      <Gallery />
      <CtaAndFooter />
      <Tweaks tweaks={tweaks} setTweak={setTweak} />
    </>
  );
}

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