/* sameframer-app.jsx — SameFrame landing page.
 *
 * Hero is a 100vh full-bleed canvas where the timeline plays. The DOM is
 * fully static; the engine flips classes and data attributes to drive the
 * animation. Below the hero are the persistent sections (how it works,
 * manifesto, CTA, footer) that stay reachable by scroll.
 */

const { useState, useEffect, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "dark": true,
  "speed": 1,
  "paused": false,
  "alignPerson": "C",
  "offsetAx": 0,
  "offsetAy": -240,
  "offsetBx": -26,
  "offsetBy": -296,
  "offsetCx": -47,
  "offsetCy": -337,
  "footAy": 0,
  "footBy": 0,
  "footCy": 0,
  "scaleA": 0.99,
  "scaleB": 0.89,
  "scaleC": 0.83,
  "bgEchoOpacity": 0.4,
  "bgEchoBlur": 0
}/*EDITMODE-END*/;

const ALIGN_KEYS = ['A', 'B', 'C'];

const TWEAKS_LS_KEY = 'sf:tweaks';

/** Local dev auto-on; production hidden unless unlocked via ?tweaks=1 or localStorage. */
function isTweaksEnabled() {
  const host = location.hostname;
  if (host === 'localhost' || host === '127.0.0.1') return true;

  const param = new URLSearchParams(location.search).get('tweaks');
  if (param === '1') {
    try { localStorage.setItem(TWEAKS_LS_KEY, '1'); } catch {}
    return true;
  }

  try { return localStorage.getItem(TWEAKS_LS_KEY) === '1'; } catch { return false; }
}

/** Design canvas size — all alignment px values in TWEAK_DEFAULTS are relative to this. */
const STAGE_W = 1440;
const STAGE_H = 900;

/** Per-person slider bounds (offset Y negative = move up). */
const ALIGN_LIMITS = {
  A: { footY: [-40, 40], offsetX: [-80, 80], offsetY: [-360, 120] },
  B: { footY: [-40, 40], offsetX: [-80, 80], offsetY: [-360, 120] },
  C: { footY: [-120, 40], offsetX: [-160, 80], offsetY: [-360, 120] },
};

function clampAlign(person, axis, value) {
  const [min, max] = ALIGN_LIMITS[person]?.[axis] ?? [-80, 80];
  return Math.min(max, Math.max(min, value));
}

function buildPersonLayout(t) {
  const pick = (k) => ({
    footY: t[`foot${k}y`] ?? 0,
    offsetX: t[`offset${k}x`] ?? 0,
    offsetY: t[`offset${k}y`] ?? 0,
    scale: t[`scale${k}`] ?? 1,
  });
  return { A: pick('A'), B: pick('B'), C: pick('C') };
}

// City metadata for captions / overlays.
const CITIES = [
  { key: 'A', city: 'Shanghai',   stamp: '31.2°N · 121.5°E · 07:42 PM' },
  { key: 'B', city: 'Copenhagen', stamp: '55.7°N · 12.6°E · 01:42 PM' },
  { key: 'C', city: 'New York',   stamp: '40.7°N · 74.0°W · 07:42 AM' },
];

// Asset filenames for each city. Group composite is no longer used —
// pre-capture and post-capture both render bg + cutout (the cutout is the
// person, the bg is just the environment behind them), so the figure size
// is identical across the capture moment.
const ASSETS = {
  A: { bg: 'assets/09_A_bg.webp', cutout: 'assets/05_A_cutout.webp' },
  B: { bg: 'assets/10_B_bg.webp', cutout: 'assets/06_B_cutout.webp' },
  C: { bg: 'assets/11_C_bg.webp', cutout: 'assets/07_C_cutout.webp' },
};

const WAITLIST_ENDPOINT = 'https://formspree.io/f/xbdeyage';
const WAITLIST_READY = !WAITLIST_ENDPOINT.includes('REPLACE_WITH_FORM_ID');

// ── Column (one city, bg + cutout) ───────────────────────────────────────
function Column({ city, stamp, ckey }) {
  const a = ASSETS[ckey];
  return (
    <div id={`col-${ckey}`} className="col" data-city={ckey}
         data-comment-anchor={`col-${ckey}`}>
      {/* Image stack — bg first, cutout on top. Both visible from the start
          so the figure doesn't pop in on capture. */}
      <img className="col-img img-bg" src={a.bg} alt="" decoding="async"
           fetchPriority={ckey === 'A' ? 'high' : 'auto'} />
      <img className="col-img img-cutout" src={a.cutout} alt="" decoding="async" />

      <div className="dim-overlay" />

      {/* EVF viewfinder. */}
      <div className="viewfinder" aria-hidden="true">
        <div className="corner tl" />
        <div className="corner tr" />
        <div className="corner bl" />
        <div className="corner br" />
        <div className="focus-ring" />
        <div className="mark h-top" />
        <div className="mark h-bot" />
        <div className="mark v-lt" />
        <div className="mark v-rt" />
        <div className="evf-readout">
          <span className="rec-dot" />
          <span>REC · F 2.8 · ISO 200</span>
        </div>
      </div>

      {/* Flash sheet. */}
      <div id={`flash-${ckey}`} className="flash" aria-hidden="true" />

      {/* Caption. */}
      <div className="col-caption">
        <span className="in">in</span>
        <span className="city">{city}</span>
        <span className="stamp">{stamp}</span>
      </div>
    </div>
  );
}

// ── Converge layer (3 cutouts that fly into the photo frame) ─────────────
function ConvergeLayer({ layout }) {
  return (
    <div className="converge" aria-hidden="true">
      <div className="conv-group">
        {['A', 'B', 'C'].map((k) => {
          const p = layout[k];
          return (
            <div key={k} className={`conv-person p-${k}`}
                 style={{
                   '--foot-y': `${p.footY}px`,
                   '--offset-x': `${p.offsetX}px`,
                   '--offset-y': `${p.offsetY}px`,
                   '--person-scale': p.scale,
                 }}>
              <img data-defer-src={ASSETS[k].cutout} alt="" decoding="async" />
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Birthday backdrop (below cutouts) ────────────────────────────────────
function BirthdayBackdrop() {
  return (
    <div className="birthday-bg" aria-hidden="true">
      <img className="frame-img frame-birthday" data-defer-src="assets/04_bg_birthday.webp"
           alt="" decoding="async" />
    </div>
  );
}

// ── Refined composite (above cutouts, below frame chrome) ────────────────
function FrameRefinedLayer() {
  return (
    <div className="frame-refined-layer" aria-hidden="true">
      <img className="frame-img frame-refined" data-defer-src="assets/12_final_refined.webp"
           alt="" decoding="async" />
      <div className="frame-shimmer" />
    </div>
  );
}

// ── Photo frame (white border + strip only — hollow center) ──────────────
function PhotoFrame() {
  return (
    <div className="frame-wrap" id="frame-wrap">
      <div className="frame-photo" aria-hidden="true" />
      <div className="frame-strip">
        <div className="frame-stamp">
          <span className="stamp-text">
            SameFrame <span className="stamp-sep">·</span>{' '}
            <span className="stamp-sub">Remote portrait</span>
          </span>
        </div>
        <div className="frame-tagline">
          <strong>Together</strong>, even when apart.
        </div>
      </div>
    </div>
  );
}

// ── Hero ─────────────────────────────────────────────────────────────────
function Hero({ speed, personLayout, bgEchoOpacity, bgEchoBlur, onReady, completed, onReplay }) {
  const heroRef = useRef(null);
  const animRef = useRef(null);
  const [, force] = useState(0);

  useEffect(() => {
    if (typeof window.SameFramerAnimation === 'undefined') {
      console.error('[same-framer] Animation engine not loaded');
      return;
    }
    const dom = {
      cols: {
        A: document.getElementById('col-A'),
        B: document.getElementById('col-B'),
        C: document.getElementById('col-C'),
      },
      flashes: {
        A: document.getElementById('flash-A'),
        B: document.getElementById('flash-B'),
        C: document.getElementById('flash-C'),
      },
    };
    const eng = new window.SameFramerAnimation(heroRef.current, dom, {
      speed,
      onComplete: () => onReady?.(),
    });
    animRef.current = eng;
    onReady?.(eng);     // expose engine to parent for tweaks
    eng.start();
    return () => eng.dispose();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => { animRef.current?.setSpeed(speed); }, [speed]);

  return (
    <section id="top" ref={heroRef} className="hero" data-screen-label="01 Hero"
             style={{
               '--stage-w-px': `${STAGE_W}px`,
               '--stage-h-px': `${STAGE_H}px`,
               '--bg-echo-opacity': bgEchoOpacity ?? 0.2,
               '--bg-echo-blur': `${bgEchoBlur ?? 8}px`,
             }}>
      <div className="hero-backdrop" />
      <div className="hero-stage">

      <div className="cols">
        {CITIES.map((c) => (
          <Column key={c.key} ckey={c.key} city={c.city} stamp={c.stamp} />
        ))}
      </div>

      <BirthdayBackdrop />
      <ConvergeLayer layout={personLayout} />
      <FrameRefinedLayer />
      <PhotoFrame />

      {/* CTA layer — appears at end of timeline. */}
      <div className="hero-cta-layer" aria-live="polite">
        <WaitlistForm compact />
        <div className="hero-cta-hint">
          Early access <span>·</span> 3 launch credits <span>·</span> No app download
        </div>
      </div>

      <button
        className={`replay${completed ? ' is-ready' : ''}`}
        onClick={onReplay}
        aria-label="Replay animation">
        <i className="ti ti-rotate-clockwise-2" />
        <span>Replay</span>
      </button>
      </div>
      <button
        className={`mobile-replay${completed ? ' is-ready' : ''}`}
        onClick={onReplay}
        aria-label="Replay animation">
        <i className="ti ti-rotate-clockwise-2" />
        <span>Replay</span>
      </button>
    </section>
  );
}

// ── Top nav ──────────────────────────────────────────────────────────────
function Nav({ dark, onToggleTheme }) {
  return (
    <nav className="nav">
      <a className="nav-logo" href="#top" aria-label="SameFrame home">
        SameFrame
      </a>
      <div className="nav-actions">
        <a className="nav-link" href="#how">How it works</a>
        <button
          className="theme-toggle"
          onClick={onToggleTheme}
          aria-label={dark ? "Switch to light mode" : "Switch to dark mode"}>
          <i className={dark ? "ti ti-sun" : "ti ti-moon"} />
        </button>
      </div>
    </nav>
  );
}

// ── Below-the-fold sections ──────────────────────────────────────────────
function MobileHeroWaitlist() {
  return (
    <section className="mobile-hero-waitlist" aria-label="SameFrame early access">
      <div className="section">
        <WaitlistForm />
        <div className="cta-hint">
          Early access <span>·</span> 3 launch credits <span>·</span> No app download
        </div>
      </div>
    </section>
  );
}

function WaitlistForm({ compact = false }) {
  const [email, setEmail] = useState('');
  const [status, setStatus] = useState('idle');
  const [message, setMessage] = useState('');

  const isBusy = status === 'loading';
  const isSuccess = status === 'success';
  const tone = status === 'error' ? 'error' : status === 'success' ? 'success' : 'neutral';

  const buttonText = compact
    ? (isBusy ? 'Reserving...' : isSuccess ? "You're on the list" : 'Reserve')
    : (isBusy ? 'Reserving...' : isSuccess ? "You're on the list" : 'Reserve your first SameFrame');

  const handleSubmit = async (event) => {
    event.preventDefault();
    const normalizedEmail = email.trim();

    if (!normalizedEmail) {
      setStatus('error');
      setMessage('Add your email to reserve a spot.');
      return;
    }

    if (!WAITLIST_READY) {
      setStatus('error');
      setMessage('Waitlist form is almost ready. Email hello@sameframe.photo for now.');
      return;
    }

    setStatus('loading');
    setMessage('Saving your early access spot...');

    try {
      const response = await fetch(WAITLIST_ENDPOINT, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email: normalizedEmail,
          source: 'sameframe_landing_page',
          offer: 'early_access_3_launch_credits',
        }),
      });

      if (!response.ok) throw new Error('Form submission failed');

      setStatus('success');
      setMessage('Reserved. Your early access invite is on the way.');
      setEmail('');
    } catch {
      setStatus('error');
      setMessage('Something did not send. Please try again in a moment.');
    }
  };

  return (
    <form className={`waitlist-form${compact ? ' waitlist-form-compact' : ''}`}
          onSubmit={handleSubmit}
          data-status={status}>
      <div className="waitlist-control">
        <label className="sr-only" htmlFor={compact ? 'hero-waitlist-email' : 'waitlist-email'}>
          Email for SameFrame early access
        </label>
        <div className="waitlist-input-wrap">
          <i className="ti ti-mail" aria-hidden="true" />
          <input
            id={compact ? 'hero-waitlist-email' : 'waitlist-email'}
            className="waitlist-input"
            type="email"
            inputMode="email"
            autoComplete="email"
            placeholder={isSuccess ? 'You are on the early list' : 'Email for early access'}
            value={email}
            onChange={(event) => {
              setEmail(event.target.value);
              if (status !== 'idle') {
                setStatus('idle');
                setMessage('');
              }
            }}
            disabled={isBusy || isSuccess}
            required
          />
        </div>
        <button className="waitlist-button" type="submit" disabled={isBusy || isSuccess}>
          <span>{buttonText}</span>
          <i className={isSuccess ? 'ti ti-check' : 'ti ti-arrow-right'} aria-hidden="true" />
        </button>
      </div>
      <div className="waitlist-status" data-tone={tone} role="status" aria-live="polite">
        {message}
      </div>
    </form>
  );
}

function HowItWorks() {
  const steps = [
    { num: "01", title: "Start a shared frame",
      body: "Set the occasion, choose the frame, and send one link to the people you want in the photo." },
    { num: "02", title: "Everyone takes their place",
      body: "Friends open the link, choose where they stand, and take their own photo. No account needed." },
    { num: "03", title: "The portrait comes together",
      body: "After the last person finishes, SameFrame uses AI composition to bring everyone into one final photo." },
  ];
  return (
    <section className="howitworks" id="how">
      <div className="section">
        <div className="section-eyebrow">How it works</div>
        <div className="how-grid">
          {steps.map((s) => (
            <div key={s.num} className="how-step">
              <div className="how-step-mark">
                <div className="num">{s.num}</div>
                <div className="title">{s.title}</div>
              </div>
              <div className="body">{s.body}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

function Moments() {
  const moments = [
    { label: 'Birthdays across cities', icon: 'ti ti-gift', tone: 'rose' },
    { label: 'Remote teams', icon: 'ti ti-users', tone: 'blue' },
    { label: 'Families abroad', icon: 'ti ti-home-heart', tone: 'green' },
    { label: 'Long-distance friends', icon: 'ti ti-heart', tone: 'amber' },
    { label: 'Group chats that never meet', icon: 'ti ti-messages', tone: 'blue' },
    { label: 'Graduations', icon: 'ti ti-school', tone: 'green' },
    { label: 'Farewells', icon: 'ti ti-map-pin-heart', tone: 'rose' },
    { label: 'Old classmates', icon: 'ti ti-notebook', tone: 'amber' },
  ];
  const row = [...moments, ...moments];

  return (
    <section className="moments" aria-label="SameFrame use cases">
      <div className="section">
        <div className="section-eyebrow">Made for</div>
        <h2 className="moments-headline">
          The small reasons <em>to gather.</em>
        </h2>
      </div>
      <div className="moment-marquee" aria-hidden="true">
        <div className="moment-row">
          <div className="moment-track">
            {row.map((item, index) => (
              <span className="moment-chip" data-tone={item.tone} key={`a-${item.label}-${index}`}>
                <i className={item.icon} aria-hidden="true" />
                {item.label}
              </span>
            ))}
          </div>
        </div>
        <div className="moment-row moment-row-reverse">
          <div className="moment-track">
            {[...row].reverse().map((item, index) => (
              <span className="moment-chip" data-tone={item.tone} key={`b-${item.label}-${index}`}>
                <i className={item.icon} aria-hidden="true" />
                {item.label}
              </span>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

function Manifesto() {
  return (
    <section className="manifesto">
      <div className="section">
        <div className="manifesto-quote">
          You do not need the same room to belong in
          <em> the same picture.</em>
        </div>
        <div className="manifesto-meta">A photograph, taken across cities</div>
      </div>
    </section>
  );
}

function CTA() {
  return (
    <section className="cta" id="waitlist">
      <div className="section">
        <h2 className="cta-headline">
          Save a first frame <em>for the people you miss.</em>
        </h2>
        <WaitlistForm />
        <p className="waitlist-privacy">
          Waitlist privacy: we only use your email for SameFrame early access and launch updates.
        </p>
        <div className="cta-hint">
          Early access <span>·</span> 3 launch credits <span>·</span> No app download
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer className="footer">
      <div className="footer-left">
        <span className="mark">SameFrame</span>
        <span>© 2026</span>
        <a className="studio-link" href="https://yi.studio/" target="_blank" rel="noreferrer">
          YI / STUDIO
        </a>
      </div>
      <div className="footer-right">
        <span className="footer-contact-label">Questions, or just want to say hi?</span>
        <a href="mailto:hello@sameframe.photo">hello@sameframe.photo</a>
      </div>
    </footer>
  );
}

// ── App ───────────────────────────────────────────────────────────────────
function App() {
  const tweaksEnabled = isTweaksEnabled();
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [engine, setEngine] = useState(null);
  const [completed, setCompleted] = useState(false);
  const [phase, setPhase] = useState('Scene 1 · Intro');

  // Theme on <html>.
  useEffect(() => {
    document.documentElement.dataset.theme = t.dark ? 'dark' : 'light';
  }, [t.dark]);

  const handleReady = (eng) => {
    if (!eng) { setCompleted(true); return; }
    setEngine(eng);
    eng.onPhase = setPhase;
    eng.onComplete = () => setCompleted(true);
  };
  const onReplay = () => {
    setCompleted(false);
    engine?.restart();
    if (t.paused) engine?.pause();
  };
  const jumpTo = (n) => {
    setCompleted(false);
    engine?.jumpToScene(n);
    if (t.paused) engine?.pause();
  };

  const scenes = window.SAMEFRAMER_SCENES || [];
  const personLayout = buildPersonLayout(t);
  const person = t.alignPerson || 'A';
  const oxKey = `offset${person}x`;
  const oyKey = `offset${person}y`;
  const footKey = `foot${person}y`;
  const scaleKey = `scale${person}`;
  const limits = ALIGN_LIMITS[person] ?? ALIGN_LIMITS.A;
  const [copiedDefaults, setCopiedDefaults] = useState(false);

  const copyTweakDefaults = async () => {
    const baked = { ...t, paused: false };
    const json = JSON.stringify(baked, null, 2);
    try {
      await navigator.clipboard.writeText(json);
      setCopiedDefaults(true);
      setTimeout(() => setCopiedDefaults(false), 2500);
    } catch {
      window.prompt('Copy into TWEAK_DEFAULTS in sameframer-app.jsx:', json);
    }
  };

  useEffect(() => {
    if (!engine) return;
    if (t.paused) engine.pause();
    else engine.resume();
  }, [t.paused, engine]);

  // Arrow keys nudge the selected person (Scene 8/9 preview recommended).
  useEffect(() => {
    if (!tweaksEnabled) return;
    const onKey = (e) => {
      if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) return;
      e.preventDefault();
      const step = e.shiftKey ? 5 : 1;
      const x = t[oxKey] ?? 0;
      const y = t[oyKey] ?? 0;
      if (e.key === 'ArrowLeft')  setTweak(oxKey, clampAlign(person, 'offsetX', x - step));
      if (e.key === 'ArrowRight') setTweak(oxKey, clampAlign(person, 'offsetX', x + step));
      if (e.key === 'ArrowUp')    setTweak(oyKey, clampAlign(person, 'offsetY', y - step));
      if (e.key === 'ArrowDown')  setTweak(oyKey, clampAlign(person, 'offsetY', y + step));
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [tweaksEnabled, person, t[oxKey], t[oyKey], setTweak, oxKey, oyKey]);

  useEffect(() => {
    if (tweaksEnabled) {
      window.postMessage({ type: '__activate_edit_mode' }, '*');
    }
  }, [tweaksEnabled]);

  return (
    <>
      <Nav dark={t.dark} onToggleTheme={() => setTweak('dark', !t.dark)} />
      <Hero speed={t.speed} personLayout={personLayout}
            bgEchoOpacity={t.bgEchoOpacity ?? 0.2}
            bgEchoBlur={t.bgEchoBlur ?? 8}
            onReady={handleReady}
            completed={completed} onReplay={onReplay} />
      <MobileHeroWaitlist />

      <HowItWorks />
      <Moments />
      <Manifesto />
      <CTA />
      <Footer />

      {tweaksEnabled && (
        <TweaksPanel title="Tweaks">
          <TweakSection label="Theme">
            <TweakToggle label="Dark mode" value={t.dark}
                         onChange={(v) => setTweak('dark', v)} />
          </TweakSection>

          <TweakSection label="Animation">
            <TweakSlider label="Speed" value={t.speed} min={0.25} max={2} step={0.05}
                         unit="×" onChange={(v) => setTweak('speed', v)} />
            <TweakToggle label="Pause animation" value={!!t.paused}
                         onChange={(v) => setTweak('paused', v)} />
            <TweakButton label="↻ Replay from start" secondary onClick={onReplay} />
          </TweakSection>

          <TweakSection label={`Jump to scene · ${phase}`}>
            <div className="jump-row">
              {scenes.map((s) => (
                <button key={s.id} className="jump-btn"
                        title={s.label}
                        onClick={() => jumpTo(s.id)}>
                  {s.id}
                </button>
              ))}
            </div>
          </TweakSection>

          <TweakSection label="Background · Scene 5+">
            <TweakSlider label="ABC bg echo opacity" value={t.bgEchoOpacity ?? 0.2}
                         min={0} max={1} step={0.01}
                         onChange={(v) => setTweak('bgEchoOpacity', v)} />
            <TweakSlider label="ABC bg echo blur" value={t.bgEchoBlur ?? 8}
                         min={0} max={40} step={1} unit="px"
                         onChange={(v) => setTweak('bgEchoBlur', v)} />
            <p style={{ margin: 0, fontSize: 10, opacity: 0.55, lineHeight: 1.4 }}>
              Faint city backgrounds after Scene 5 dissolve. Pause, jump to Scene 5–6 to preview.
            </p>
          </TweakSection>

          <TweakSection label="Person align · Scene 8/9">
            <TweakRadio label="Person" value={person}
                        options={ALIGN_KEYS}
                        onChange={(v) => setTweak('alignPerson', v)} />
            <TweakSlider label={`${person} foot Y`} value={t[footKey] ?? 0}
                         min={limits.footY[0]} max={limits.footY[1]} step={1} unit="px"
                         onChange={(v) => setTweak(footKey, v)} />
            <TweakSlider label={`${person} offset X`} value={t[oxKey] ?? 0}
                         min={limits.offsetX[0]} max={limits.offsetX[1]} step={1} unit="px"
                         onChange={(v) => setTweak(oxKey, v)} />
            <TweakSlider label={`${person} offset Y`} value={t[oyKey] ?? 0}
                         min={limits.offsetY[0]} max={limits.offsetY[1]} step={1} unit="px"
                         onChange={(v) => setTweak(oyKey, v)} />
            <TweakSlider label={`${person} 等比缩放`} value={t[scaleKey] ?? 1}
                         min={0.5} max={1.5} step={0.01} unit="×"
                         onChange={(v) => setTweak(scaleKey, v)} />
            <p style={{ margin: 0, fontSize: 10, opacity: 0.55, lineHeight: 1.4 }}>
              Frame align only affects Scene 7+. Scene 5/6 stay at column positions. Pause, jump to Scene 8/9, then adjust.
            </p>
          </TweakSection>

          <TweakSection label="Save alignment">
            <TweakButton
              label={copiedDefaults ? '✓ Copied to clipboard' : 'Copy values as defaults JSON'}
              secondary
              onClick={copyTweakDefaults}
            />
            <p style={{ margin: 0, fontSize: 10, opacity: 0.55, lineHeight: 1.4 }}>
              Paste into <code style={{ fontSize: 9 }}>TWEAK_DEFAULTS</code> in sameframer-app.jsx, then commit.
            </p>
          </TweakSection>
        </TweaksPanel>
      )}
    </>
  );
}

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