// Home screen + app shell with routing, Tweaks integration.

const { useState: useStateA, useEffect: useEffectA, useRef: useRefA, useMemo: useMemoA } = React;

// Live countdown hook — returns ms remaining, re-renders each second
function useCountdown() {
  const [ms, setMs] = useStateA(window.PLANETALIN.msUntilNextWindow());
  useEffectA(() => {
    const iv = setInterval(() => setMs(window.PLANETALIN.msUntilNextWindow()), 1000);
    return () => clearInterval(iv);
  }, []);
  return ms;
}

function formatCountdown(ms) {
  if (ms <= 0) return '00:00:00';
  const s = Math.floor(ms / 1000);
  const h = String(Math.floor(s / 3600)).padStart(2, '0');
  const m = String(Math.floor((s % 3600) / 60)).padStart(2, '0');
  const sec = String(s % 60).padStart(2, '0');
  return `${h}:${m}:${sec}`;
}

function poeticWindow(ms) {
  const h = ms / 3600000;
  if (h > 20) return 'The sky has just closed.';
  if (h > 16) return 'Stars are still settling.';
  if (h > 10) return 'The cosmos is breathing.';
  if (h > 5)  return 'The window is narrowing to open.';
  if (h > 1)  return 'Soon. The light is gathering.';
  if (h > 0.1) return 'Moments now. Listen for the bell.';
  return 'The window opens.';
}

// ============ Home ============
function HomeHero({ starfieldDensity, animIntensity }) {
  const canvasRef = useRefA(null);
  const cooldownMs = useCountdown();
  const canCreate = cooldownMs === 0;
  const seal = useMemoA(() => window.PLANETALIN.getSeal(), []);
  const myWorlds = window.PLANETALIN.allPlanets().length;

  useEffectA(() => {
    if (!canvasRef.current) return;
    const seeds = window.PLANETALIN_HERO_SEEDS;
    const spec = seeds[Math.floor(Math.random()*seeds.length)];
    const scene = window.PLANETALIN.createPlanetScene(canvasRef.current, {
      cameraDistance: 3.4,
      starfieldDensity,
      animIntensity,
    });
    scene.setSpec(spec);
    return () => scene.dispose();
  }, []);

  return (
    <div className="home">
      <div className="home-hero">
        <canvas ref={canvasRef} className="hero-canvas" />
        <div className="home-overlay">
          <div className="home-eyebrow">PL·01 · constellation lab</div>
          <h1 className="home-title" style={{fontFamily:'"Cormorant Garamond", serif'}}>
            PLANETALIN
          </h1>
          <p className="home-subtitle">
            Compose an atmosphere from its elements.<br/>
            Seal a world that time will carry forward.
          </p>
          <div className="home-actions">
            <button
              className="solid-btn big"
              disabled={!canCreate}
              onClick={() => canCreate && window.PLANETALIN.go('create')}
              title={canCreate ? '' : poeticWindow(cooldownMs)}
            >
              {canCreate ? 'Begin a world' : 'The sky is closed'}
            </button>
            <button className="ghost-btn big" onClick={() => window.PLANETALIN.go('constellation')}>
              {myWorlds > 0 ? `View your constellation (${myWorlds})` : 'View the cosmos'}
            </button>
          </div>

          {!canCreate && (
            <div className="gate">
              <div className="gate-poem" style={{fontFamily:'"Cormorant Garamond", serif'}}>{poeticWindow(cooldownMs)}</div>
              <div className="gate-count">
                <span className="gate-lbl">Next window opens in</span>
                <span className="gate-time">{formatCountdown(cooldownMs)}</span>
              </div>
              <div className="gate-note">You may shape one world per day. The rest is rest.</div>
            </div>
          )}

          <div className="home-seal" style={{'--sealhue': seal.hue}} title={`Your creator seal · born ${new Date(seal.bornAt).toLocaleDateString()}`}>
            <span className="seal-glyph">{seal.glyph}</span>
            <span className="seal-meta">
              <span className="seal-meta-lbl">your seal</span>
              <span className="seal-meta-name">{seal.name}</span>
            </span>
            <span className="seal-meta">
              <span className="seal-meta-lbl">worlds shaped</span>
              <span className="seal-meta-name">{myWorlds}</span>
            </span>
          </div>

          <CatalogueActions />
        </div>
      </div>
    </div>
  );
}

// ============ Catalogue import/export ============
function CatalogueActions() {
  const fileRef = useRefA(null);
  const [flash, setFlash] = useStateA('');
  function toast(msg) { setFlash(msg); setTimeout(() => setFlash(''), 2000); }

  function exportAll() {
    const data = window.PLANETALIN.exportCatalogueJson();
    if (!data.planets.length) { toast('Nothing to export yet'); return; }
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `planetalin-catalogue-${data.seal?.name||'anon'}-${Date.now()}.json`;
    a.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
    toast(`Exported ${data.planets.length} world${data.planets.length===1?'':'s'}`);
  }

  function triggerImport() { fileRef.current?.click(); }

  async function onFile(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    try {
      const text = await file.text();
      const data = JSON.parse(text);
      if (data.format !== 'planetalin.catalogue.v1') throw new Error('Not a PLANETALIN catalogue');
      let n = 0;
      for (const p of (data.planets||[])) {
        if (window.PLANETALIN.importCataloguePlanet(p)) n++;
      }
      toast(`Imported ${n} world${n===1?'':'s'}`);
      setTimeout(() => window.location.reload(), 900);
    } catch(err) {
      toast('Import failed: ' + err.message);
    } finally {
      e.target.value = '';
    }
  }

  function seedSolarSystem() {
    const n = window.PLANETALIN.seedSolarSystem();
    if (n === 0) { toast('Solar system already catalogued'); return; }
    toast(`Catalogued ${n} real planet${n===1?'':'s'} from our solar system`);
    setTimeout(() => window.location.reload(), 900);
  }

  return (
    <div className="catalogue-actions">
      <button className="link-btn" onClick={exportAll}>↓ Export catalogue</button>
      <span className="dot-sep">·</span>
      <button className="link-btn" onClick={triggerImport}>↑ Import</button>
      <span className="dot-sep">·</span>
      <button className="link-btn" onClick={seedSolarSystem}>☉ Seed solar system</button>
      <input type="file" accept="application/json,.json" ref={fileRef} onChange={onFile} style={{display:'none'}} />
      {flash && <span className="action-flash">{flash}</span>}
    </div>
  );
}

// ============ Passkey Gate ============
// Renders before the wizard on every create attempt.
// Checks for a valid JWT first — if present, calls onAuthorized immediately.
// If not, prompts the user to register (first time) or sign in (returning).
//
// The passkey identifies every world created in this session and lets the
// user's constellation persist across devices and time.
function PasskeyGate({ onAuthorized }) {
  const seal = useMemoA(() => window.PLANETALIN.getSeal(), []);
  const isReturning = window.PLANETALIN.hasRegisteredPasskey?.() || false;
  const supported = window.PLANETALIN.webAuthnSupported();

  // 'check' | 'register' | 'login' | 'busy' | 'error'
  const [phase, setPhase] = useStateA('check');
  const [error, setError] = useStateA('');

  useEffectA(() => {
    const token = window.PLANETALIN.getToken?.();
    if (window.PLANETALIN.isTokenValid?.(token)) {
      onAuthorized(token);
      return;
    }
    setPhase(isReturning ? 'login' : 'register');
  }, []);

  async function doRegister() {
    setPhase('busy'); setError('');
    try {
      const result = await window.PLANETALIN.performRegistration(seal);
      onAuthorized(result.token);
    } catch(e) {
      if (e.name === 'NotAllowedError') {
        setError('Passkey was dismissed. Try again when you\'re ready.');
      } else {
        setError(e.message || 'Passkey creation failed');
      }
      setPhase('register');
    }
  }

  async function doLogin() {
    setPhase('busy'); setError('');
    try {
      const result = await window.PLANETALIN.performLogin(null); // discoverable
      onAuthorized(result.token);
    } catch(e) {
      if (e.name === 'NotAllowedError') {
        setError('Sign-in was dismissed.');
      } else {
        setError(e.message || 'Sign in failed');
      }
      setPhase('login');
    }
  }

  if (phase === 'check') return null;

  return (
    <div className="passkey-gate">
      <div className="pg-card">
        <div className="pg-glyph" style={{'--sealhue': seal.hue}}>{seal.glyph}</div>

        <h2 className="pg-title" style={{fontFamily:'"Cormorant Garamond", serif'}}>
          {phase === 'login' ? 'Welcome back, creator' : 'Identify your creation'}
        </h2>

        <p className="pg-body">
          {phase === 'register' || phase === 'busy' && !isReturning ? (
            <>
              A passkey saves your progress and signs every world you create —
              so your constellation stays yours, across devices and across time.
              <br /><br />
              Your private key never leaves your device.
            </>
          ) : (
            <>
              Use your passkey to continue. Your worlds and seal will be ready.
            </>
          )}
        </p>

        {!supported && (
          <div className="pg-error">
            This browser does not support passkeys. Try Safari, Chrome, or Firefox on a modern device.
          </div>
        )}

        {error && <div className="pg-error">{error}</div>}

        {phase === 'busy' && (
          <div className="pg-busy">
            <span className="pg-spinner" />
            authorizing…
          </div>
        )}

        {phase === 'register' && supported && (
          <div className="pg-actions">
            <button className="solid-btn pg-cta" onClick={doRegister}>
              Create a passkey &rarr; begin
            </button>
            {isReturning && (
              <button className="link-btn pg-switch" onClick={() => { setError(''); setPhase('login'); }}>
                Already have one? Sign in
              </button>
            )}
          </div>
        )}

        {phase === 'login' && supported && (
          <div className="pg-actions">
            <button className="solid-btn pg-cta" onClick={doLogin}>
              Continue with passkey
            </button>
            <button className="link-btn pg-switch" onClick={() => { setError(''); setPhase('register'); }}>
              Register a new passkey instead
            </button>
          </div>
        )}

        <p className="pg-fine">
          Sealed with <span style={{fontFamily:'JetBrains Mono, monospace', fontSize:'0.85em', opacity:0.7}}>{seal.name}</span>
        </p>
      </div>
    </div>
  );
}

// ============ Create wizard host ============
function CreateFlow({ forkFromId, starfieldDensity, animIntensity }) {
  const { defaultSpec, WIZ_STEPS, StepAtmosphere, StepSurface, StepMoons, StepRings, StepStar, StepSeal } = window.PLANETALINWizard;

  // Token state — null means gate not yet passed
  const [token, setToken] = useStateA(() => {
    const t = window.PLANETALIN.getToken?.();
    return window.PLANETALIN.isTokenValid?.(t) ? t : null;
  });

  const initial = useMemoA(() => {
    if (forkFromId) {
      const src = window.PLANETALIN.findById(forkFromId);
      if (src) {
        return { ...JSON.parse(JSON.stringify(src)), name: '', discoverer: '', id: undefined, createdAt: undefined };
      }
    }
    return defaultSpec();
  }, [forkFromId]);

  const [spec, setSpec] = useStateA(initial);
  const [stepIdx, setStepIdx] = useStateA(0);
  const [sealState, setSealState] = useStateA('idle'); // idle | sealing | done | blocked | error

  const canvasRef = useRefA(null);
  const sceneRef = useRefA(null);

  useEffectA(() => {
    if (!canvasRef.current || !token) return; // don't init canvas until past gate
    const scene = window.PLANETALIN.createPlanetScene(canvasRef.current, {
      cameraDistance: 3.3,
      starfieldDensity,
      animIntensity,
    });
    sceneRef.current = scene;
    scene.setSpec(spec);
    return () => scene.dispose();
  }, [token]); // re-run when token is set (gate cleared)

  useEffectA(() => {
    if (sceneRef.current) sceneRef.current.setSpec(spec);
  }, [spec]);

  useEffectA(() => {
    if (sceneRef.current) sceneRef.current.update({ starfieldDensity, animIntensity });
  }, [starfieldDensity, animIntensity]);

  // ── Gate: show PasskeyGate until token is obtained ──────────────────────
  if (!token) {
    return <PasskeyGate onAuthorized={(tok) => setToken(tok)} />;
  }

  // ── Seal: call server to persist planet ──────────────────────────────────
  async function onSeal() {
    if (sealState === 'sealing' || sealState === 'done') return;
    setSealState('sealing');
    try {
      const seal = window.PLANETALIN.getSeal();
      const specWithMeta = {
        ...spec,
        createdAt: new Date().toISOString(),
        seal,
      };
      const result = await window.PLANETALIN.createPlanet(specWithMeta);
      // result.spec has the server-stamped canonical PL-X##-YYY id
      const savedSpec = typeof result.spec === 'string' ? JSON.parse(result.spec) : result.spec;
      window.PLANETALIN.saveOne(savedSpec); // mirror locally for offline viewer access
      window.PLANETALIN.markCreated();      // also update local cooldown for UI
      setSealState('done');
      setTimeout(() => window.PLANETALIN.go(`planet/${result.id}`), 1200);
    } catch(e) {
      if (e.code === 'COOLDOWN_ACTIVE') {
        setSealState('blocked');
        setTimeout(() => setSealState('idle'), 2600);
      } else if (e.status === 401) {
        // JWT expired mid-session — send back through the gate
        window.PLANETALIN.clearToken?.();
        setToken(null);
        setSealState('idle');
      } else {
        setSealState('error');
        setTimeout(() => setSealState('idle'), 2600);
      }
    }
  }

  const step = WIZ_STEPS[stepIdx];
  const palette = window.PLANETALIN.derivePalette(spec.atmosphere || []);

  return (
    <div className="create" style={{'--glow': palette.glow, '--atmos': palette.atmos}}>
      {(sealState === 'sealing' || sealState === 'done') && (
        <div className={`seal-overlay ${sealState}`}>
          <div className="seal-ring-anim">
            <svg viewBox="0 0 200 200">
              <circle cx="100" cy="100" r="90" fill="none" stroke="rgba(255,255,255,0.2)" strokeWidth="1" />
              <circle cx="100" cy="100" r="76" fill="none" stroke="rgba(255,255,255,0.3)" strokeWidth="1" strokeDasharray="4 6" className="seal-circle-a" />
              <circle cx="100" cy="100" r="60" fill="none" stroke="rgba(255,255,255,0.5)" strokeWidth="1" className="seal-circle-b" />
              <text fill="rgba(255,255,255,0.8)" fontSize="10" fontFamily="JetBrains Mono" textAnchor="middle">
                <textPath href="#sealcurve" startOffset="0">SEALED · IMMUTABLE · CATALOGUED · ETERNAL · </textPath>
              </text>
              <defs>
                <path id="sealcurve" d="M 100,100 m -45,0 a 45,45 0 1,1 90,0 a 45,45 0 1,1 -90,0" />
              </defs>
            </svg>
          </div>
          <div className="seal-text">
            {sealState==='sealing' && 'sealing the specification…'}
            {sealState==='done' && 'world catalogued.'}
          </div>
        </div>
      )}

      <header className="create-top">
        <button className="ghost-btn small" onClick={() => window.PLANETALIN.go('')}>← PLANETALIN</button>
        <div className="stepper">
          {WIZ_STEPS.map((s, i) => (
            <button key={s.id} className={`step-btn ${i===stepIdx?'is-cur':''} ${i<stepIdx?'is-done':''}`} onClick={() => setStepIdx(i)}>
              <span className="step-n">{String(i+1).padStart(2,'0')}</span>
              <span className="step-l">{s.label}</span>
            </button>
          ))}
        </div>
        {forkFromId && <div className="fork-tag">forked from {forkFromId}</div>}
      </header>

      <div className="create-body">
        <aside className="create-preview">
          <canvas ref={canvasRef} className="preview-canvas" />
          <div className="preview-meta">
            <div className="pm-row"><span>Atmosphere</span><span>{spec.atmosphere.length} elements</span></div>
            <div className="pm-row"><span>Surface</span><span>{spec.surface}</span></div>
            <div className="pm-row"><span>Moons</span><span>{spec.moons.length}</span></div>
            <div className="pm-row"><span>Rings</span><span>{spec.rings.enabled?`${spec.rings.count} bands`:'none'}</span></div>
            <div className="pm-row"><span>Star</span><span>{spec.star}-type</span></div>
          </div>
          <div className="palette-strip">
            {spec.atmosphere.map(a => {
              const el = window.PLANETALIN_ELEMENTS.find(e => e.sym === a.sym);
              return <div key={a.sym} className="palette-swatch" style={{background: el?.color, flex: a.pct}} />;
            })}
          </div>
        </aside>

        <main className="create-main">
          <div className="main-inner">
            {step.id === 'atmosphere' && <StepAtmosphere spec={spec} setSpec={setSpec} />}
            {step.id === 'surface' && <StepSurface spec={spec} setSpec={setSpec} />}
            {step.id === 'moons' && <StepMoons spec={spec} setSpec={setSpec} />}
            {step.id === 'rings' && <StepRings spec={spec} setSpec={setSpec} />}
            {step.id === 'star' && <StepStar spec={spec} setSpec={setSpec} />}
            {step.id === 'seal' && <StepSeal spec={spec} setSpec={setSpec} onSeal={onSeal} sealState={sealState} />}
          </div>
          <footer className="wiz-foot">
            <button className="ghost-btn" onClick={() => setStepIdx(Math.max(0, stepIdx-1))} disabled={stepIdx===0}>← back</button>
            <div className="wiz-progress">
              {WIZ_STEPS.map((_,i) => <span key={i} className={`prog-dot ${i<=stepIdx?'is-on':''}`} />)}
            </div>
            <button className="solid-btn" onClick={() => setStepIdx(Math.min(WIZ_STEPS.length-1, stepIdx+1))} disabled={stepIdx===WIZ_STEPS.length-1}>next →</button>
          </footer>
        </main>
      </div>
    </div>
  );
}

// ============ Tweaks panel ============
function TweaksPanel({ tweaks, setTweaks, onSave, visible }) {
  if (!visible) return null;
  return (
    <div className="tweaks-panel">
      <div className="tweaks-head">Tweaks</div>
      <div className="tweak-row">
        <label>Starfield density</label>
        <input type="range" min="0.2" max="2" step="0.1" value={tweaks.starfieldDensity}
          onChange={e => { const v=+e.target.value; setTweaks({...tweaks, starfieldDensity:v}); onSave({starfieldDensity:v}); }} />
        <span>{tweaks.starfieldDensity.toFixed(1)}×</span>
      </div>
      <div className="tweak-row">
        <label>Animation intensity</label>
        <input type="range" min="0" max="3" step="0.1" value={tweaks.animIntensity}
          onChange={e => { const v=+e.target.value; setTweaks({...tweaks, animIntensity:v}); onSave({animIntensity:v}); }} />
        <span>{tweaks.animIntensity.toFixed(1)}×</span>
      </div>
    </div>
  );
}

// ============ Dev panel (?dev=1) ============
function DevPanel() {
  const [open, setOpen] = useStateA(true);
  const [flash, setFlash] = useStateA('');
  function toast(msg) { setFlash(msg); setTimeout(() => setFlash(''), 1800); }

  return (
    <div className={`dev-panel ${open?'is-open':''}`}>
      <button className="dev-toggle" onClick={() => setOpen(!open)}>
        <span className="dev-dot" /> dev
      </button>
      {open && (
        <div className="dev-body">
          <div className="dev-head">
            <span>dev tools</span>
            <span className="dev-sub">?dev=1</span>
          </div>
          <div className="dev-section">
            <div className="dev-lbl">Cooldown</div>
            <div className="dev-row">
              <button className="dev-btn" onClick={() => { window.PLANETALIN.devResetCooldown(); toast('Cooldown cleared'); }}>Reset now</button>
              <button className="dev-btn" onClick={() => { window.PLANETALIN.devSetCooldownHours(12); toast('12h remaining'); }}>12h</button>
              <button className="dev-btn" onClick={() => { window.PLANETALIN.devSetCooldownHours(1); toast('1h remaining'); }}>1h</button>
              <button className="dev-btn" onClick={() => { window.PLANETALIN.devSetCooldownHours(0.05); toast('~3min remaining'); }}>3m</button>
            </div>
          </div>
          <div className="dev-section">
            <div className="dev-lbl">Storage</div>
            <div className="dev-row">
              <button className="dev-btn" onClick={() => { window.PLANETALIN.devClearWorlds(); toast('Worlds wiped'); location.reload(); }}>Clear worlds</button>
              <button className="dev-btn" onClick={() => { window.PLANETALIN.devClearSeal(); window.PLANETALIN.clearToken?.(); toast('Seal + token reset'); location.reload(); }}>New seal</button>
              <button className="dev-btn danger" onClick={() => { if (confirm('Wipe everything?')) { window.PLANETALIN.devNukeAll(); window.PLANETALIN.clearToken?.(); location.reload(); } }}>Nuke all</button>
            </div>
          </div>
          <div className="dev-section">
            <div className="dev-lbl">Auth</div>
            <div className="dev-row">
              <button className="dev-btn" onClick={() => {
                const t = window.PLANETALIN.getToken?.();
                toast(t ? (window.PLANETALIN.isTokenValid?.(t) ? 'Token valid' : 'Token expired') : 'No token');
              }}>Check token</button>
              <button className="dev-btn" onClick={() => { window.PLANETALIN.clearToken?.(); toast('Token cleared'); }}>Clear token</button>
            </div>
          </div>
          <div className="dev-section">
            <div className="dev-lbl">Seed</div>
            <div className="dev-row">
              <button className="dev-btn" onClick={() => {
                const seed = window.PLANETALIN_HERO_SEEDS[Math.floor(Math.random()*window.PLANETALIN_HERO_SEEDS.length)];
                const id = window.PLANETALIN.importCataloguePlanet({ ...seed, id: window.PLANETALIN.genId(), seal: window.PLANETALIN.getSeal(), createdAt: new Date().toISOString() });
                toast('Seeded ' + id);
              }}>+1 sample world</button>
              <button className="dev-btn" onClick={() => {
                for (let i=0;i<5;i++){
                  const seed = window.PLANETALIN_HERO_SEEDS[Math.floor(Math.random()*window.PLANETALIN_HERO_SEEDS.length)];
                  window.PLANETALIN.importCataloguePlanet({ ...seed, id: window.PLANETALIN.genId(), seal: window.PLANETALIN.getSeal(), createdAt: new Date().toISOString() });
                }
                toast('Seeded 5 worlds');
              }}>+5</button>
              <button className="dev-btn" onClick={() => {
                const n = window.PLANETALIN.seedSolarSystem();
                toast(n ? `Seeded ${n} solar system` : 'Already seeded');
              }}>☉ Solar system</button>
            </div>
          </div>
          {flash && <div className="dev-flash">{flash}</div>}
        </div>
      )}
    </div>
  );
}

// ============ Root ============
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "starfieldDensity": 1.0,
  "animIntensity": 1.0
}/*EDITMODE-END*/;

function App() {
  const [route, setRoute] = useStateA(window.PLANETALIN.parseHash());
  const [tweaks, setTweaks] = useStateA(TWEAK_DEFAULTS);
  const [tweaksVisible, setTweaksVisible] = useStateA(false);

  useEffectA(() => {
    const onHash = () => setRoute(window.PLANETALIN.parseHash());
    window.addEventListener('hashchange', onHash);

    const onMsg = (e) => {
      if (e.data?.type === '__activate_edit_mode') setTweaksVisible(true);
      if (e.data?.type === '__deactivate_edit_mode') setTweaksVisible(false);
    };
    window.addEventListener('message', onMsg);
    try { window.parent.postMessage({type:'__edit_mode_available'}, '*'); } catch(e){}

    return () => {
      window.removeEventListener('hashchange', onHash);
      window.removeEventListener('message', onMsg);
    };
  }, []);

  function saveTweaks(edits) {
    try { window.parent.postMessage({type:'__edit_mode_set_keys', edits}, '*'); } catch(e){}
  }

  const common = { starfieldDensity: tweaks.starfieldDensity, animIntensity: tweaks.animIntensity };

  return (
    <>
      {route.route === 'home' && <HomeHero {...common} />}
      {route.route === 'create' && <CreateFlow forkFromId={route.forkFrom} {...common} />}
      {route.route === 'planet' && (() => {
        const p = window.PLANETALIN.findById(route.id);
        if (!p) return <NotFound />;
        return <window.PLANETALINViewer.ViewerPanel planet={p} {...common} />;
      })()}
      {route.route === 'permalink' && (() => {
        try {
          const p = window.PLANETALIN.decodeSpec(route.encoded);
          return <window.PLANETALINViewer.ViewerPanel planet={p} permalink {...common} />;
        } catch(e) {
          return <NotFound />;
        }
      })()}
      {route.route === 'constellation' && <window.PLANETALINConstellation.Constellation {...common} />}

      <TweaksPanel tweaks={tweaks} setTweaks={setTweaks} onSave={saveTweaks} visible={tweaksVisible} />
      {window.PLANETALIN.isDevMode() && <DevPanel />}
    </>
  );
}

function NotFound() {
  return (
    <div className="notfound">
      <div className="nf-inner">
        <div className="nf-eyebrow">404 · uncharted</div>
        <h1 style={{fontFamily:'"Cormorant Garamond", serif'}}>No such world</h1>
        <p>This planet is not in the catalogue.</p>
        <button className="solid-btn" onClick={() => window.PLANETALIN.go('')}>Return home</button>
      </div>
    </div>
  );
}

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