// invite.jsx — the multi-tenant event invitation page.
// EVERYTHING guest-visible comes from the event's config (window.__TENANT__,
// injected by the middleware) with brand-neutral fallbacks. No couple's names,
// venue, dates, or addresses may ever be hard-coded here — a new event must
// render complete from data alone. Optional sections (schedule, personal note,
// travel) only appear when the event configures them.
const { useEffect, useRef, useState, useMemo } = React;

// ─── Palettes (ink, accent, cream) ─────────────────────────────────────
const PALETTES = [
  { id: "earth",    label: "Earth",    vals: ["#2A1C12", "#B9CFDA", "#F2EDE4"] },
  { id: "espresso", label: "Espresso", vals: ["#1B120A", "#9CBAC8", "#EAE2D2"] },
  { id: "fawn",     label: "Fawn",     vals: ["#594028", "#C9DCE4", "#F6F1E6"] },
  { id: "midnight", label: "Midnight", vals: ["#241A14", "#7FA0B0", "#ECE4D2"] },
];

function applyPalette(p) {
  const [brown, blue, cream] = p;
  const root = document.documentElement.style;
  root.setProperty("--brown", brown);
  root.setProperty("--blue", blue);
  root.setProperty("--cream", cream);
  root.setProperty("--brown-2", mix(brown, cream, 0.22));
  root.setProperty("--brown-3", mix(brown, cream, 0.5));
  root.setProperty("--blue-2",  mix(blue, brown, 0.18));
  root.setProperty("--blue-3",  mix(blue, cream, 0.55));
  root.setProperty("--cream-2", mix(cream, brown, 0.08));
  root.setProperty("--paper",   mix(cream, "#ffffff", 0.4));
}
function mix(a, b, t) {
  const pa = hexToRgb(a), pb = hexToRgb(b);
  if (!pa || !pb) return a; // malformed palette value in config → don't throw
  const r = Math.round(pa.r + (pb.r - pa.r) * t);
  const g = Math.round(pa.g + (pb.g - pa.g) * t);
  const bl = Math.round(pa.b + (pb.b - pa.b) * t);
  return `rgb(${r},${g},${bl})`;
}
function hexToRgb(h) {
  if (typeof h !== 'string') return null; // guard: tenant palettes come from DB JSON
  if (h.startsWith("rgb")) {
    const m = h.match(/\d+/g);
    if (!m || m.length < 3) return null;
    const n = m.map(Number);
    return { r: n[0], g: n[1], b: n[2] };
  }
  const s = h.replace("#", "");
  if (s.length < 6) return null;
  return { r: parseInt(s.slice(0,2),16), g: parseInt(s.slice(2,4),16), b: parseInt(s.slice(4,6),16) };
}

// ─── Event model — every guest-visible string, from data ───────────────
// Config keys used (all optional, all set via the admin Settings tab / AI):
//   names, short, tagline, location, date_display, time_display,
//   venue_name, venue_city, invite_line, sign_off,
//   schedule: [[label, value], …]          → schedule strip (hidden if absent)
//   note: "para\n\npara"                   → personal-note panel (hidden if absent)
//   travel: [{label,title,body,coord}, …]  → travel panel (hidden if absent)
//   contact_email                          → contact link (hidden if absent)
//   copy: { any COPY key }                 → per-tone copy overrides
//   email_content.event_details            → the detail cards (shared with emails)
const TN = window.__TENANT__ || null;
const TENANT_CFG = (TN && TN.config) || {};

function prettyDate(d) {
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(d || '').trim());
  if (!m) return String(d || '').trim();
  const dt = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
  return dt.toLocaleDateString('en-AU', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' });
}

function buildEvent() {
  const c = TENANT_CFG;
  const names = String(c.names || (TN && TN.display_name) || '').trim();
  // "Maddi & Rhys" / "Maddi and Rhys" → two hero words. One name is fine too.
  const parts = names.split(/\s*(?:&|\+|\band\b)\s*/i).map(s => s.trim()).filter(Boolean);
  const nameA = parts[0] || names || 'You’re';
  const nameB = parts.length > 1 ? parts.slice(1).join(' & ') : (names ? '' : 'invited');
  const monogram = c.short || (parts.length > 1 ? `${nameA[0] || ''}&${(parts[1] || '')[0] || ''}` : (names[0] || '—'));
  const dateDisplay = c.date_display || prettyDate(TN && TN.event_date) || '';
  const deadline = prettyDate((TN && TN.rsvp_deadline) || c.rsvp_deadline) || '';
  const details = Array.isArray(c.email_content && c.email_content.event_details)
    ? c.email_content.event_details.filter(r => Array.isArray(r) && String(r[0] || '').trim() && String(r[1] || '').trim())
    : [];
  const year = /^(\d{4})/.exec(String((TN && TN.event_date) || ''))?.[1] || '';
  return {
    names: names || 'You’re invited',
    nameA, nameB, monogram,
    tagline: c.tagline || 'A celebration',
    location: c.location || '',
    dateDisplay,
    timeDisplay: c.time_display || '',
    venueName: c.venue_name || '',
    venueCity: c.venue_city || '',
    deadline,
    details,
    schedule: Array.isArray(c.schedule) ? c.schedule.filter(r => Array.isArray(r) && r[0] && r[1]) : [],
    note: String(c.note || '').trim(),
    travel: Array.isArray(c.travel) ? c.travel.filter(t => t && (t.title || t.body)) : [],
    contactEmail: String(c.contact_email || '').trim(),
    year,
  };
}
const EVENT = buildEvent();

// ─── Copy tone variants — venue-agnostic; events override via config.copy ──
function makeCopy(ev) {
  const dl = ev.deadline ? `by ${ev.deadline}` : 'as soon as you can';
  return {
    warm: {
      eyebrow: "Together with their families",
      invite:  "joyfully invite you to celebrate with them",
      blurb:   "An evening among the people we love — and the start of what's next.",
      dateLabel: "The date",
      venueLabel:"You'll find us at",
      venueBlurb:"",
      rsvpHead:  "Will you join us?",
      rsvpLead:  `We'd love your reply ${dl}. The form takes a minute — let us know any dietaries.`,
      rsvpBtn:   "Reply to the invitation",
      footHead:  "We can't wait to see you.",
      footNote:  `With every kindness,\n${ev.monogram}`,
    },
    formal: {
      eyebrow: "The honour of your presence is requested",
      invite:  `at the celebration of ${ev.names}`,
      blurb:   "An evening of celebration in the company of family and friends.",
      dateLabel: "The date",
      venueLabel:"The venue",
      venueBlurb:"",
      rsvpHead:  "Kindly reply",
      rsvpLead:  `Responses are requested ${ev.deadline ? 'no later than ' + ev.deadline : 'at your earliest convenience'}. Please indicate any dietary requirements.`,
      rsvpBtn:   "Respond",
      footHead:  "We look forward to your company.",
      footNote:  `With warmest regards,\n${ev.names}`,
    },
    playful: {
      eyebrow: "It's happening. You're invited.",
      invite:  "come celebrate with us.",
      blurb:   "Bring an appetite, comfortable shoes for dancing, and the friend you'd want next to you at a good party.",
      dateLabel: "Save this date",
      venueLabel:"The spot",
      venueBlurb:"",
      rsvpHead:  "Coming?",
      rsvpLead:  `Tell us ${dl}. Yes, no, allergies — all of it.`,
      rsvpBtn:   "Tell us yes",
      footHead:  "See you there.",
      footNote:  `Big love,\n${ev.monogram}`,
    },
  };
}
const COPY = makeCopy(EVENT);

// ─── Reveal hook ───────────────────────────────────────────────────────
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll("[data-reveal], [data-reveal-letter]");
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          e.target.classList.add("in");
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.18, rootMargin: "0px 0px -8% 0px" });
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  });
}

function Letters({ text, base = 0 }) {
  return (
    <span data-reveal-letter>
      {[...text].map((ch, i) => (
        <span className="lt" style={{ "--i": base + i }} key={i}>
          {ch === " " ? " " : ch}
        </span>
      ))}
    </span>
  );
}

// Sub-line under the hero names: whichever event facts exist, dot-separated.
function HeroSub({ items }) {
  const list = items.filter(Boolean);
  if (!list.length) return null;
  return (
    <div className="hero-sub" data-reveal style={{ "--rd": "1200ms" }}>
      {list.map((it, i) => (
        <React.Fragment key={i}>
          {i > 0 && <span className="sep"></span>}
          <span>{it}</span>
        </React.Fragment>
      ))}
    </div>
  );
}

// ─── Hero ──────────────────────────────────────────────────────────────
function Hero({ variant, copy }) {
  const subItems = [EVENT.dateDisplay, EVENT.timeDisplay, EVENT.venueName, EVENT.venueCity];
  return (
    <section className="hero" data-variant={variant}>
      <div className="hero-inner">
        {variant === "stacked" && (
          <>
            <div className="hero-eyebrow" data-reveal>
              <span className="pad"></span>
              {copy.eyebrow}
              <span className="pad"></span>
            </div>
            <div className="names">
              <span className="word"><Letters text={EVENT.nameA} /></span>
              {EVENT.nameB && <span className="amp"> &amp; </span>}
              {EVENT.nameB && <span className="word"><Letters text={EVENT.nameB} base={EVENT.nameA.length + 1} /></span>}
            </div>
            <HeroSub items={subItems} />
          </>
        )}

        {variant === "split" && (
          <>
            <div className="hero-eyebrow" data-reveal>{copy.eyebrow}</div>
            <div className="hero-l">
              <div className="names"><Letters text={EVENT.nameA} /></div>
            </div>
            <div className="hero-r">
              {EVENT.nameB && (
                <div className="names" style={{ fontSize: "clamp(56px,12vw,184px)", color: "var(--brown-3)" }}>
                  <span>&amp;</span>
                </div>
              )}
              {EVENT.nameB && <div className="names"><Letters text={EVENT.nameB} base={EVENT.nameA.length + 1} /></div>}
            </div>
            <HeroSub items={[EVENT.dateDisplay, EVENT.venueName || EVENT.venueCity]} />
          </>
        )}

        {variant === "asym" && (
          <>
            <div className="hero-eyebrow" data-reveal>{copy.eyebrow}</div>
            <div className="names" style={{ marginLeft: "2vw" }}>
              <Letters text={EVENT.nameA} />
            </div>
            {EVENT.nameB && (
              <div className="names row-y" style={{ marginTop: "-.06em" }}>
                <span className="amp" style={{ fontSize: ".8em" }}>&amp;</span>{" "}
                <Letters text={EVENT.nameB} base={EVENT.nameA.length + 1} />
              </div>
            )}
            <HeroSub items={[EVENT.dateDisplay, [EVENT.venueName, EVENT.venueCity].filter(Boolean).join(' · ')]} />
          </>
        )}

        <div className="serial">№ 001 / Invitation</div>
        <div className="serial serial-r">Edition of one</div>
      </div>
    </section>
  );
}

// ─── Detail block ──────────────────────────────────────────────────────
function Detail({ n, label, title, body, coord }) {
  return (
    <div className="detail" data-reveal>
      <div className="lab"><span className="n">{n}</span><span style={{opacity:.35}}>/</span><span>{label}</span></div>
      <h2>{title}</h2>
      {body ? <p>{body}</p> : null}
      {coord && (
        <div style={{ marginTop: 24, fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".22em", color: "var(--brown-3)", textTransform: "uppercase" }}>
          {coord}
        </div>
      )}
    </div>
  );
}

// Split a detail value at the first "·"/"—" so the second half italicises like
// the original design ("Saturday 14 Nov · 6pm" → Saturday 14 Nov / 6pm).
function detailTitle(value) {
  const s = String(value || '');
  const m = s.split(/\s*[·—]\s*/);
  if (m.length >= 2) {
    return <>{m[0]}<br/><em style={{fontStyle:"italic"}}>{m.slice(1).join(' · ')}</em></>;
  }
  return s;
}

// ─── Schedule strip (config.schedule = [[label, value], …]) ────────────
function ScheduleStrip({ items }) {
  if (!items.length) return null;
  return (
    <div className="strip" data-reveal>
      {items.map((it, i) => (
        <div className="cell" key={i} style={{ "--rd": `${i * 120}ms` }} data-reveal>
          <div className="k">{it[0]}</div>
          <div className="v">{it[1]}</div>
        </div>
      ))}
    </div>
  );
}

// The invite token for this page: injected by the tenant middleware from the
// /<slug>/i/<token> path, or the legacy ?t=… query param.
function currentToken() {
  if (window.__INVITE_TOKEN__ && /^[a-z0-9]{10}$/.test(window.__INVITE_TOKEN__)) return window.__INVITE_TOKEN__;
  const q = new URLSearchParams(window.location.search).get("t");
  return q && /^[a-z0-9]{10}$/.test(q) ? q : null;
}

// Fold any tenant-supplied palette/tone/hero/template over the compiled-in
// defaults so a new event restyles itself from data alone.
const TEMPLATE_IDS = ['editorial', 'minimal', 'romantic', 'modern'];
function tenantDefaults() {
  const d = { ...TWEAK_DEFAULTS };
  if (Array.isArray(TENANT_CFG.palette) && TENANT_CFG.palette.length === 3) d.palette = TENANT_CFG.palette;
  if (TENANT_CFG.tone) d.tone = TENANT_CFG.tone;
  if (TENANT_CFG.hero) d.hero = TENANT_CFG.hero;
  d.template = TEMPLATE_IDS.includes(TENANT_CFG.template) ? TENANT_CFG.template : 'editorial';
  return d;
}

// Couple's uploaded photos (config.photos = [{url, key}, …]) → plain URL list.
// Templates that declare photo slots (romantic, modern) fill them in order and
// render a styled placeholder when a slot has no photo yet.
const PHOTOS = Array.isArray(TENANT_CFG.photos)
  ? TENANT_CFG.photos.filter(p => p && typeof p.url === 'string' && p.url).map(p => p.url)
  : [];

// Patch the static (non-React) chrome in index.html with this event's identity.
function applyTenantChrome() {
  document.title = EVENT.names + (EVENT.dateDisplay ? ' · ' + EVENT.dateDisplay : '');
  const setText = (sel, val) => { const el = document.querySelector(sel); if (el && val) el.textContent = val; };
  setText('.side-lab', EVENT.location || [EVENT.venueCity].filter(Boolean).join(' · '));
  const metaSpans = document.querySelectorAll('header.meta > div:first-child > span');
  if (metaSpans[0]) metaSpans[0].textContent = EVENT.monogram;
  if (metaSpans[2]) metaSpans[2].textContent = EVENT.tagline;
  setText('header.meta > div:last-child', EVENT.dateDisplay);
}

// Apply the tenant's font + background presets (from window.RSVP_DESIGN) on top
// of the palette. Safe no-op if the presets script or config keys are absent.
function applyDesign(palette) {
  const D = window.RSVP_DESIGN;
  if (!D) return;
  if (TENANT_CFG.font_preset) D.applyFont(TENANT_CFG.font_preset);
  const cream = Array.isArray(palette) ? palette[2] : undefined;
  if (TENANT_CFG.background) D.applyBackground(TENANT_CFG.background, cream);
}

// ─── Shared RSVP section (the form's destination in every template) ──────
function RsvpSection({ copy }) {
  return (
    <section className="rsvp">
      <div data-reveal>
        <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".32em", textTransform: "uppercase", color: "var(--brown-3)", marginBottom: 28 }}>
          <span style={{display:"inline-block",width:36,height:1,background:"currentColor",verticalAlign:"middle",marginRight:14,opacity:.5}}></span>
          RSVP
          <span style={{display:"inline-block",width:36,height:1,background:"currentColor",verticalAlign:"middle",marginLeft:14,opacity:.5}}></span>
        </div>
        <h2><em style={{fontStyle:"italic"}}>{copy.rsvpHead}</em></h2>
        <p className="lead">{copy.rsvpLead}</p>
        <RsvpForm copy={copy} />
        {(EVENT.deadline || EVENT.contactEmail) && (
          <div className="rsvp-meta" style={{marginTop:48}}>
            {EVENT.deadline && <span>Reply by {EVENT.deadline}</span>}
            {EVENT.deadline && EVENT.contactEmail && <span>·</span>}
            {EVENT.contactEmail && <a href={`mailto:${EVENT.contactEmail}`}>{EVENT.contactEmail}</a>}
          </div>
        )}
      </div>
    </section>
  );
}

// Photo slot: the couple's Nth uploaded photo, or a styled placeholder so the
// arch/frame still reads on a bare event.
function PhotoSlot({ i, label, className = "" }) {
  const url = PHOTOS[i];
  return url
    ? <img src={url} alt="" className={className} loading="lazy" />
    : <div className={"ph " + className}>{label}</div>;
}

// ─── TEMPLATE · Editorial (the classic — original layout) ────────────────
function TemplateEditorial({ t, copy, detailRows, noteParas, inShort }) {
  return (
    <>
      <Hero variant={t.hero} copy={copy} />

      {/* Event details panel — accent */}
      <section className="panel blue" style={{ background: "var(--blue)" }}>
        <div className="band" data-reveal>
          <div className="eyebrow">Chapter I · The Occasion</div>
          <h3>{TENANT_CFG.invite_line || copy.invite}</h3>
          {EVENT.venueCity && <div className="coord" style={{lineHeight:1.7}}>{EVENT.venueCity}</div>}
        </div>

        {detailRows.length > 0 && (
          <div className="detail-grid">
            {detailRows.slice(0, 4).map((row, i) => (
              <Detail
                key={i}
                n={String(i + 1).padStart(2, '0')}
                label={row[0]}
                title={detailTitle(row[1])}
                body={i === 1 ? copy.venueBlurb : ''}
              />
            ))}
          </div>
        )}

        <ScheduleStrip items={EVENT.schedule} />
      </section>

      {/* Personal note — only when the event writes one (config.note) */}
      {noteParas.length > 0 && (
        <section className="note">
          <div className="corner l" data-reveal>A note · please read</div>
          <div className="corner r" data-reveal>{EVENT.monogram}</div>
          <div className="note-inner" data-reveal>
            <div className="eyebrow">
              <span className="pad"></span>
              A note from us
              <span className="pad"></span>
            </div>
            {noteParas.map((p, i) => <p key={i}>{p}</p>)}
          </div>
        </section>
      )}

      {/* Travel & stay — only when the event provides entries (config.travel) */}
      {EVENT.travel.length > 0 && (
        <section className="panel cream">
          <div className="band" data-reveal>
            <div className="eyebrow">Chapter II · Getting there</div>
            <h3><em style={{fontStyle:"italic"}}>Travel</em> &amp; stay</h3>
            {EVENT.venueCity && <div className="coord">{EVENT.venueCity}</div>}
          </div>
          <div className="detail-grid">
            {EVENT.travel.slice(0, 4).map((tr, i) => (
              <Detail
                key={i}
                n={String(detailRows.length + i + 1).padStart(2, '0')}
                label={tr.label || 'Travel'}
                title={detailTitle(tr.title || '')}
                body={tr.body || ''}
                coord={tr.coord}
              />
            ))}
          </div>
        </section>
      )}

      {/* RSVP */}
      <RsvpSection copy={copy} />

      {/* Footer */}
      <footer>
        <div className="foot-grid">
          <div data-reveal>
            <div className="lab">Closing</div>
            <p className="big" style={{ fontStyle: "italic" }}>{copy.footHead}</p>
          </div>
          <div data-reveal style={{ "--rd": "120ms" }}>
            <div className="lab">A note</div>
            <p style={{ whiteSpace: "pre-line" }}>{TENANT_CFG.sign_off ? `${copy.footNote.split('\n')[0]}\n${TENANT_CFG.sign_off}` : copy.footNote}</p>
          </div>
          {inShort && (
            <div data-reveal style={{ "--rd": "240ms" }}>
              <div className="lab">In short</div>
              <p>{inShort}</p>
            </div>
          )}
        </div>
        <div className="foot-bot">
          <span>{EVENT.monogram}{EVENT.year ? <><span style={{display:"inline-block",width:"1.4em"}}></span>·<span style={{display:"inline-block",width:"1.4em"}}></span>{EVENT.year}</> : null}</span>
          <span>{EVENT.venueCity || EVENT.location || ''}</span>
        </div>
      </footer>
    </>
  );
}

// ─── TEMPLATE · Minimal (one typographic statement) ──────────────────────
function TemplateMinimal({ copy, detailRows, noteParas }) {
  const facts = [
    EVENT.dateDisplay,
    EVENT.timeDisplay,
    [EVENT.venueName, EVENT.venueCity].filter(Boolean).join(' · '),
  ].filter(Boolean);
  return (
    <div className="tpl-min">
      <section className="min-hero">
        <div className="min-eyebrow" data-reveal>{copy.eyebrow}</div>
        <h1 className="min-names" data-reveal>
          {EVENT.nameA}{EVENT.nameB && <span className="amp">&amp;</span>}{EVENT.nameB}
        </h1>
        {facts.length > 0 && (
          <div className="min-facts" data-reveal>
            {facts.map((fct, i) => <div key={i}>{fct}</div>)}
          </div>
        )}
      </section>

      {detailRows.length > 0 && (
        <section className="min-lines">
          {detailRows.map((row, i) => (
            <div className="min-line" key={i} data-reveal>
              <span className="k">{row[0]}</span>
              <span className="v">{row[1]}</span>
            </div>
          ))}
        </section>
      )}

      {noteParas.length > 0 && (
        <section className="min-note" data-reveal>
          {noteParas.map((p, i) => <p key={i}>{p}</p>)}
        </section>
      )}

      <RsvpSection copy={copy} />

      <div className="min-foot" data-reveal>{EVENT.monogram}</div>
    </div>
  );
}

// ─── TEMPLATE · Romantic (arches & soft rhythm) ──────────────────────────
function TemplateRomantic({ copy, detailRows, noteParas }) {
  const cards = detailRows.slice(0, 3);
  return (
    <div className="tpl-rom">
      <section className="rom-hero">
        <div className="rom-arch" data-reveal><PhotoSlot i={0} label="A photo of the two of you" /></div>
        <h1 className="rom-names" data-reveal>
          {EVENT.nameA}{EVENT.nameB && <span className="amp"> &amp; </span>}{EVENT.nameB}
        </h1>
        <p className="rom-sub" data-reveal>{copy.blurb}</p>
        <div className="rom-meta" data-reveal>{[EVENT.dateDisplay, EVENT.venueCity || EVENT.location].filter(Boolean).join(' — ')}</div>
      </section>

      {(TENANT_CFG.invite_line || copy.invite) && (
        <section className="rom-intro" data-reveal>
          <p>{TENANT_CFG.invite_line || copy.invite}</p>
        </section>
      )}

      {cards.length > 0 && (
        <section className="rom-cards">
          {cards.map((row, i) => (
            <div className="rom-card" key={i} data-reveal style={{ "--rd": `${i * 100}ms` }}>
              <div className="k">{row[0]}</div>
              <div className="v">{row[1]}</div>
            </div>
          ))}
        </section>
      )}

      {EVENT.schedule.length > 0 && (
        <section className="rom-schedule" data-reveal>
          <div className="rom-h">The day, hour by hour</div>
          <div className="rom-sched-grid">
            {EVENT.schedule.map((it, i) => (
              <div className="cell" key={i}><div className="t">{it[0]}</div><div className="l">{it[1]}</div></div>
            ))}
          </div>
        </section>
      )}

      {(PHOTOS[1] || EVENT.travel.length > 0) && (
        <section className="rom-arch2" data-reveal><PhotoSlot i={1} label="The venue" /></section>
      )}

      {EVENT.travel.length > 0 && (
        <section className="rom-travel">
          {EVENT.travel.slice(0, 2).map((tr, i) => (
            <div key={i} data-reveal>
              <div className="k">{tr.title || tr.label || 'Getting there'}</div>
              <div className="v">{tr.body || ''}</div>
            </div>
          ))}
        </section>
      )}

      {noteParas.length > 0 && (
        <section className="rom-intro" data-reveal>
          {noteParas.map((p, i) => <p key={i} style={{ fontStyle: 'italic' }}>{p}</p>)}
        </section>
      )}

      <RsvpSection copy={copy} />

      <footer style={{ textAlign: 'center', padding: '60px 6vw 90px' }}>
        <p className="big" style={{ fontStyle: 'italic', fontFamily: 'var(--serif)', fontSize: 'clamp(32px,5vw,64px)', color: 'var(--cream)' }}>{copy.footHead}</p>
      </footer>
    </div>
  );
}

// ─── TEMPLATE · Modern (structured colour blocking) ──────────────────────
function TemplateModern({ copy, detailRows, noteParas }) {
  const blocks = detailRows.slice(0, 3);
  const slug = window.__SLUG__ ? String(window.__SLUG__).toUpperCase() : '';
  return (
    <div className="tpl-mod">
      <section className="mod-hero">
        <div className="mod-top" data-reveal>
          <span>{copy.eyebrow}</span>
          <span>RSVP-TO{slug ? ' / ' + slug : ''}</span>
        </div>
        <div className="mod-names">
          <div className="mod-name" data-reveal>{EVENT.nameA}</div>
          {EVENT.nameB && (
            <div className="mod-amp-row" data-reveal>
              <span className="amp">&amp;</span>
              {(EVENT.venueCity || EVENT.location) && <span className="mod-city">{EVENT.venueCity || EVENT.location}</span>}
            </div>
          )}
          {EVENT.nameB && <div className="mod-name" data-reveal>{EVENT.nameB}</div>}
        </div>
        <div className="mod-band" data-reveal>
          <span className="big">{EVENT.dateDisplay || EVENT.tagline}</span>
          <span className="small">{[EVENT.timeDisplay, EVENT.venueName].filter(Boolean).join(' · ')}</span>
        </div>
      </section>

      <section className="mod-intro">
        <div data-reveal>
          <div className="eyebrow">The invitation</div>
          <p>{TENANT_CFG.invite_line || copy.invite}</p>
        </div>
        <div className="mod-photo" data-reveal><PhotoSlot i={0} label="A photo of the two of you" /></div>
      </section>

      {blocks.length > 0 && (
        <section className="mod-blocks">
          {blocks.map((row, i) => (
            <div className="mod-block" key={i} data-reveal>
              <div className="n">{String(i + 1).padStart(2, '0')} — {row[0]}</div>
              <div className="k">{row[1]}</div>
            </div>
          ))}
        </section>
      )}

      {EVENT.schedule.length > 0 && (
        <section className="mod-run" data-reveal>
          <div className="mod-h">Run of show</div>
          <div className="mod-run-grid">
            {EVENT.schedule.map((it, i) => (
              <div className="cell" key={i}><div className="t">{it[0]}</div><div className="l">{it[1]}</div></div>
            ))}
          </div>
        </section>
      )}

      {EVENT.travel.length > 0 && (
        <section className="mod-stay">
          {EVENT.travel.slice(0, 2).map((tr, i) => (
            <div className="blk" key={i} data-reveal>
              <div className="k">{tr.label || 'Getting there'}</div>
              <div className="t">{tr.title || ''}</div>
              <div className="v">{tr.body || ''}</div>
            </div>
          ))}
        </section>
      )}

      {noteParas.length > 0 && (
        <div className="mod-note" data-reveal>{noteParas.join(' ')}</div>
      )}

      <RsvpSection copy={copy} />

      <footer>
        <div className="foot-bot" style={{ marginTop: 0 }}>
          <span>{copy.footHead}</span>
          <span>{EVENT.monogram}{EVENT.year ? ' · ' + EVENT.year : ''}</span>
        </div>
      </footer>
    </div>
  );
}

// ─── App — palette/chrome side-effects + template switch ─────────────────
function App() {
  const [t, setTweak] = useTweaks(tenantDefaults());
  const copy = { ...(COPY[t.tone] || COPY.warm), ...(TENANT_CFG.copy || {}) };
  const template = TEMPLATE_IDS.includes(t.template) ? t.template : 'editorial';

  useEffect(() => { applyPalette(t.palette); applyDesign(t.palette); }, [t.palette]);
  useEffect(() => { applyTenantChrome(); }, []);
  useEffect(() => { document.body.dataset.template = template; }, [template]);
  useReveal();

  // Fire-and-forget beacon: tells the server this invite link was loaded in a
  // real browser. Failures are swallowed — tracking must never block render.
  useEffect(() => {
    const token = currentToken();
    if (!token) return;
    const headers = { "content-type": "application/json" };
    if (window.__SLUG__) headers["X-Tenant"] = window.__SLUG__;
    fetch("/api/track-hit", {
      method: "POST",
      headers,
      body: JSON.stringify({ token }),
      keepalive: true,
    }).catch(() => {});
  }, []);

  // Detail cards: the event_details rows the admin sets in Settings (shared
  // with the acceptance emails). Fallback: whatever core facts exist.
  const detailRows = EVENT.details.length ? EVENT.details : [
    ['When', [EVENT.dateDisplay, EVENT.timeDisplay].filter(Boolean).join(' · ')],
    ['Where', [EVENT.venueName, EVENT.venueCity].filter(Boolean).join(' · ')],
  ].filter(r => r[1]);
  const noteParas = EVENT.note ? EVENT.note.split(/\n\s*\n/).filter(Boolean) : [];
  const inShort = [
    EVENT.dateDisplay, EVENT.timeDisplay,
    [EVENT.venueName, EVENT.venueCity].filter(Boolean).join(', '),
    EVENT.deadline ? `RSVP by ${EVENT.deadline}` : '',
  ].filter(Boolean).join(' · ');

  const shared = { t, copy, detailRows, noteParas, inShort };
  const Template = template === 'minimal' ? TemplateMinimal
    : template === 'romantic' ? TemplateRomantic
    : template === 'modern' ? TemplateModern
    : TemplateEditorial;

  return (
    <>
      <Template {...shared} />

      {/* Tweaks */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Template" />
        <TweakRadio
          label="Layout"
          value={template}
          options={TEMPLATE_IDS}
          onChange={(v) => setTweak("template", v)}
        />
        <TweakSection label="Palette" />
        <TweakColor
          label="Colour"
          value={t.palette}
          options={PALETTES.map(p => p.vals)}
          onChange={(v) => setTweak("palette", v)}
        />
        {template === 'editorial' && <TweakSection label="Hero layout" />}
        {template === 'editorial' && (
          <TweakRadio
            label="Variant"
            value={t.hero}
            options={["stacked", "split", "asym"]}
            onChange={(v) => setTweak("hero", v)}
          />
        )}
        <TweakSection label="Copy tone" />
        <TweakRadio
          label="Voice"
          value={t.tone}
          options={["warm", "formal", "playful"]}
          onChange={(v) => setTweak("tone", v)}
        />
      </TweaksPanel>
    </>
  );
}

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