// Main app: tweaks panel, lightbox overlay, mock email collection.

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "template": "product",
  "pricingModel": "free",
  "view": "template",
  "demoState": "default",
  "viewport": "auto",
  "customWidth": 1024
}/*EDITMODE-END*/;

// ─── Gallery images ───────────────────────────────────────────────────────────

const GALLERY_IMAGES = [
  { src: 'assets/product-listing-1.jpg', label: 'Overview' },
  { src: 'assets/product-listing-2.jpg', label: 'Home — My Journey' },
  { src: 'assets/product-listing-3.jpg', label: 'Workout Hub' },
  { src: 'assets/product-listing-4.jpg', label: 'Daily Workout' },
  { src: 'assets/product-listing-5.jpg', label: 'Training' },
  { src: 'assets/product-listing-6.jpg', label: 'Training Tracker' },
  { src: 'assets/product-listing-7.jpg', label: 'Nutrition Hub' },
  { src: 'assets/product-listing-8.jpg', label: 'Daily Meal Planner' },
  { src: 'assets/product-listing-9.jpg', label: 'Pantry & Vitamins' },
];

// ─── Gallery (controlled, no internal arrows) ─────────────────────────────────

const ImageGalleryControlled = ({ idx, setIdx, onClose }) => {
  const current = GALLERY_IMAGES[idx];
  const narrowScreen = window.innerWidth < 600;

  React.useEffect(() => {
    const onKey = e => {
      if (e.key === 'Escape')      onClose();
      if (e.key === 'ArrowRight')  setIdx(i => (i + 1) % GALLERY_IMAGES.length);
      if (e.key === 'ArrowLeft')   setIdx(i => (i - 1 + GALLERY_IMAGES.length) % GALLERY_IMAGES.length);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  return (
    <div style={{
      width: 'min(86vw, 1400px)', height: narrowScreen ? 'min(80vh, 560px)' : 'min(94vh, 1100px)',
      display: 'flex', gap: 32, alignItems: 'stretch',
      animation: 'pop 240ms ease',
    }}>
      {/* Main image */}
      <div style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <img key={current.src} src={current.src} alt={current.label}
          onClick={e => e.stopPropagation()}
          style={{
            maxWidth: '100%', maxHeight: '100%', objectFit: 'contain',
            borderRadius: 16,
            filter: 'drop-shadow(0 20px 60px rgba(0,0,0,0.35))',
            animation: 'fadeIn 220ms ease', cursor: 'default',
          }}/>
      </div>

      {/* Thumbnail column — hidden on narrow screens */}
      {!narrowScreen && (
        <div className="spl-gallery-thumbs" style={{
          width: 152, display: 'flex', flexDirection: 'column', gap: 12,
          flexShrink: 0, justifyContent: 'flex-start',
          maxHeight: '100%', overflowY: 'auto',
          paddingRight: 16,
          scrollbarGutter: 'stable',
          scrollbarWidth: 'thin', scrollbarColor: 'rgba(255,255,255,0.7) transparent',
        }}>
          <style>{`
            .spl-gallery-thumbs::-webkit-scrollbar { width: 6px; }
            .spl-gallery-thumbs::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.7); border-radius: 3px; }
            .spl-gallery-thumbs::-webkit-scrollbar-track { background: transparent; }
          `}</style>
          {GALLERY_IMAGES.map((g, i) => (
            <button key={g.src} onClick={e => { e.stopPropagation(); setIdx(i); }} aria-label={g.label}
              style={{
                width: 120, height: 120, padding: 0, cursor: 'pointer', flexShrink: 0,
                borderRadius: 14, overflow: 'hidden',
                border: i === idx ? '2px solid #fff' : '2px solid rgba(255,255,255,0.18)',
                background: `url(${g.src}) #1a1a1a 50% / cover no-repeat`,
                opacity: i === idx ? 1 : 0.55,
                transition: 'all 160ms ease',
              }}/>
          ))}
        </div>
      )}
    </div>
  );
};

// ─── Gallery + floating prev/next buttons ─────────────────────────────────────

const navBtnStyle = side => ({
  position: 'fixed', [side]: 24, top: '50%', transform: 'translateY(-50%)',
  width: 56, height: 56, borderRadius: '50%',
  background: 'rgba(255,255,255,0.7)', border: 'none', color: '#101010', cursor: 'pointer',
  display: 'grid', placeItems: 'center', zIndex: 4,
  backdropFilter: 'blur(8px)',
});

const GalleryWithNav = ({ initialIdx, onClose }) => {
  const safeStart = Number.isInteger(initialIdx)
    ? Math.max(0, Math.min(GALLERY_IMAGES.length - 1, initialIdx))
    : 0;
  const [idx, setIdx] = React.useState(safeStart);
  const prev = () => setIdx(i => (i - 1 + GALLERY_IMAGES.length) % GALLERY_IMAGES.length);
  const next = () => setIdx(i => (i + 1) % GALLERY_IMAGES.length);
  return (
    <>
      <button onClick={e => { e.stopPropagation(); prev(); }} aria-label="Previous" style={navBtnStyle('left')}>
        <svg width="22" height="22" viewBox="0 0 20 20"><path d="M13 4l-6 6 6 6" stroke="#101010" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
      </button>
      <button onClick={e => { e.stopPropagation(); next(); }} aria-label="Next" style={navBtnStyle('right')}>
        <svg width="22" height="22" viewBox="0 0 20 20"><path d="M7 4l6 6-6 6" stroke="#101010" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
      </button>
      <ImageGalleryControlled key={`g-${initialIdx}`} idx={idx} setIdx={setIdx} onClose={onClose}/>
    </>
  );
};

// ─── Lightbox ─────────────────────────────────────────────────────────────────

const Lightbox = ({ payload, onClose }) => {
  const narrowScreen = window.innerWidth < 600;

  React.useEffect(() => {
    if (!payload) return;
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    const prevOverflow   = document.body.style.overflow;
    const prevPR         = document.body.style.paddingRight;
    const scrollbarW     = window.innerWidth - document.documentElement.clientWidth;
    document.body.style.overflow = 'hidden';
    if (scrollbarW > 0) document.body.style.paddingRight = scrollbarW + 'px';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.body.style.overflow     = prevOverflow;
      document.body.style.paddingRight = prevPR;
    };
  }, [payload]);

  if (!payload) return null;

  return ReactDOM.createPortal(
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 50,
      background: 'rgba(11,11,11,0.88)',
      display: 'grid', placeItems: 'center', backdropFilter: 'blur(12px)',
      animation: 'fadeIn 220ms ease', overflow: 'hidden',
    }}>
      <style>{`
        @keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
        @keyframes pop { from { opacity: 0; transform: scale(0.96) } to { opacity: 1; transform: scale(1) } }
      `}</style>
      <button onClick={onClose} aria-label="Close" style={{
        position: 'absolute', top: 24, right: 24, width: 48, height: 48, borderRadius: '50%',
        background: 'rgba(255,255,255,0.7)', border: 'none', color: '#101010', cursor: 'pointer',
        display: 'grid', placeItems: 'center', zIndex: 3, backdropFilter: 'blur(8px)',
      }}>
        <svg width="20" height="20" viewBox="0 0 22 22"><path d="M3 3l16 16M19 3L3 19" stroke="#101010" strokeWidth="2" strokeLinecap="round"/></svg>
      </button>

      {payload.kind === 'ipad' && (
        <GalleryWithNav key={`g-${payload.idx}`} initialIdx={payload.idx} onClose={onClose}/>
      )}

      {payload.kind !== 'ipad' && (
        <div onClick={e => e.stopPropagation()} style={{ animation: 'pop 240ms ease' }}>
          {payload.kind === 'content' && (
            <div style={{
              width: 'min(90vw, 1280px)', aspectRatio: '16/9',
              borderRadius: narrowScreen ? 16 : 24, overflow: 'hidden',
              background: `url(${payload.src}) center/cover no-repeat`, position: 'relative',
            }}>
              <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }}>
                <PlayBadge/>
              </div>
            </div>
          )}
          {payload.kind === 'newsletter' && (
            <div style={{
              maxWidth: 'min(94vw, 1400px)',
              borderRadius: narrowScreen ? 16 : 24,
              overflow: 'hidden',
              background: '#fff',
            }}>
              <img src="assets/newsletter-issues.jpg" alt="Newsletter previews"
                style={{ width: '100%', height: 'auto', display: 'block' }}/>
            </div>
          )}
        </div>
      )}
    </div>,
    document.body
  );
};

// ─── Subscriber list ──────────────────────────────────────────────────────────

const STORAGE_KEY = 'spl_subscribers';

function loadSubscribers() {
  try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
  catch { return []; }
}

function addSubscriber(email, template) {
  const list = loadSubscribers();
  list.push({ email, template, date: new Date().toISOString() });
  localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
  return list;
}

function downloadCsv(list) {
  const rows = [['email','template','date_iso'], ...list.map(s => [s.email, s.template, s.date])];
  const csv  = rows.map(r => r.map(c => `"${String(c).replace(/"/g,'""')}"`).join(',')).join('\n');
  const blob = new Blob([csv], { type: 'text/csv' });
  const url  = URL.createObjectURL(blob);
  const a    = document.createElement('a');
  a.href = url; a.download = 'subscribers.csv';
  document.body.appendChild(a); a.click(); a.remove();
  URL.revokeObjectURL(url);
}

// ─── Subscribers viewer ───────────────────────────────────────────────────────

const SubscribersViewer = ({ open, onClose }) => {
  const [list, setList] = React.useState(loadSubscribers());
  React.useEffect(() => { if (open) setList(loadSubscribers()); }, [open]);
  if (!open) return null;
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 60, background: 'rgba(11,11,11,0.6)', display: 'grid', placeItems: 'center' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 'min(720px, 94vw)', maxHeight: '80vh', background: '#fff', borderRadius: 20, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '20px 24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #f0f0f0' }}>
          <div style={{ fontFamily: 'Inter', fontWeight: 600, fontSize: 18, color: '#101010' }}>Collected emails — {list.length}</div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button onClick={() => downloadCsv(list)} disabled={!list.length} style={{ padding: '8px 14px', borderRadius: 8, border: 'none', background: '#101010', color: '#fff', fontFamily: 'Inter', fontWeight: 500, fontSize: 13, cursor: list.length ? 'pointer' : 'not-allowed', opacity: list.length ? 1 : 0.5 }}>Download CSV</button>
            <button onClick={() => { if (confirm('Clear all collected emails?')) { localStorage.removeItem(STORAGE_KEY); setList([]); } }} style={{ padding: '8px 14px', borderRadius: 8, border: '1px solid #e0e0e0', background: '#fff', fontFamily: 'Inter', fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>Clear</button>
            <button onClick={onClose} style={{ padding: '8px 14px', borderRadius: 8, border: '1px solid #e0e0e0', background: '#fff', fontFamily: 'Inter', fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>Close</button>
          </div>
        </div>
        <div style={{ overflowY: 'auto', flex: 1 }}>
          {list.length === 0 ? (
            <div style={{ padding: 48, textAlign: 'center', color: '#8b8b8b', fontFamily: 'Inter', fontSize: 14 }}>No emails collected yet. Submit the form to add one.</div>
          ) : (
            <table style={{ width: '100%', borderCollapse: 'collapse', fontFamily: 'Inter', fontSize: 13 }}>
              <thead><tr style={{ background: '#f8f8f8' }}>
                <th style={{ padding: '10px 16px', textAlign: 'left', color: '#696a6d', fontWeight: 500 }}>Email</th>
                <th style={{ padding: '10px 16px', textAlign: 'left', color: '#696a6d', fontWeight: 500 }}>Template</th>
                <th style={{ padding: '10px 16px', textAlign: 'left', color: '#696a6d', fontWeight: 500 }}>Date</th>
              </tr></thead>
              <tbody>{list.slice().reverse().map((s, i) => (
                <tr key={i} style={{ borderTop: '1px solid #f0f0f0' }}>
                  <td style={{ padding: '10px 16px', color: '#101010' }}>{s.email}</td>
                  <td style={{ padding: '10px 16px', color: '#696a6d', textTransform: 'capitalize' }}>{s.template}</td>
                  <td style={{ padding: '10px 16px', color: '#696a6d' }}>{new Date(s.date).toLocaleString()}</td>
                </tr>
              ))}</tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
};

// ─── Committed width input — only commits on blur / Enter so typing isn't
//     clamped per-keystroke (the stock TweakNumber clamps live and prevents
//     reaching values like 1024 from a default of 1024 → 1 keystroke). ───
const CommittedWidthInput = ({ value, min, max, onChange }) => {
  const [draft, setDraft] = React.useState(String(value));
  React.useEffect(() => { setDraft(String(value)); }, [value]);
  const commit = () => {
    const n = parseInt(draft, 10);
    if (!Number.isFinite(n)) { setDraft(String(value)); return; }
    const clamped = Math.max(min, Math.min(max, n));
    setDraft(String(clamped));
    if (clamped !== value) onChange(clamped);
  };
  return (
    <TweakRow label="Exact">
      <input
        className="twk-field"
        type="number"
        inputMode="numeric"
        value={draft}
        min={min}
        max={max}
        onChange={e => setDraft(e.target.value)}
        onBlur={commit}
        onKeyDown={e => { if (e.key === 'Enter') { commit(); e.currentTarget.blur(); } }}
        style={{ textAlign: 'right' }}
      />
    </TweakRow>
  );
};

// ─── App ──────────────────────────────────────────────────────────────────────

// ─── URL <-> template sync ────────────────────────────────────────────────────
// Allows deep-linking to a specific template via ?t=newsletter etc.
// Works on Vercel / any static host where the Tweaks panel isn't exposed.
const VALID_TEMPLATES = ['product', 'extraText', 'content', 'newsletter'];

function matchTemplate(raw) {
  if (!raw) return null;
  return VALID_TEMPLATES.find(x => x.toLowerCase() === raw.toLowerCase()) || null;
}

function readTemplateFromUrl() {
  if (typeof window === 'undefined') return null;

  // 1) Page-level override (set by inline script in a per-variant HTML file).
  if (window.__INITIAL_TEMPLATE) {
    const v = matchTemplate(window.__INITIAL_TEMPLATE);
    if (v) return v;
  }

  // 2) Pathname → template. Handles both /newsletter and /newsletter.html
  //    so it works on Vercel with or without `cleanUrls`.
  const seg = window.location.pathname.split('/').pop().replace(/\.html?$/i, '');
  const fromPath = matchTemplate(seg);
  if (fromPath) return fromPath;

  // 3) Query-param fallback: ?t=newsletter or ?template=newsletter
  const params = new URLSearchParams(window.location.search);
  return matchTemplate(params.get('t') || params.get('template'));
}

function writeTemplateToUrl(template) {
  if (typeof window === 'undefined') return;
  // If the pathname already designates a template (e.g. /newsletter.html),
  // don't add a redundant ?t= — leave the URL alone.
  const seg = window.location.pathname.split('/').pop().replace(/\.html?$/i, '');
  if (matchTemplate(seg)) return;

  const params = new URLSearchParams(window.location.search);
  if (template && template !== 'product') params.set('t', template);
  else                                    params.delete('t');
  const qs = params.toString();
  const next = window.location.pathname + (qs ? '?' + qs : '') + window.location.hash;
  window.history.replaceState(null, '', next);
}

function App() {
  const [t, setTweak]     = useTweaks(TWEAK_DEFAULTS);
  const [formState, setFormState] = React.useState('default');
  const [lightbox, setLightbox]   = React.useState(null);
  const [viewer,   setViewer]     = React.useState(false);
  const [legalPage, setLegalPage] = React.useState(null);  // null | 'privacy' | 'terms'

  // Footer links dispatch 'openLegal' — open the matching reading overlay.
  React.useEffect(() => {
    const onOpenLegal = e => setLegalPage(e.detail === 'terms' ? 'terms' : 'privacy');
    window.addEventListener('openLegal', onOpenLegal);
    return () => window.removeEventListener('openLegal', onOpenLegal);
  }, []);

  // On mount, pick up ?t=… from the URL and apply it.
  React.useEffect(() => {
    const fromUrl = readTemplateFromUrl();
    const patch = {};
    if (fromUrl && fromUrl !== t.template) patch.template = fromUrl;
    // Page-level overrides for pricing model & view (set by per-variant HTML files).
    if (window.__INITIAL_PRICING_MODEL === 'paid' || window.__INITIAL_PRICING_MODEL === 'free') {
      patch.pricingModel = window.__INITIAL_PRICING_MODEL;
    }
    // View can come from a page-level global or a ?view= query param (the query
    // param wins, so a Stripe success redirect / shared link can deep-link
    // straight to the payment or thank-you screen).
    const viewParam = new URLSearchParams(window.location.search).get('view');
    const initView = viewParam || window.__INITIAL_VIEW;
    if (initView === 'payment' || initView === 'template' || initView === 'thanks') {
      patch.view = initView;
    }
    if (Object.keys(patch).length) setTweak(patch);
    // Also handle browser back/forward.
    const onPop = () => {
      const v = readTemplateFromUrl() || 'product';
      setTweak('template', v);
    };
    window.addEventListener('popstate', onPop);
    return () => window.removeEventListener('popstate', onPop);
  }, []);

  // Keep the URL in sync whenever the template changes.
  React.useEffect(() => { writeTemplateToUrl(t.template); }, [t.template]);

  // Apply / clear the preview-viewport override whenever the tweak changes.
  // __forcedBp accepts: null | 'desktop' | 'tablet' | 'mobile' | <number px>
  React.useEffect(() => {
    let forced = null;
    if      (t.viewport === 'auto')   forced = null;
    else if (t.viewport === 'custom') forced = t.customWidth;
    else                              forced = t.viewport;
    window.__forcedBp = forced;
    window.dispatchEvent(new Event('forcedBpChange'));
  }, [t.viewport, t.customWidth]);

  // If pricing is switched back to Free while looking at payment/thank-you, snap back.
  React.useEffect(() => {
    if (t.pricingModel === 'free' && t.view !== 'template') setTweak('view', 'template');
  }, [t.pricingModel]);

  // Scroll to top when switching between template <> payment so the user
  // lands on the new view's header, not mid-page.
  React.useEffect(() => { window.scrollTo({ top: 0, behavior: 'instant' }); }, [t.view]);

  const demoEmail      = t.demoState === 'success' ? 'yeonnytriesyoga@gmail.com' : '';
  const forcedFormState = t.demoState === 'success' ? 'success' : 'default';
  const effectiveFormState = t.demoState === 'default' ? formState : forcedFormState;

  React.useEffect(() => { setFormState('default'); }, [t.template, t.demoState]);

  const submitEmail = email => {
    // Always keep a local copy so the Tweaks "View collected emails" viewer
    // still works in preview / when offline.
    addSubscriber(email, t.template);
    setFormState('success');
    setTimeout(() => setFormState('default'), 4000);

    // Forward to Brevo via our serverless function. Skipped in the editor
    // sandbox (no /api route) — fails silently so the UI still shows success.
    fetch('/api/subscribe', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ email, template: t.template }),
    }).catch(err => console.warn('subscribe POST failed', err));
  };

  // ── Product metadata per template — drives PricePill + PaymentPage ──
  // delivery: 'download' → secure file link  |  'watch' → gated video access link.
  // assetLabel shows on the Thank-you delivery card. id maps to the PRODUCTS
  // table inside the Cloudflare Worker (see SETUP.md Part D).
  const PRODUCT_META = {
    product:    { id: 'product',    title: 'Wellness Yoga Journey by Tara',   image: 'assets/product-listing-1.jpg', price: 20, delivery: 'download', assetLabel: 'PDF + template pack' },
    extraText:  { id: 'extraText',  title: 'Wellness Yoga Journey by Tara',   image: 'assets/product-listing-1.jpg', price: 20, delivery: 'download', assetLabel: 'PDF + template pack' },
    content:    { id: 'content',    title: '10 Hours Breathwork with yoga',   image: 'assets/content-cover.jpg',     price: 15, delivery: 'download', assetLabel: 'Video access', subLabel: 'Video' },
    newsletter: { id: 'newsletter', title: 'Advanced Yoga Newsletter',         image: 'assets/newsletter-preview.jpg', price: 8,  delivery: 'download', assetLabel: 'eBook (PDF)' },
  };
  const product = PRODUCT_META[t.template] || PRODUCT_META.product;

  const pricingValue = {
    model:    t.pricingModel,
    price:    String(product.price),
    currency: 'CAD',
    onPurchase: () => setTweak('view', 'payment'),
  };

  const sharedProps = {
    formState: effectiveFormState,
    submitEmail,
    openLightbox: setLightbox,
    defaultEmail: demoEmail,
    forcedError: t.demoState === 'error' ? 'Missing Field' : null,
  };

  const showingPayment = t.pricingModel === 'paid' && (t.view === 'payment' || t.view === 'thanks');

  return (
    <>
      <PricingContext.Provider value={pricingValue}>
        {legalPage ? (
          <LegalPage page={legalPage} onClose={() => setLegalPage(null)}/>
        ) : showingPayment ? (
          <PaymentPage
            product={product}
            demoState={t.demoState}
            forceDone={t.view === 'thanks'}
            onBack={() => setTweak('view', 'template')}/>
        ) : (
          <>
            {t.template === 'product'    && <ProductTemplate          {...sharedProps}/>}
            {t.template === 'extraText'  && <ProductExtraTextTemplate {...sharedProps}/>}
            {t.template === 'content'    && <ContentTemplate          {...sharedProps}/>}
            {t.template === 'newsletter' && <NewsletterTemplate       {...sharedProps}/>}
          </>
        )}
      </PricingContext.Provider>

      <Lightbox payload={lightbox} onClose={() => setLightbox(null)}/>
      <SubscribersViewer open={viewer} onClose={() => setViewer(false)}/>

      <TweaksPanel title="Tweaks">
        <TweakSection label="Preview viewport"/>
        <TweakSelect
          label="Width"
          value={t.viewport}
          options={[
            { value: 'auto',         label: 'Auto (follow window)' },
            { value: 400,            label: 'Small Mobile — 320–480 px' },
            { value: 640,            label: 'Large Mobile — 481–767 px' },
            { value: 900,            label: 'Tablet — 768–1024 px' },
            { value: 1100,           label: 'Desktop — 1025–1200 px' },
            { value: 1440,           label: 'Large Desktop — > 1200 px' },
            { value: 'custom',       label: 'Custom…' },
          ]}
          onChange={v => {
            // TweakSelect returns strings; coerce numeric width presets back to numbers.
            const n = Number(v);
            setTweak('viewport', (v === 'auto' || v === 'custom') ? v : (Number.isFinite(n) ? n : v));
          }}/>
        <div style={{
          fontSize: 11, lineHeight: 1.5, color: '#8b8b8b',
          padding: '6px 12px 10px', fontFamily: 'Inter',
        }}>
          <div style={{ color: '#101010', fontWeight: 600, marginBottom: 4 }}>Breakpoint ranges</div>
          <div>• Small Mobile: 320 – 480 px</div>
          <div>• Large Mobile: 481 – 767 px</div>
          <div>• Tablet: 768 – 1024 px</div>
          <div>• Desktop: 1025 – 1200 px</div>
          <div>• Large Desktop: &gt; 1200 px (e.g. 1400, 1920)</div>
        </div>
        {t.viewport === 'custom' && (
          <>
            <TweakSlider
              label="Custom"
              value={t.customWidth}
              min={320} max={1920} step={1} unit="px"
              onChange={v => setTweak('customWidth', v)}/>
            <CommittedWidthInput
              value={t.customWidth}
              min={320} max={1920}
              onChange={v => setTweak('customWidth', v)}/>
          </>
        )}
        <TweakSection label="Template"/>
        <TweakSelect
          label="Variant"
          value={t.template}
          options={[
            { value: 'product',    label: 'Product' },
            { value: 'extraText',  label: 'Extra text' },
            { value: 'content',    label: 'Content' },
            { value: 'newsletter', label: 'News' },
          ]}
          onChange={v => { setLegalPage(null); setTweak('template', v); }}/>
        <TweakSection label="Pricing"/>
        <TweakRadio
          label="Model"
          value={t.pricingModel}
          options={[
            { value: 'free', label: 'Free' },
            { value: 'paid', label: 'Paid' },
          ]}
          onChange={v => { setLegalPage(null); setTweak('pricingModel', v); }}/>
        {t.pricingModel === 'paid' && (
          <TweakRadio
            label="View"
            value={t.view}
            options={[
              { value: 'template', label: 'Template' },
              { value: 'payment',  label: 'Payment'  },
              { value: 'thanks',   label: 'Thank you' },
            ]}
            onChange={v => { setLegalPage(null); setTweak('view', v); }}/>
        )}
        <TweakSection label="Demo state"/>
        <TweakSelect
          label="State"
          value={t.demoState}
          options={[
            { value: 'default', label: 'Default (live)' },
            { value: 'success', label: 'Success' },
            { value: 'error',   label: 'Error' },
          ]}
          onChange={v => setTweak('demoState', v)}/>
        <TweakSection label="Email collection"/>
        <TweakButton label="View collected emails" onClick={() => setViewer(true)}/>
        <TweakSection label="Legal pages"/>
        <TweakButton label="Privacy Policy" onClick={() => setLegalPage('privacy')}/>
        <TweakButton label="Terms and Conditions" onClick={() => setLegalPage('terms')}/>
      </TweaksPanel>
    </>
  );
}

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