// dm-landing.jsx — Gal Pal Society × Dyke March 2026 signup landing page.
// Lilac brand primary + rainbow accents · warm community tone · bilingual (中 / EN).
// All copy lives in COPY[lang]; language toggle persists to localStorage + ?lang.
// Visual "direction" (editorial / wave / pastel) is driven by a root class + tokens.
// Exports DykeMarchLanding to window.

const ZEFFY_URL = 'https://www.zeffy.com/en-CA/ticketing/2026-dyke-march-join-our-chinese-speaking-queer-womens-group-dyke-march-allies-are-welcomed';
const ZEFFY_EMBED = 'https://www.zeffy.com/embed/ticketing/2026-dyke-march-join-our-chinese-speaking-queer-womens-group-dyke-march-allies-are-welcomed';
const LINKTREE_URL = 'https://linktr.ee/gps.galpalsociety';

// ---- bilingual copy ----
const COPY = {
  zh: {
    nav: { about: '关于活动', schedule: '当天流程', bring: '出发准备', gift: '限定丝巾', register: '立即报名', gallery: '往期回顾' },
    cta: '立即报名',
    hero: {
      sub: { a: '和懂你的人一起，走进', em: '属于我们的六月', b: '。' },
      slogan: 'Gals Together, Pals Forever!',
      date: '6 月 27 日', dateNote: '周六', time: '1:00 – 6:00 PM', place: 'Church St. & Hayden St.',
    },
    about: {
      eyebrow: '关于这次游行',
      h: { a: '第四年，\n我们一起', accent: '走上街头', b: '。' },
      lead: '作为华语 Queer Women，当我们一起走上街头，「可见性」便不再只是被代表，而是一种宣告：我们也属于这里。我们的故事、文化、语言与身份，都理应在更广阔的酷儿社区中拥有一席之地。',
      stat: '4 年 · 50+ 场活动 · 两个微信群',
      p2a: '这次我们想和大家一起重新思考：作为华语 queer women，大家希望以什么样的方式被看见？又想共同建立一个怎样的 community？也许这些问题没有标准答案，',
      p2em: '但只要你愿意来到这里，你本身就是答案的一部分。',
      p3: '不管你是从第一年就跟着 G.P.S. 的老朋友，还是刚刚找到我们的新面孔，在 6 月 27 日这一天，大家都在同一个空间里，一起展现属于这个 community 的样子。',
    },
    schedule: {
      eyebrow: '当天流程',
      h: { a: '6 月 27 日，我们这样度过' },
      sub: '1:00 pm 在 Church & Hayden 集合，2:00 pm 游行准时出发。',
      steps: [
        { t: '1:00 – 2:00 PM', emoji: '🎤', h: 'Rally 集合 + 起点演讲', d: '在起点集合，现场有 performance & 演讲，一起为今天暖场。', loc: 'Church St. & Hayden St.（集合点 / 起点）' },
        { t: '2:00 – 3:00 PM', emoji: '🏳️‍🌈', h: 'Dyke March 游行', d: '队伍正式出发，带上你的旗帜与标语，一起走上街头。', loc: '起点 Church & Hayden → 终点 Church & Gould' },
        { t: '3:00 – 5:00 PM', emoji: '☀️', h: '草坪 Social Time', d: '游行后在草坪上歇脚、晒太阳、认识新朋友，慢慢聊。' },
        { t: '5:30 PM', emoji: '🍻', h: '自由结伴时间', d: '意犹未尽？结伴去吃点东西，把这一天延续下去。' },
      ],
    },
    bring: {
      eyebrow: '出发前准备',
      h: { a: '轻装上阵，', accent: '一起热闹' },
      sub: '现场将提供彩虹贴纸、纹身贴和彩虹旗，也欢迎大家以最“Pride”的方式装扮自己！六月天气较晒，记得带水和做好防晒措施。',
      items: [
        { icon: 'rainbow', label: '彩虹贴纸 · 纹身贴', tag: '现场提供' },
        { icon: 'flag', label: '彩虹旗', tag: '现场提供 / 自带' },
        { icon: 'sign', label: '标语牌', tag: '欢迎自带' },
        { icon: 'bottle', label: '饮用水', tag: '请务必带好' },
        { icon: 'sun', label: '防晒防暑', tag: '帽子 · 防晒霜' },
        { icon: 'steps', label: '舒适鞋子', tag: '要走一段路' },
      ],
    },
    scarf: {
      eyebrow: '限定礼物',
      h: { a: '一点小小的\n', accent: '感谢心意' },
      body: { a: '选择「Cover-the-Cost」或以上档位报名，', strong: '前 30 位', b: '伙伴可获 G.P.S. 2026 限定纪念丝巾一条。' },
      checklist: ['选择 cover 活动成本的档位及以上', '作为感谢，送给前 30 位伙伴', '一巾多戴，把这一天戴在身上'],
      tiles: [
        { src: 'assets/scarf-head.jpg', label: '头巾系法' },
        { src: 'assets/scarf-neck.jpg', label: '颈间丝巾' },
        { src: 'assets/scarf-bag.jpg', label: '包饰系法' },
        { src: 'assets/scarf-wrist.jpg', label: '手腕系法' },
      ],
      heroAlt: 'G.P.S. 限定纪念丝巾',
      cta: '前往报名支持',
    },
    register: {
      eyebrow: '立即报名',
      h: { a: '和我们一起', accent: '走上街头' },
      sub: '在下方完成报名，Allies are welcome。',
      fallback: '报名表单未能在此页面显示？',
      fallbackBtn: '前往 Zeffy 报名页面',
      openBtn: '在新页面打开报名表',
      note: '报名与支持均由 Zeffy 安全托管',
    },
    gallery: {
      eyebrow: '往期回顾',
      h: { a: '我们一起走过的', accent: '那些路' },
      sub: '往期 Dyke March 的瞬间。同一面横幅，和懂你的人一起走上街头。',
    },
    footer: {
      brand: '一个属于华语 queer women 的空间。它仍在成长，也并不完美，但正在被一群人认真地维护与陪伴着。',
      infoH: '活动信息',
      info: ['6 月 27 日（周六）1–6 PM', 'Church St. & Hayden St. 集合', '华语 Queer Women & Allies'],
      joinH: '一起加入',
      joinP: '还没找到我们？所有报名、社群与活动入口都在我们的 Link Tree 里，欢迎随时来找我们。',
      joinBtn: '打开 G.P.S. Link Tree',
      bottom: '和懂你的人一起，这个六月，属于我们 🏳️‍🌈',
    },
  },
  en: {
    nav: { about: 'About', schedule: 'Schedule', bring: 'What to Bring', gift: 'Scarf', register: 'Register', gallery: 'Gallery' },
    cta: 'Register',
    hero: {
      sub: { a: 'With people who get you, ', em: 'this June is ours', b: '.' },
      slogan: 'Gals Together, Pals Forever!',
      date: 'June 27', dateNote: 'Sat', time: '1:00 – 6:00 PM', place: 'Church St. & Hayden St.',
    },
    about: {
      eyebrow: 'About the march',
      h: { a: 'Four years in,\n', accent: 'out', b: ' in the streets, together.' },
      lead: 'As Chinese-speaking queer women, when we take to the streets together, visibility is no longer about being represented — it’s a declaration: we belong here too. Our stories, cultures, languages and identities all deserve a place in the wider queer community.',
      stat: '4 years · 50+ events · two WeChat groups',
      p2a: 'This year, we want to rethink together: as Chinese-speaking queer women, how do we want to be seen? And what kind of community do we want to build? Maybe there\u2019s no single answer, ',
      p2em: 'but just by being here, you\u2019re already part of it.',
      p3: 'Whether you\u2019ve marched with G.P.S. since year one or just found us, on June 27 we\u2019re all in the same space, showing the world what this community looks like.',
    },
    schedule: {
      eyebrow: 'Schedule',
      h: { a: 'How June 27 unfolds' },
      sub: 'Meet at Church & Hayden at 1:00 PM; the march sets off at 2:00 PM sharp.',
      steps: [
        { t: '1:00 – 2:00 PM', emoji: '🎤', h: 'Rally + opening', d: 'Gather at the start point for performances & speeches, warming up together.', loc: 'Church St. & Hayden St. (meet / start)' },
        { t: '2:00 – 3:00 PM', emoji: '🏳️‍🌈', h: 'Dyke March', d: 'The march sets off, so bring your flags and signs and take to the streets.', loc: 'Starts Church & Hayden → ends Church & Gould' },
        { t: '3:00 – 5:00 PM', emoji: '☀️', h: 'Lawn social time', d: 'Rest on the lawn, soak up the sun, and meet new friends.' },
        { t: '5:30 PM', emoji: '🍻', h: 'Off together', d: 'Not ready to end? Grab a bite together and keep the day going.' },
      ],
    },
    bring: {
      eyebrow: 'What to bring',
      h: { a: 'Come light,', accent: ' show up loud' },
      sub: 'We’ll have rainbow stickers, temporary tattoos and flags on site — come dressed in your most “Pride” self! It’s hot in June, so bring water and sun protection.',
      items: [
        { icon: 'rainbow', label: 'Stickers · tattoos', tag: 'Provided on site' },
        { icon: 'flag', label: 'Rainbow flag', tag: 'Provided / bring own' },
        { icon: 'sign', label: 'Sign / banner', tag: 'Bring your own' },
        { icon: 'bottle', label: 'Water', tag: 'Please bring' },
        { icon: 'sun', label: 'Sun care', tag: 'Hat · sunscreen' },
        { icon: 'steps', label: 'Comfy shoes', tag: 'We\u2019ll walk a bit' },
      ],
    },
    scarf: {
      eyebrow: 'A little thank-you',
      h: { a: 'A small\n', accent: 'thank-you' },
      body: { a: 'Pick the Cover-the-Cost Ticket or above, and the ', strong: 'first 30 friends', b: ' get a G.P.S. 2026 limited keepsake scarf.' },
      checklist: ['For the Cover-the-Cost Ticket & above', 'A thank-you for the first 30 friends', 'One scarf, many ways to wear it'],
      tiles: [
        { src: 'assets/scarf-head.jpg', label: 'As a headscarf' },
        { src: 'assets/scarf-neck.jpg', label: 'Around the neck' },
        { src: 'assets/scarf-bag.jpg', label: 'On a bag' },
        { src: 'assets/scarf-wrist.jpg', label: 'On the wrist' },
      ],
      heroAlt: 'G.P.S. limited keepsake scarf',
      cta: 'Register & support',
    },
    register: {
      eyebrow: 'Register',
      h: { a: 'March', accent: ' with us' },
      sub: 'Complete your registration below. Allies are welcome.',
      fallback: 'Form not showing on this page?',
      fallbackBtn: 'Open the Zeffy page',
      openBtn: 'Open registration in a new tab',
      note: 'Registration & support securely hosted by Zeffy',
    },
    gallery: {
      eyebrow: 'Looking back',
      h: { a: 'The streets we’ve walked', accent: ' together' },
      sub: 'Moments from a past Dyke March. Out on the streets with people who get you.',
    },
    footer: {
      brand: 'A space for Chinese-speaking queer women. Still growing and not perfect, but cared for and kept alive by a community that shows up.',
      infoH: 'Event info',
      info: ['Sat, June 27 · 1–6 PM', 'Meet at Church St. & Hayden St.', 'Chinese-speaking queer women & allies'],
      joinH: 'Join us',
      joinP: 'Haven\u2019t found us yet? Every link (registration, community, events) lives on our Link Tree. Come say hi any time.',
      joinBtn: 'Open the G.P.S. Link Tree',
      bottom: 'With people who get you. This June is ours 🏳️‍🌈',
    },
  },
};

// render a heading object {a, accent, b}; '\n' in a/b becomes <br/>
const withBreaks = (str) => str.split('\n').map((part, i, arr) => (
  <React.Fragment key={i}>{part}{i < arr.length - 1 && <br />}</React.Fragment>
));
const Heading = ({ h, className }) => (
  <h2 className={className}>
    {withBreaks(h.a)}
    {h.accent && <span className="dm-accent">{h.accent}</span>}
    {h.b && withBreaks(h.b)}
  </h2>
);

// ---- icons (line glyphs, currentColor) ----
const DMIcon = ({ name, size = 24, stroke = 1.6 }) => {
  const paths = {
    arrow: <><path d="M5 12h13" /><path d="m13 6 6 6-6 6" /></>,
    chevron: <path d="m6 9 6 6 6-6" />,
    pin: <><path d="M12 21s6-5.3 6-10a6 6 0 1 0-12 0c0 4.7 6 10 6 10z" /><circle cx="12" cy="11" r="2.2" /></>,
    clock: <><circle cx="12" cy="12" r="8.5" /><path d="M12 7.5V12l3 2" /></>,
    calendar: <><rect x="4" y="5.5" width="16" height="15" rx="2.5" /><path d="M4 9.5h16M8.5 3.5v4M15.5 3.5v4" /></>,
    mic: <><rect x="9" y="3" width="6" height="11" rx="3" /><path d="M6 11a6 6 0 0 0 12 0M12 17v4M9 21h6" /></>,
    flag: <><path d="M6 21V4M6 4.5c3-1.6 6 1.6 9 0V13c-3 1.6-6-1.6-9 0" /></>,
    leaf: <><path d="M5 19c0-7 5-12 14-13 .5 7-3.5 13-11 13-1.2 0-3-.4-3-3z" /><path d="M9 15c2-2.5 4.2-4 7-5" /></>,
    bowl: <><path d="M3.5 11h17a8.5 8.5 0 0 1-17 0z" /><path d="M12 11V8M12 5.5v.01M9 8c0-1.5 1-1.5 1.5-2.5M15 8c0-1.5-1-1.5-1.5-2.5" /></>,
    gift: <><rect x="4" y="9" width="16" height="11" rx="1.5" /><path d="M3 9h18M12 9v11M12 9S10.5 4.5 8.5 4.5 6 7 8 9m4 0s1.5-4.5 3.5-4.5S18 7 16 9" /></>,
    spark: <path d="M12 4c.6 3.2 1.8 4.4 5 5-3.2.6-4.4 1.8-5 5-.6-3.2-1.8-4.4-5-5 3.2-.6 4.4-1.8 5-5z" />,
    heart: <path d="M12 20s-7-4.6-7-9.3C5 8.1 6.8 6.4 9 6.4c1.4 0 2.5.7 3 1.8.5-1.1 1.6-1.8 3-1.8 2.2 0 4 1.7 4 4.3C19 15.4 12 20 12 20z" />,
    ig: <><rect x="4" y="4" width="16" height="16" rx="4.5" /><circle cx="12" cy="12" r="3.3" /><circle cx="17" cy="7" r="0.6" fill="currentColor" stroke="none" /></>,
    mail: <><rect x="3.5" y="5.5" width="17" height="13" rx="2" /><path d="m4 7 8 6 8-6" /></>,
    chat: <><path d="M4 5.5h16v10H9l-4 3.5V15.5H4z" /><circle cx="9.5" cy="10.5" r="0.7" fill="currentColor" stroke="none" /><circle cx="14.5" cy="10.5" r="0.7" fill="currentColor" stroke="none" /></>,
    check: <path d="m5 12.5 4.5 4.5L19 7" />,
    users: <><circle cx="9" cy="8.5" r="3" /><path d="M3.5 19c0-3 2.5-5 5.5-5s5.5 2 5.5 5" /><path d="M16 6.2a3 3 0 0 1 0 5.6M16.5 14c2.4.3 4 2.3 4 5" /></>,
    sun: <><circle cx="12" cy="12" r="4" /><path d="M12 2.5v2.5M12 19v2.5M4.2 4.2l1.8 1.8M18 18l1.8 1.8M2.5 12H5M19 12h2.5M4.2 19.8 6 18M18 6l1.8-1.8" /></>,
    bottle: <><path d="M9.5 3.5h5M10 3.5V6c0 .8-.4 1.3-1 2-.7.8-1 1.6-1 2.8V19a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-8.2c0-1.2-.3-2-1-2.8-.6-.7-1-1.2-1-2V3.5" /><path d="M8 13h8" /></>,
    fan: <><path d="M12 20 5 9a8 8 0 0 1 14 0l-7 11z" /><path d="M12 20V6M8.5 7.2 12 20M15.5 7.2 12 20" /></>,
    sticker: <><path d="M14 3.5H6.5A2.5 2.5 0 0 0 4 6v12a2.5 2.5 0 0 0 2.5 2.5h7.5L20 14V6a2.5 2.5 0 0 0-2.5-2.5z" /><path d="M14 20.5V16a2 2 0 0 1 2-2h4.5" /><path d="M8 9.5c.8-1 2.2-1 3 0M13 9.5c.8-1 2.2-1 3 0" /></>,
    rainbow: <><path d="M3 18a9 9 0 0 1 18 0" /><path d="M6.5 18a5.5 5.5 0 0 1 11 0" /><path d="M10 18a2 2 0 0 1 4 0" /></>,
    sign: <><rect x="5" y="3.5" width="14" height="10" rx="1.5" /><path d="M12 13.5V21" /><path d="M8.5 7.5h7M8.5 10h4" /></>,
    shoe: <><path d="M3 16.5h11l4.5 1.5c1.5.5 2.5.5 2.5-1.5 0-1-.6-1.6-1.8-2L13 11l-2-3.5-2 1 .5 2.5-3 1.5z" /><path d="M3 16.5V18h19" /></>,
    link: <><path d="M9.5 14.5 14.5 9.5" /><path d="M11 7.5 12.6 6a3.5 3.5 0 0 1 5 5l-1.6 1.5" /><path d="M13 16.5 11.4 18a3.5 3.5 0 0 1-5-5L8 11.5" /></>,
  };
  // 'steps' is a filled Material glyph on a 0 -960 960 960 grid — render it on its own
  if (name === 'steps') {
    return (
      <svg width={size} height={size} viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true">
        <path d="M216-580q39 0 74 14t64 41l382 365h24q17 0 28.5-11.5T800-200q0-8-1.5-17T788-235L605-418l-71-214-74 18q-38 10-69-14t-31-63v-84l-28-14-154 206q-1 1-1 1.5t-1 1.5h40Zm0 80h-46q3 7 7.5 13t10.5 11l324 295q11 11 25 16t29 5h54L299-467q-17-17-38.5-25t-44.5-8ZM566-80q-30 0-57-11t-50-31L134-417q-46-42-51.5-103T114-631l154-206q17-23 45.5-30.5T368-861l28 14q21 11 32.5 30t11.5 42v84l74-19q30-8 58 7.5t38 44.5l65 196 170 170q20 20 27.5 43t7.5 49q0 50-35 85t-85 35H566Z" />
      </svg>
    );
  }
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      {paths[name]}
    </svg>
  );
};

// ---- decorative rainbow wave (abstract painterly band, echoes the poster) ----
const RainbowWave = ({ flip = false }) => (
  <div className={`dm-wave${flip ? ' dm-wave-flip' : ''}`} aria-hidden="true">
    <svg viewBox="0 0 1440 160" preserveAspectRatio="none">
      <path className="w1" d="M0,70 C320,10 560,120 760,70 C980,16 1200,110 1440,52 L1440,0 L0,0 Z" />
      <path className="w2" d="M0,92 C320,34 560,140 760,92 C980,40 1200,130 1440,76 L1440,0 L0,0 Z" />
      <path className="w3" d="M0,116 C320,60 560,160 760,116 C980,66 1200,150 1440,102 L1440,0 L0,0 Z" />
      <path className="w4" d="M0,140 C320,86 560,176 760,140 C980,92 1200,168 1440,128 L1440,0 L0,0 Z" />
    </svg>
  </div>
);

// ---- reusable bits ----
const Btn = ({ children, href = '#', variant = 'primary', arrow = true }) => (
  <a href={href} className={`dm-btn dm-btn-${variant}`}
    {...(href.startsWith('http') ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>
    <span>{children}</span>
    {arrow && <DMIcon name="arrow" size={17} stroke={1.9} />}
  </a>
);

const Eyebrow = ({ en, cn }) => (
  <div className="dm-eyebrow">
    <span className="dm-eyebrow-rule" />
    <span className="dm-eyebrow-en">{en}</span>
    {cn && <span className="dm-eyebrow-cn">{cn}</span>}
  </div>
);

// ---- sections ----
function Header({ c, lang, setLang }) {
  return (
    <header className="dm-header">
      <a href="#top" className="dm-logo">
        <img src="assets/gps-logo-primary.png" alt="Gal Pal Society" className="dm-logo-img" />
      </a>
      <nav className="dm-nav">
        <a href="#about" className="dm-nav-link">{c.nav.about}</a>
        <a href="#schedule" className="dm-nav-link">{c.nav.schedule}</a>
        <a href="#bring" className="dm-nav-link">{c.nav.bring}</a>
        <a href="#gift" className="dm-nav-link">{c.nav.gift}</a>
      </nav>
      <div className="dm-header-right">
        <div className="dm-langtoggle" role="group" aria-label="Language">
          <button type="button" className={lang === 'zh' ? 'on' : ''} onClick={() => setLang('zh')} aria-pressed={lang === 'zh'}>中文</button>
          <button type="button" className={lang === 'en' ? 'on' : ''} onClick={() => setLang('en')} aria-pressed={lang === 'en'}>EN</button>
        </div>
        <a href={ZEFFY_URL} target="_blank" rel="noopener noreferrer" className="dm-btn dm-btn-primary dm-btn-sm dm-header-cta"><span>{c.cta}</span></a>
      </div>
    </header>
  );
}

function Hero({ c }) {
  const s = c.hero;
  return (
    <section className="dm-hero" id="top">
      <h1 className="dm-sr-only">Gal Pal Society × Dyke March 2026 · Gals Together, Pals Forever</h1>
      <div className="dm-hero-banner">
        <picture>
          <source media="(max-width: 640px)" srcSet="assets/hero-banner-mobile.png" />
          <img src="assets/hero-banner.png" alt="G.P.S. × Dyke March 2026 · Gals Together, Pals Forever" />
        </picture>
      </div>
      <div className="dm-factbar">
        <div className="dm-factbar-left">
          <p className="dm-factbar-sub">{s.sub.a}<em>{s.sub.em}</em>{s.sub.b}</p>
          <p className="dm-factbar-slogan">{s.slogan}</p>
          <ul className="dm-factbar-facts">
            <li><DMIcon name="calendar" size={20} /><span>{s.date}<i>{s.dateNote}</i></span></li>
            <li><DMIcon name="clock" size={20} /><span>{s.time}</span></li>
            <li><DMIcon name="pin" size={20} /><span>{s.place}</span></li>
          </ul>
        </div>
        <a href={ZEFFY_URL} target="_blank" rel="noopener noreferrer" className="dm-btn dm-btn-primary dm-btn-lg"><span>{c.cta}</span><DMIcon name="arrow" size={18} stroke={1.9} /></a>
      </div>
    </section>
  );
}

function About({ c }) {
  const s = c.about;
  return (
    <section className="dm-section" id="about">
      <div className="dm-about">
        <div className="dm-about-head">
          <Eyebrow en="ABOUT" cn={s.eyebrow} />
          <Heading h={s.h} className="dm-h2" />
          <p className="dm-lead">{s.lead}</p>
          <p className="dm-about-stat">{s.stat}</p>
        </div>
        <div className="dm-about-media">
          <div className="dm-video-frame">
            <iframe
              src="https://www.youtube.com/embed/6JGlkB1C7i0?rel=0"
              title="Gal Pal Society × Dyke March"
              allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
              referrerPolicy="strict-origin-when-cross-origin"
              allowFullScreen
              loading="lazy"
            />
          </div>
        </div>
      </div>
    </section>
  );
}

function Schedule({ c }) {
  const s = c.schedule;
  return (
    <section className="dm-section dm-section-band" id="schedule">
      <div className="dm-band-head">
        <Eyebrow en="SCHEDULE" cn={s.eyebrow} />
        <Heading h={s.h} className="dm-h2 dm-center" />
        <p className="dm-body dm-center dm-narrow">{s.sub}</p>
      </div>
      <ol className="dm-timeline">
        {s.steps.map((step) => (
          <li key={step.h} className="dm-step">
            <div className="dm-step-rail"><span className="dm-step-dot">{step.emoji ? <span className="dm-step-emoji">{step.emoji}</span> : <DMIcon name={step.icon} size={22} />}</span></div>
            <div className="dm-step-card">
              <span className="dm-step-time">{step.t}</span>
              <h3 className="dm-h3">{step.h}</h3>
              {step.loc && <span className="dm-step-loc"><DMIcon name="pin" size={15} stroke={1.8} />{step.loc}</span>}
            </div>
          </li>
        ))}
      </ol>
    </section>
  );
}

function Bring({ c }) {
  const s = c.bring;
  return (
    <section className="dm-section" id="bring">
      <div className="dm-band-head">
        <Eyebrow en="WHAT TO BRING" cn={s.eyebrow} />
        <Heading h={s.h} className="dm-h2 dm-center" />
        <p className="dm-body dm-center dm-narrow">{s.sub}</p>
      </div>
      <ul className="dm-bring-grid">
        {s.items.map((it) => (
          <li key={it.label} className="dm-bring-card">
            <span className="dm-bring-icon"><DMIcon name={it.icon} size={26} /></span>
            <span className="dm-bring-label">{it.label}</span>
            <span className="dm-bring-tag">{it.tag}</span>
          </li>
        ))}
      </ul>
    </section>
  );
}

function Scarf({ c }) {
  const s = c.scarf;
  return (
    <section className="dm-section dm-section-band" id="gift">
      <div className="dm-scarf">
        <div className="dm-scarf-copy">
          <Eyebrow en="A LITTLE THANK-YOU" cn={s.eyebrow} />
          <Heading h={s.h} className="dm-h2" />
          <p className="dm-body">{s.body.a}<strong>{s.body.strong}</strong>{s.body.b}</p>
          <Btn href={ZEFFY_URL}>{s.cta}</Btn>
        </div>
        <div className="dm-scarf-feature">
          <div className="dm-scarf-hero">
            <img src="assets/scarf-flat.jpg" alt={s.heroAlt} />
          </div>
          <div className="dm-scarf-grid">
            {s.tiles.map((t) => (
              <figure key={t.label} className="dm-scarf-tile">
                <img src={t.src} alt={t.label} />
                <figcaption>{t.label}</figcaption>
              </figure>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

function Register({ c }) {
  const s = c.register;
  return (
    <section className="dm-section" id="register">
      <div className="dm-band-head">
        <Eyebrow en="REGISTER" cn={s.eyebrow} />
        <Heading h={s.h} className="dm-h2 dm-center" />
        <p className="dm-body dm-center dm-narrow">{s.sub}</p>
      </div>
      <div className="dm-register">
        <a href={ZEFFY_URL} className="dm-btn dm-btn-primary dm-btn-lg" target="_blank" rel="noopener noreferrer">
          <span>{s.openBtn}</span><DMIcon name="arrow" size={18} stroke={1.9} />
        </a>
        <p className="dm-register-note">{s.note}</p>
      </div>
    </section>
  );
}

function Footer({ c }) {
  const s = c.footer;
  return (
    <footer className="dm-footer">
      <div className="dm-footer-grid">
        <div className="dm-footer-brand">
          <img src="assets/gps-logo-primary.png" alt="Gal Pal Society" className="dm-logo-img dm-logo-img-footer" />
          <p>{s.brand}</p>
        </div>
        <div className="dm-footer-info">
          <h4>{s.infoH}</h4>
          <ul>
            <li><DMIcon name="calendar" size={17} />{s.info[0]}</li>
            <li><DMIcon name="pin" size={17} />{s.info[1]}</li>
            <li><DMIcon name="users" size={17} />{s.info[2]}</li>
          </ul>
        </div>
        <div className="dm-footer-join">
          <h4>{s.joinH}</h4>
          <p>{s.joinP}</p>
          <a href={LINKTREE_URL} className="dm-btn dm-btn-secondary" target="_blank" rel="noopener noreferrer"><span>{s.joinBtn}</span><DMIcon name="arrow" size={16} stroke={1.9} /></a>
        </div>
      </div>
      <div className="dm-footer-bottom">
        <span>© 2026 Gal Pal Society · G.P.S. ESTD 2023</span>
        <span>{s.bottom}</span>
      </div>
    </footer>
  );
}

function Gallery({ c }) {
  const s = c.gallery;
  const photos = [
    { src: 'assets/g-group.jpg', cls: 'g-wide', alt: 'G.P.S. Dyke March group photo' },
    { src: 'assets/g-banner.jpg', cls: 'g-tall', alt: 'Gals Together, Pals Forever banner' },
    { src: 'assets/g-march.jpg', cls: 'g-a', alt: 'Marching down the street with the banner' },
    { src: 'assets/g-flag.jpg', cls: 'g-b', alt: 'A Pride flag in the crowd' },
  ];
  return (
    <section className="dm-section" id="gallery">
      <div className="dm-band-head">
        <Eyebrow en="GALLERY" cn={s.eyebrow} />
        <Heading h={s.h} className="dm-h2 dm-center" />
        <p className="dm-body dm-center dm-narrow">{s.sub}</p>
      </div>
      <div className="dm-gallery">
        {photos.map((p) => (
          <figure key={p.src} className={`dm-gphoto ${p.cls}`}>
            <img src={p.src} alt={p.alt} loading="lazy" />
          </figure>
        ))}
      </div>
    </section>
  );
}

// ---- root ----
function DykeMarchLanding({ t }) {
  const direction = (t && t.direction) || 'editorial';
  const primary = (t && t.primary) || '#8260D0';
  const showDecor = t ? t.decor !== false : true;

  const [lang, setLang] = React.useState(() => {
    try {
      const u = new URLSearchParams(window.location.search).get('lang');
      if (u === 'en' || u === 'zh') return u;
      const saved = localStorage.getItem('dm-lang');
      if (saved === 'en' || saved === 'zh') return saved;
    } catch (e) {}
    return 'zh';
  });

  React.useEffect(() => {
    try {
      localStorage.setItem('dm-lang', lang);
      const url = new URL(window.location.href);
      url.searchParams.set('lang', lang);
      window.history.replaceState(null, '', url);
      document.documentElement.lang = lang === 'en' ? 'en' : 'zh';
    } catch (e) {}
  }, [lang]);

  const c = COPY[lang];

  const rootStyle = {
    '--brand': primary,
    '--bg': '#FEFCF8', '--section': '#F6F2EB', '--card': '#FFFFFF', '--footer': '#F1ECE3',
    '--border': '#E2D9CA', '--body': '#2A2520', '--secondary': '#5C534A',
    '--caption': '#8E847B', '--disabled': '#BDB3A7',
  };

  return (
    <div className={`dm dir-${direction} lang-${lang}${showDecor ? '' : ' no-decor'}`} style={rootStyle}>
      {showDecor && direction === 'wave' && <div className="dm-topwave"><RainbowWave /></div>}
      <Header c={c} lang={lang} setLang={setLang} />
      <Hero c={c} />
      <About c={c} />
      <Schedule c={c} />
      <Bring c={c} />
      <Scarf c={c} />
      <Register c={c} />
      <Footer c={c} />
    </div>
  );
}

window.DykeMarchLanding = DykeMarchLanding;
