/* App root: ticket modal, tweaks panel, router */
const { useState: useStateApp, useEffect: useEffectApp } = React;
const {
  Icon, WandLogo, EventPosterPlaceholder,
  Nav, Footer, useHashRoute,
  HomePage, AboutPage, EventsPage, VenuesPage, GalleryPage, FAQPage, ContactPage, PrivacyPage,
  NewsletterPage, NewsletterPopup,
  useTweaks, TweaksPanel, TweakSection, TweakSelect, TweakRadio, TweakSlider,
} = window;

/* Render a description as real paragraphs. A blank line (Enter pressed twice
   in the back-end editor) starts a new paragraph; a single line break inside a
   paragraph is preserved too. This keeps the website mirroring exactly what the
   owner typed in the Show File's Description box. */
function ShowParagraphs({ text, className }) {
  const parts = String(text || '').split(/\n{2,}/).map(s => s.replace(/^\s+|\s+$/g, '')).filter(Boolean);
  if (!parts.length) return null;
  return parts.map((p, i) => (
    <p key={i} className={className} style={{ whiteSpace: 'pre-line' }}>{p}</p>
  ));
}

/* ===== Event Detail + Ticket Modal ===== */
function TicketModal({ event, onClose, variant }) {
  const [step, setStep] = useStateApp('details'); // details | review | confirm
  const [tier, setTier] = useStateApp(event ? event.tiers[0] : null);
  const [qty, setQty] = useStateApp(2);
  const scrollRef = React.useRef(null);
  const ticketsRef = React.useRef(null);

  useEffectApp(() => {
    const k = (e) => e.key === 'Escape' && onClose();
    window.addEventListener('keydown', k);
    return () => window.removeEventListener('keydown', k);
  }, [onClose]);

  // reset scroll to top whenever the step changes
  useEffectApp(() => { if (variant === 'page') window.scrollTo(0, 0); else if (scrollRef.current) scrollRef.current.scrollTop = 0; }, [step]);

  if (!event) return null;

  const total = tier ? parseInt(tier.price.replace('$', '')) * qty : 0;
  const skillMeta = (window.SKILL_META && window.SKILL_META[event.skill || event.type]) || null;
  const reviews = event.reviews || (window.WAND_DATA.testimonials || []).slice(0, 3);

  const availMeta = (a) => ({
    good:           { cls: 'avail-good', label: 'Available' },
    'selling-fast': { cls: 'avail-fast', label: 'Selling Fast' },
    'few-left':     { cls: 'avail-few',  label: 'Few Left' },
  }[a] || { cls: 'avail-good', label: 'Available' });

  const scrollToTickets = () => ticketsRef.current && ticketsRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });

  const inner = (
        <div className="detail-scroll" ref={scrollRef}>

          {step === 'details' && (
            <div>
              {/* Top band */}
              <div className="detail-hero">
                <div className="detail-photo">
                  {event.photo
                    ? <img src={event.photo} alt={event.title} />
                    : <EventPosterPlaceholder style={event.posterStyle} title={event.title} />}
                </div>
                <div className="detail-info">
                  {skillMeta && (
                    <div className="skill-chip" style={{ color: skillMeta.color, marginBottom: 6 }}>
                      <span className="skill-dot" style={{ background: skillMeta.color }}></span>{skillMeta.label}
                    </div>
                  )}
                  <h2 className="detail-title">{event.title}</h2>
                  {event.tagline && <p className="detail-tagline">{event.tagline}</p>}

                  <div className="detail-when">
                    <span className="cal-block cal-block-lg">
                      <span className="cal-month">{event.date.month}</span>
                      <span className="cal-day">{event.date.day}</span>
                    </span>
                    <span className="cal-when">
                      <span className="cal-dow">{({ SUN:'Sunday',MON:'Monday',TUE:'Tuesday',WED:'Wednesday',THU:'Thursday',FRI:'Friday',SAT:'Saturday' }[event.dow]) || event.dow}, {event.date.year}</span>
                      <span className="cal-time"><Icon name="clock" size={16} /> {event.time}</span>
                      <span className="detail-venue"><Icon name="pin" size={16} /> {event.venue}</span>
                    </span>
                  </div>

                  <div className="detail-cta-row">
                    <button className="btn btn-primary" onClick={scrollToTickets}>Get Tickets <Icon name="ticket" size={16} /></button>
                    {event.video && <button className="btn btn-ghost" style={{ color: 'var(--ink-700)', borderColor: 'rgba(20,30,60,0.2)' }} onClick={() => { const v = document.getElementById('detail-video'); v && v.scrollIntoView({ behavior: 'smooth' }); }}>Watch Preview</button>}
                  </div>
                </div>
              </div>

              {/* About */}
              <section className="detail-section">
                <h3 className="detail-h">About the Show</h3>
                <ShowParagraphs text={event.description} className="detail-body" />
              </section>

              {/* Video — only shown when the show actually has a preview video.
                  Add a video in the back end and this section appears; with no
                  video it's hidden entirely (no empty box). MCA 2026-08-06. */}
              {event.video && (
              <section className="detail-section" id="detail-video">
                <h3 className="detail-h">Watch</h3>
                <div className="detail-video">
                  <iframe src={event.video} title={`${event.title} preview`} allow="accelerometer; autoplay; encrypted-media; fullscreen" frameBorder="0"></iframe>
                </div>
              </section>
              )}

              {/* Testimonials */}
              {reviews.length > 0 && (
                <section className="detail-section">
                  <h3 className="detail-h">What Guests Are Saying</h3>
                  <div className="detail-reviews">
                    {reviews.map((r, i) => (
                      <div className="detail-review" key={i}>
                        <div className="stars">★ ★ ★ ★ ★</div>
                        <p>"{r.quote}"</p>
                        <div className="detail-review-author">– {r.author}</div>
                      </div>
                    ))}
                  </div>
                </section>
              )}

              {/* Tickets */}
              <section className="detail-section detail-tickets" ref={ticketsRef}>
                <h3 className="detail-h">Get Tickets</h3>
                {event.tiers.map((t) => {
                  const a = availMeta(t.availability);
                  const sel = tier && tier.name === t.name;
                  return (
                    <button
                      key={t.name}
                      onClick={() => setTier(t)}
                      className="tier-row"
                      style={{
                        width: '100%', cursor: 'pointer',
                        background: sel ? 'rgba(212,166,66,0.14)' : '#fff',
                        borderColor: sel ? 'var(--gold-500)' : 'rgba(20,30,60,0.12)',
                        borderWidth: sel ? 2 : 1, textAlign: 'left',
                      }}
                    >
                      <div>
                        <div style={{ fontFamily: 'var(--f-display)', fontSize: 18, fontWeight: 600, color: 'var(--ink-900)' }}>{t.name}</div>
                        <div className={`availability ${a.cls}`} style={{ marginTop: 4 }}>{a.label}</div>
                      </div>
                      <div className="price">{t.price}</div>
                    </button>
                  );
                })}
                <div className="modal-actions">
                  <button className="btn btn-primary" disabled={!tier} onClick={() => setStep('review')} style={{ opacity: tier ? 1 : 0.5 }}>
                    Continue to Checkout <Icon name="chevronRight" size={14} stroke={2.4} />
                  </button>
                </div>
              </section>
            </div>
          )}

          {step === 'review' && tier && (
            <div className="detail-checkout">
              <button className="detail-back" onClick={() => setStep('details')}><Icon name="chevronRight" size={14} stroke={2.4} style={{ transform: 'rotate(180deg)' }} /> Back to show</button>
              <div className="eyebrow-ink">Review Order</div>
              <h3 className="detail-title" style={{ fontSize: 30, margin: '8px 0 14px' }}>{event.title}</h3>
              <div className="detail-when" style={{ marginBottom: 18 }}>
                <span className="cal-block cal-block-lg">
                  <span className="cal-month">{event.date.month}</span>
                  <span className="cal-day">{event.date.day}</span>
                </span>
                <span className="cal-when">
                  <span className="cal-dow">{event.dow}, {event.date.year}</span>
                  <span className="cal-time"><Icon name="clock" size={16} /> {event.time}</span>
                  <span className="detail-venue"><Icon name="pin" size={16} /> {event.venue}</span>
                </span>
              </div>

              <div style={{ background: '#fff', padding: 20, borderRadius: 10, border: '1px solid rgba(20,30,60,0.1)' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
                  <div>
                    <div style={{ fontFamily: 'var(--f-display)', fontSize: 19, fontWeight: 600 }}>{tier.name}</div>
                    <div style={{ fontSize: 14, color: 'var(--ink-500)' }}>{tier.price} each</div>
                  </div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                    <button onClick={() => setQty(Math.max(1, qty - 1))} style={{ width: 36, height: 36, borderRadius: 8, background: 'rgba(20,30,60,0.08)', fontSize: 18 }}>−</button>
                    <span style={{ minWidth: 26, textAlign: 'center', fontWeight: 700, fontSize: 17 }}>{qty}</span>
                    <button onClick={() => setQty(Math.min(8, qty + 1))} style={{ width: 36, height: 36, borderRadius: 8, background: 'rgba(20,30,60,0.08)', fontSize: 18 }}>+</button>
                  </div>
                </div>
                <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid rgba(20,30,60,0.1)', paddingTop: 14, fontSize: 15 }}>
                  <span>Subtotal</span><span>${total}</span>
                </div>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 15, marginTop: 6 }}>
                  <span>Fees</span><span>${Math.round(total * 0.08)}</span>
                </div>
                <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid rgba(20,30,60,0.1)', marginTop: 12, paddingTop: 12, fontFamily: 'var(--f-display)', fontSize: 24, fontWeight: 600 }}>
                  <span>Total</span><span>${total + Math.round(total * 0.08)}</span>
                </div>
              </div>

              <div className="modal-actions">
                <button className="btn btn-primary" onClick={() => setStep('confirm')}>Checkout <Icon name="chevronRight" size={14} stroke={2.4} /></button>
                <button className="btn btn-ghost" style={{ color: 'var(--ink-700)', borderColor: 'rgba(20,30,60,0.18)' }} onClick={() => setStep('details')}>Back</button>
              </div>
            </div>
          )}

          {step === 'confirm' && tier && (
            <div className="detail-checkout" style={{ textAlign: 'center', padding: '40px 20px' }}>
              <div style={{ width: 72, height: 72, borderRadius: 999, background: 'var(--gold-500)', color: 'var(--navy-900)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 22px' }}>
                <Icon name="check" size={36} stroke={3} />
              </div>
              <h3 className="detail-title" style={{ fontSize: 34, textAlign: 'center' }}>You're In!</h3>
              <p style={{ textAlign: 'center', color: 'var(--ink-700)', fontSize: 16, maxWidth: 420, margin: '10px auto 0' }}>
                We'll send {qty} ticket{qty > 1 ? 's' : ''} to your email. See you on <strong>{event.dow}, {event.date.month} {event.date.day}</strong> at {event.venue}.
              </p>
              <div className="modal-actions" style={{ justifyContent: 'center' }}>
                <button className="btn btn-primary" onClick={onClose}>Done</button>
              </div>
            </div>
          )}
        </div>
  );

  // Render as a full PAGE (own URL) or, legacy, as a modal overlay.
  if (variant === 'page') {
    return (
      <main className="event-page">
        <div className="container event-page-inner">
          <button className="event-back" onClick={onClose}>
            <Icon name="chevronRight" size={15} stroke={2.4} style={{ transform: 'rotate(180deg)' }} /> All shows
          </button>
          <div className="detail-modal detail-modal--page">
            {inner}
          </div>
        </div>
      </main>
    );
  }

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="detail-modal" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose} aria-label="Close"><Icon name="close" size={18} /></button>
        {inner}
      </div>
    </div>
  );
}

/* ===== Tweaks panel ===== */
const PALETTES = [
  { name: 'Theater Gold (default)', vars: { '--bg': '#0B1635', '--bg-alt': '#101E45', '--bg-cream': '#F5EEDC', '--accent': '#F8AB2F', '--accent-bright': '#FBBF57', '--navy-900': '#08112C' }, swatch: ['#0B1635', '#F5EEDC', '#F8AB2F'] },
  { name: 'Velvet & Brass',         vars: { '--bg': '#2A0E1D', '--bg-alt': '#3A1428', '--bg-cream': '#F4EBD8', '--accent': '#C9A24F', '--accent-bright': '#E0B86E', '--navy-900': '#1B0813' }, swatch: ['#2A0E1D', '#F4EBD8', '#C9A24F'] },
  { name: 'Midnight Plum',          vars: { '--bg': '#1E1238', '--bg-alt': '#2A1A4A', '--bg-cream': '#F3ECDB', '--accent': '#E5BB6B', '--accent-bright': '#F1CE85', '--navy-900': '#140929' }, swatch: ['#1E1238', '#F3ECDB', '#E5BB6B'] },
  { name: 'Spotlight Charcoal',     vars: { '--bg': '#121417', '--bg-alt': '#1B1E22', '--bg-cream': '#F0EBE0', '--accent': '#D4A642', '--accent-bright': '#EAC15A', '--navy-900': '#0A0B0D' }, swatch: ['#121417', '#F0EBE0', '#D4A642'] },
  { name: 'Emerald Stage',          vars: { '--bg': '#0A2622', '--bg-alt': '#0F332D', '--bg-cream': '#F1EDDD', '--accent': '#CBA14A', '--accent-bright': '#E0BA63', '--navy-900': '#051A16' }, swatch: ['#0A2622', '#F1EDDD', '#CBA14A'] },
  { name: 'Crimson Curtain',        vars: { '--bg': '#3A0E12', '--bg-alt': '#4C151A', '--bg-cream': '#F5EBDD', '--accent': '#E0B656', '--accent-bright': '#F0C96E', '--navy-900': '#260709' }, swatch: ['#3A0E12', '#F5EBDD', '#E0B656'] },
  { name: 'Sapphire & Silver',      vars: { '--bg': '#0C1B3A', '--bg-alt': '#13264D', '--bg-cream': '#EEF1F6', '--accent': '#B9C2D6', '--accent-bright': '#D4DBEA', '--navy-900': '#07122A' }, swatch: ['#0C1B3A', '#EEF1F6', '#B9C2D6'] },
  { name: 'Rose & Champagne',       vars: { '--bg': '#2E1322', '--bg-alt': '#3F1B2F', '--bg-cream': '#F7ECE6', '--accent': '#D9A87E', '--accent-bright': '#E9C09A', '--navy-900': '#1F0C18' }, swatch: ['#2E1322', '#F7ECE6', '#D9A87E'] },
  { name: 'Teal & Coral',           vars: { '--bg': '#0B2A33', '--bg-alt': '#103943', '--bg-cream': '#F3EEE4', '--accent': '#E07856', '--accent-bright': '#EE9171', '--navy-900': '#061B22' }, swatch: ['#0B2A33', '#F3EEE4', '#E07856'] },
  { name: 'Royal Indigo',           vars: { '--bg': '#1A1A4E', '--bg-alt': '#252563', '--bg-cream': '#F0EEDF', '--accent': '#E5BB6B', '--accent-bright': '#F2CD83', '--navy-900': '#101035' }, swatch: ['#1A1A4E', '#F0EEDF', '#E5BB6B'] },
  { name: 'Espresso & Gold',        vars: { '--bg': '#241A12', '--bg-alt': '#33261A', '--bg-cream': '#F4EDDE', '--accent': '#D4A642', '--accent-bright': '#E6BC5C', '--navy-900': '#170F09' }, swatch: ['#241A12', '#F4EDDE', '#D4A642'] },
  { name: 'Slate & Amber',          vars: { '--bg': '#1C2128', '--bg-alt': '#262D37', '--bg-cream': '#EFEBE2', '--accent': '#E0A23C', '--accent-bright': '#F0B756', '--navy-900': '#11151A' }, swatch: ['#1C2128', '#EFEBE2', '#E0A23C'] },
  { name: 'Vegas Neon',             vars: { '--bg': '#170A2E', '--bg-alt': '#231142', '--bg-cream': '#F2EDE2', '--accent': '#FF6BB5', '--accent-bright': '#FF8AC6', '--navy-900': '#0E0520' }, swatch: ['#170A2E', '#F2EDE2', '#FF6BB5'] },
  { name: 'Forest Playbill',        vars: { '--bg': '#15241A', '--bg-alt': '#1F3325', '--bg-cream': '#F1EFE2', '--accent': '#C9A24F', '--accent-bright': '#DCB663', '--navy-900': '#0C1710' }, swatch: ['#15241A', '#F1EFE2', '#C9A24F'] },
  { name: 'Ivory & Navy (light)',   vars: { '--bg': '#1A2A4A', '--bg-alt': '#22355C', '--bg-cream': '#FBF7EE', '--accent': '#C08A2E', '--accent-bright': '#D6A33F', '--navy-900': '#12203B' }, swatch: ['#FBF7EE', '#1A2A4A', '#C08A2E'] },
  { name: 'Mono Marquee',           vars: { '--bg': '#161616', '--bg-alt': '#202020', '--bg-cream': '#F2F0EB', '--accent': '#E8E2D2', '--accent-bright': '#FFFFFF', '--navy-900': '#0C0C0C' }, swatch: ['#161616', '#F2F0EB', '#E8E2D2'] },
];

const TYPE_PAIRS = [
  { name: 'Figtree (clean, readable)',   display: 'Figtree',             sans: 'Figtree',        script: 'Pinyon Script' },
  { name: 'Cormorant + Inter',           display: 'Cormorant Garamond',  sans: 'Inter',          script: 'Allura' },
  { name: 'DM Serif + DM Sans',          display: 'DM Serif Display',    sans: 'DM Sans',        script: 'Great Vibes' },
  { name: 'Fraunces + Manrope',          display: 'Fraunces',            sans: 'Manrope',        script: 'Pinyon Script' },
  { name: 'Bodoni + Jost',               display: 'Bodoni Moda',         sans: 'Jost',           script: 'Tangerine' },
  { name: 'Libre Baskerville + Work Sans', display: 'Libre Baskerville', sans: 'Work Sans',      script: 'Dancing Script' },
  { name: 'EB Garamond + Mulish',        display: 'EB Garamond',         sans: 'Mulish',         script: 'Parisienne' },
  { name: 'Prata + Outfit',              display: 'Prata',               sans: 'Outfit',         script: 'Sacramento' },
  { name: 'Marcellus + Nunito Sans',     display: 'Marcellus',           sans: 'Nunito Sans',    script: 'Italianno' },
  { name: 'Lora + Karla',                display: 'Lora',                sans: 'Karla',          script: 'Petit Formal Script' },
  { name: 'Spectral + Figtree',          display: 'Spectral',            sans: 'Figtree',        script: 'Yellowtail' },
  { name: 'Cardo + Source Sans',         display: 'Cardo',               sans: 'Source Sans 3',  script: 'Alex Brush' },
  { name: 'Bricolage + Hanken',          display: 'Bricolage Grotesque', sans: 'Hanken Grotesk', script: 'Pinyon Script' },
  { name: 'Domine + Albert Sans',        display: 'Domine',              sans: 'Albert Sans',    script: 'Mr Dafoe' },
  { name: 'Newsreader + Poppins',        display: 'Newsreader',          sans: 'Poppins',        script: 'Great Vibes' },
  { name: 'Italiana + Jost',             display: 'Italiana',            sans: 'Jost',           script: 'Allura' },
  { name: 'Fraunces + Figtree',          display: 'Fraunces',            sans: 'Figtree',        script: 'Tangerine' },
  { name: 'Playfair + Work Sans',        display: 'Playfair Display',    sans: 'Work Sans',      script: 'Dancing Script' },
  { name: 'Cormorant + Poppins',         display: 'Cormorant Garamond',  sans: 'Poppins',        script: 'Sacramento' },
  { name: 'DM Serif + Karla',            display: 'DM Serif Display',    sans: 'Karla',          script: 'Great Vibes' },
  { name: 'Prata + Manrope',             display: 'Prata',               sans: 'Manrope',        script: 'Parisienne' },
  { name: 'Marcellus + Mulish',          display: 'Marcellus',           sans: 'Mulish',         script: 'Yellowtail' },
];

function applyPalette(p) {
  const root = document.documentElement;
  Object.entries(p.vars).forEach(([k, v]) => root.style.setProperty(k, v));
}
function applyTypes(t) {
  const root = document.documentElement;
  root.style.setProperty('--f-display', `'${t.display}', serif`);
  root.style.setProperty('--f-sans', `'${t.sans}', system-ui, sans-serif`);
  root.style.setProperty('--f-script', `'${t.script}', cursive`);
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "paletteIdx": 0,
  "typeIdx": 0,
  "cardLayout": "classic",
  "photoShape": "rounded",
  "btnShape": "rounded",
  "textScale": 1
}/*EDITMODE-END*/;

// Custom font dropdown — each option rendered in its actual typeface.
function FontDropdown({ pairs, value, onChange }) {
  const [open, setOpen] = useStateApp(false);
  const cur = pairs[value] || pairs[0];
  return (
    <div className="fontdd-wrap">
      <div className="fontdd-label">Type Pair — pick a look</div>
      <button className="fontdd-trigger" onClick={() => setOpen((o) => !o)}>
        <span style={{ fontFamily: `'${cur.display}', serif`, fontSize: 17 }}>{cur.display}</span>
        <span style={{ opacity: 0.5, fontSize: 12 }}>{open ? '▲' : '▼'}</span>
      </button>
      {open && (
        <div className="fontdd-list">
          {pairs.map((p, i) => (
            <button
              key={i}
              className={`fontdd-item ${i === value ? 'sel' : ''}`}
              onClick={() => { onChange(i); setOpen(false); }}
            >
              <span className="fd-display" style={{ fontFamily: `'${p.display}', serif` }}>{p.display}</span>
              <span className="fd-sans" style={{ fontFamily: `'${p.sans}', sans-serif` }}>Body: {p.sans} · {p.script}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function WandTweaks({ onCardLayoutChange }) {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  useEffectApp(() => { applyPalette(PALETTES[t.paletteIdx]); }, [t.paletteIdx]);
  useEffectApp(() => { applyTypes(TYPE_PAIRS[t.typeIdx]); }, [t.typeIdx]);
  useEffectApp(() => { onCardLayoutChange && onCardLayoutChange(t.cardLayout); }, [t.cardLayout]);
  useEffectApp(() => { document.body.setAttribute('data-photo-shape', t.photoShape); }, [t.photoShape]);
  useEffectApp(() => { document.body.setAttribute('data-btn-shape', t.btnShape); }, [t.btnShape]);
  useEffectApp(() => { document.documentElement.style.setProperty('--app-zoom', t.textScale); }, [t.textScale]);

  return (
    <TweaksPanel>
      <TweakSection label="Color Palette" />
      <div style={{ padding: '4px 12px 12px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
        {PALETTES.map((p, i) => {
          const sel = i === t.paletteIdx;
          return (
            <button
              key={i}
              onClick={() => setTweak('paletteIdx', i)}
              style={{
                background: sel ? 'rgba(0,0,0,0.06)' : 'transparent',
                border: sel ? '1.5px solid #29261b' : '1px solid rgba(0,0,0,0.12)',
                borderRadius: 8,
                padding: 8,
                textAlign: 'left',
                cursor: 'pointer',
                color: 'inherit',
                font: 'inherit',
              }}
            >
              <div style={{ display: 'flex', gap: 3, marginBottom: 6 }}>
                {p.swatch.map((c, j) => (
                  <div key={j} style={{ width: 22, height: 22, borderRadius: 3, background: c, border: '1px solid rgba(0,0,0,0.06)' }} />
                ))}
              </div>
              <div style={{ fontSize: 10.5, opacity: 0.85, lineHeight: 1.2 }}>{p.name}</div>
            </button>
          );
        })}
      </div>

      <TweakSection label="Typography" />
      <FontDropdown pairs={TYPE_PAIRS} value={t.typeIdx} onChange={(v) => setTweak('typeIdx', v)} />

      <TweakSection label="Readability" />
      <TweakSlider
        label="Text & Page Size"
        value={t.textScale}
        min={1}
        max={1.3}
        step={0.05}
        unit="×"
        onChange={(v) => setTweak('textScale', v)}
      />

      <TweakSection label="Performer Photos" />
      <TweakRadio
        label="Photo Shape"
        value={t.photoShape}
        options={['squircle', 'circle', 'rounded']}
        onChange={(v) => setTweak('photoShape', v)}
      />

      <TweakSection label="Cards & Buttons" />
      <TweakRadio
        label="Card Corners"
        value={t.cardLayout}
        options={['sharp', 'classic', 'rounded']}
        onChange={(v) => setTweak('cardLayout', v)}
      />
      <TweakRadio
        label="Button Shape"
        value={t.btnShape}
        options={['square', 'rounded', 'pill']}
        onChange={(v) => setTweak('btnShape', v)}
      />
    </TweaksPanel>
  );
}

/* ===== Root App ===== */
// A show's own-page slug is its position id minus the "pos-" prefix.
function slugForEvent(ev) { return String((ev && ev.id) || '').replace(/^pos-/, '') || ''; }

const DOW_LONG = { SUN:'Sunday', MON:'Monday', TUE:'Tuesday', WED:'Wednesday', THU:'Thursday', FRI:'Friday', SAT:'Saturday' };

/* ===== Beautiful full show page (own URL) ===== */
// Street addresses for the two venues (The Villages, FL). MCA 2026-08-06.
// Epic Theater and Old Mill Playhouse are the SAME building (Epic Theatres at
// the Old Mill Playhouse, Spanish Springs) — every show is at this one address.
const OMP_ADDRESS = '1000 Old Mill Run, The Villages, FL 32162';
const VENUE_ADDRESS = {
  'Epic Theater': OMP_ADDRESS,
  'Epic Theatres': OMP_ADDRESS,
  'Old Mill Playhouse': OMP_ADDRESS,
};

const OMP_SUBVENUE = 'Epic Theatres, Lake Sumter Landing';
const VENUE_SUBVENUE = {
  'Epic Theater': OMP_SUBVENUE,
  'Epic Theatres': OMP_SUBVENUE,
  'Old Mill Playhouse': OMP_SUBVENUE,
};
// Break the address so "The Villages, FL 32162" sits on its own second line.
function venueAddrLines(addr) {
  const s = String(addr || '');
  const i = s.indexOf(', The Villages');
  return i === -1 ? [s] : [s.slice(0, i), s.slice(i + 2)];
}

// Turn any Vimeo/YouTube link (watch, share, manage, or already-embed) into a
// proper player URL so the owner can paste whatever link they have and it plays.
function toEmbedUrl(url) {
  const u = String(url || '').trim();
  if (!u) return '';
  // Vimeo (watch, share, manage, or already-embed). Always hide the owner
  // overlays: title=0 & byline=0 & portrait=0 (so "Michael C. Anthony" never
  // shows), plus dnt=1 (do-not-track).
  if (/vimeo\.com/i.test(u)) {
    const id = (u.match(/vimeo\.com\/(?:manage\/videos\/|video\/)?(\d{6,})/i) || [])[1];
    const h = (u.match(/[?&]h=([a-z0-9]+)/i) || u.match(/\d{6,}\/([a-z0-9]+)/i) || [])[1];
    if (id) return `https://player.vimeo.com/video/${id}?${h ? 'h=' + h + '&' : ''}title=0&byline=0&portrait=0&dnt=1`;
  }
  const yt = (u.match(/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/)|youtu\.be\/)([\w-]{6,})/i) || [])[1];
  if (yt) return `https://www.youtube.com/embed/${yt}?rel=0&modestbranding=1`;
  return u;
}

function ShowDetailPage({ event, onBack }) {
  useEffectApp(() => { window.scrollTo(0, 0); }, [event && event.id]);
  // Meta Pixel: someone viewed this specific show — a mid-funnel interest signal.
  useEffectApp(() => {
    try { window.fbq && window.fbq('track', 'ViewContent', { content_name: event && event.title, content_category: 'Show' }); } catch (e) {}
  }, [event && event.id]);
  useEffectApp(() => {
    const k = (e) => e.key === 'Escape' && onBack();
    window.addEventListener('keydown', k);
    return () => window.removeEventListener('keydown', k);
  }, [onBack]);
  if (!event) return null;

  const skillMeta = (window.SKILL_META && window.SKILL_META[event.skill || event.type]) || null;
  const dow = DOW_LONG[event.dow] || event.dow;

  // A stable per-show seed so each show gets its own (consistent) testimonials
  // and its own poster effect — variety across shows, no random re-shuffle on load.
  const seed = String(event.id || event.title || '').split('').reduce((a, c) => a + c.charCodeAt(0), 0);
  const pool = window.WAND_DATA.testimonials || [];
  const reviews = event.reviews || (pool.length
    ? Array.from({ length: Math.min(3, pool.length) }, (_, i) => pool[(seed + i) % pool.length])
    : []);

  // Videos can be a single embed URL (event.video) or a list (event.videos).
  // Each entry: a URL string, or { url, portrait?, title? }. portrait=true renders
  // a tall vertical frame; otherwise widescreen. Any mix of shapes/counts is handled.
  const videos = (event.videos && event.videos.length ? event.videos : (event.video ? [event.video] : []))
    .map((v) => (typeof v === 'string' ? { url: v } : v))
    .filter((v) => v && v.url)
    .map((v) => ({ ...v, url: toEmbedUrl(v.url) }));

  return (
    <main className="show-page">
      <section className="show-hero">
        {event.photo && <div className="show-hero-bg" style={{ backgroundImage: `url("${event.photo}")` }} aria-hidden="true"></div>}
        <div className="show-hero-veil" aria-hidden="true"></div>
        <div className="container">
          <button className="event-back" onClick={onBack}>
            <Icon name="chevronRight" size={15} stroke={2.4} style={{ transform: 'rotate(180deg)' }} /> All shows
          </button>
          <div className="show-hero-grid">
            <div className="show-poster-frame">
              {event.photo
                ? <img className="show-poster" src={event.photo} alt={event.title} />
                : <EventPosterPlaceholder style={event.posterStyle} title={event.title} />}
            </div>
            <div className="show-hero-info">
              {skillMeta && (
                <div className="show-eyebrow" style={{ color: skillMeta.color }}>
                  <span className="skill-dot" style={{ background: skillMeta.color }}></span>{skillMeta.label}
                </div>
              )}
              <h1 className="show-title">{event.title}</h1>
              {event.tagline && <p className="show-tagline">{event.tagline}</p>}
              <div className="show-meta">
                <div className="show-meta-item">
                  <span className="show-cal">
                    <span className="show-cal-m">{event.date.month}</span>
                    <span className="show-cal-d">{event.date.day}</span>
                  </span>
                  <span className="show-meta-when">
                    <span className="show-meta-dow">{dow}</span>
                    <span className="show-meta-time"><Icon name="clock" size={17} /> {event.time}</span>
                  </span>
                </div>
                <div className="show-meta-item show-meta-venue">
                  <Icon name="pin" size={22} />
                  <span className="show-meta-venue-lines">
                    <span className="show-meta-venue-name">{event.venue}{VENUE_SUBVENUE[event.venue] ? ' - ' + VENUE_SUBVENUE[event.venue] : ''}</span>
                    {VENUE_ADDRESS[event.venue] && (
                      <span className="show-meta-venue-addr">
                        {venueAddrLines(VENUE_ADDRESS[event.venue]).map((ln, i) => (
                          <React.Fragment key={i}>{i > 0 && <br />}{ln}</React.Fragment>
                        ))}
                      </span>
                    )}
                  </span>
                </div>
              </div>
              <a className="show-tickets" href={event.ticketUrl || "#/contact"} target={event.ticketUrl ? "_blank" : undefined} rel={event.ticketUrl ? "noopener noreferrer" : undefined} onClick={(e) => { try { window.fbq && window.fbq('track', 'InitiateCheckout', { content_name: event.title, content_category: 'Tickets' }); } catch (err) {} if (!event.ticketUrl) { e.preventDefault(); window.location.hash = '/contact'; } }}>
                Get Tickets <Icon name="ticket" size={18} />
              </a>
            </div>
          </div>
        </div>
      </section>

      <div className="show-body">
        <div className="container show-body-inner">
          {videos.length >= 2 ? (
            /* More than one video: description on top, videos in a row/grid below.
               Each video keeps its own true shape (wide or portrait). */
            <React.Fragment>
              <section className="show-section">
                <h2>About the Show</h2>
                <ShowParagraphs text={event.description} />
              </section>
              <section className="show-section">
                <div className="show-videos">
                  {videos.map((v, i) => (
                    <div key={i} className={`show-video show-video--${v.portrait ? 'portrait' : 'landscape'}`}>
                      <iframe src={v.url} title={v.title || `${event.title} video ${i + 1}`}
                        allow="accelerometer; autoplay; encrypted-media; fullscreen" frameBorder="0" allowFullScreen></iframe>
                    </div>
                  ))}
                </div>
              </section>
            </React.Fragment>
          ) : videos.length === 1 ? (
            /* Exactly one video: paired beside the description. */
            <section className="show-section show-about">
              <div className="show-about-copy">
                <h2>About the Show</h2>
                <ShowParagraphs text={event.description} />
              </div>
              <div className={`show-video show-video--${videos[0].portrait ? 'portrait' : 'landscape'}`}>
                <iframe src={videos[0].url} title={videos[0].title || `${event.title} video`}
                  allow="accelerometer; autoplay; encrypted-media; fullscreen" frameBorder="0" allowFullScreen></iframe>
              </div>
            </section>
          ) : (
            /* No video yet: just the description, full width. Add a real Vimeo or
               YouTube link to the show in the back office and the player appears
               here automatically — no placeholder box in the meantime. */
            <section className="show-section">
              <h2>About the Show</h2>
              <ShowParagraphs text={event.description} />
            </section>
          )}

          {reviews.length > 0 && (
            <section className="show-section">
              <h2>What Guests Are Saying</h2>
              <div className="show-reviews">
                {reviews.map((r, i) => (
                  <div className="show-review" key={i}>
                    <div className="stars">★ ★ ★ ★ ★</div>
                    <p>"{r.quote}"</p>
                    <div className="show-review-author">– {r.author}</div>
                  </div>
                ))}
              </div>
            </section>
          )}

        </div>
      </div>

      <section className="show-cta">
        <div className="container show-cta-inner">
          <div>
            <div className="show-cta-title">Don't miss {event.title}</div>
            <div className="show-cta-sub">{dow}, {event.date.month} {event.date.day} · {event.time} · {event.venue}</div>
          </div>
          <a className="show-tickets" href={event.ticketUrl || "#/contact"} target={event.ticketUrl ? "_blank" : undefined} rel={event.ticketUrl ? "noopener noreferrer" : undefined} onClick={(e) => { try { window.fbq && window.fbq('track', 'InitiateCheckout', { content_name: event.title, content_category: 'Tickets' }); } catch (err) {} if (!event.ticketUrl) { e.preventDefault(); window.location.hash = '/contact'; } }}>
            Get Tickets <Icon name="ticket" size={18} />
          </a>
        </div>
      </section>
    </main>
  );
}

function App() {
  const [route] = useHashRoute();
  const [cardLayout, setCardLayout] = useStateApp('classic');

  // Merge Gigdini performers into the curated event positions.
  // First render uses the inline fallback (sync) so nothing flashes empty;
  // then we load the live roster (or snapshot) and refine.
  const [events, setEvents] = useStateApp(() =>
    window.GIGDINI.fillPositions(window.WAND_DATA.positions, window.GIGDINI.inlineRoster())
  );
  const [rosterSource, setRosterSource] = useStateApp('inline-fallback');

  useEffectApp(() => {
    let alive = true;
    window.GIGDINI.loadRoster().then((roster) => {
      if (!alive) return;
      const staticEvents = window.GIGDINI.fillPositions(window.WAND_DATA.positions, roster);
      setEvents(staticEvents);
      setRosterSource(roster.source);
      // Then pull LIVE shows from the backend and merge them over the static
      // set by date. If the backend is unreachable this resolves to the same
      // static events, so the site never breaks.
      window.GIGDINI.loadLiveShows(staticEvents).then((merged) => {
        if (!alive || !merged || !merged.length) return;
        setEvents(merged);
        // Preview deep-link: ?show=<bookingId> opens that show's page directly
        // (used by the admin "View Live Page" button).
        try {
          const showId = new URLSearchParams(window.location.search).get('show');
          if (showId && window.location.hash.indexOf('/events/') !== 0) {
            const ev = merged.find(e => String(e.performerId) === String(showId));
            if (ev) window.location.hash = '/events/' + slugForEvent(ev);
          }
        } catch (e) { /* deep-link is best-effort */ }
      });
    });
    return () => { alive = false; };
  }, []);

  // Jump the window to the top whenever the route changes (so opening a
  // show page doesn't land you mid-scroll).
  useEffectApp(() => { window.scrollTo(0, 0); }, [route]);
  const goToEvent = (ev) => { window.location.hash = '/events/' + slugForEvent(ev); };
  // Header CTA → send people to the shows list (on the home page), where they
  // pick a show and that page has the real per-show Get Tickets.
  const openTickets = () => {
    window.location.hash = '/';
    setTimeout(() => { const el = document.getElementById('upcoming'); if (el) el.scrollIntoView({ behavior: 'smooth' }); }, 120);
  };

  let page;
  if (route.indexOf('/events/') === 0) {
    const slug = route.slice('/events/'.length);
    const ev = events.find(e => slugForEvent(e) === slug);
    page = ev
      ? <ShowDetailPage event={ev} onBack={() => { window.location.hash = '/'; }} />
      : <HomePage events={events} onCardClick={goToEvent} layout={cardLayout} />;
  }
  else if (route === '/venues')  page = <VenuesPage />;
  else if (route === '/faq')     page = <FAQPage />;
  else if (route === '/contact') page = <ContactPage />;
  else if (route === '/newsletter') page = <NewsletterPage />;
  else if (route === '/privacy') page = <PrivacyPage />;
  else                           page = <HomePage events={events} onCardClick={goToEvent} layout={cardLayout} />;

  return (
    <>
      <div className="app-shell">
        <Nav route={route} onTickets={openTickets} />
        {page}
        <Footer />
      </div>
      <NewsletterPopup />
      <WandTweaks onCardLayoutChange={setCardLayout} />
    </>
  );
}

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