/* MycoPolicy — focused / map-first experience
   ─────────────────────────────────────────────────────────────────────
   One job: pick a state, read about it. Everything else is secondary nav.
   Map is the entry point; clicking a state opens an inline reading panel.
   ─────────────────────────────────────────────────────────────────────
*/

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": ["forest-civic"],
  "paletteOverrides": {},
  "mapTilt": 14,
  "background": "paper",
  "fontPair": "lora-inter",
  "showLegalRibbon": false,
  "reducedMotion": false
}/*EDITMODE-END*/;

// Palettes (same set as v1)
const PALETTE_GROUPS = ['Civic', 'Earth', 'Editorial', 'Other'];
const PALETTES = {
  'forest-civic':    { group: 'Civic',     name: 'Forest × Ink',       primary: '152 56% 18%', secondary: '210 18% 9%',  glow: '42 88% 52%' },
  'navy-civic':      { group: 'Civic',     name: 'Civic Navy',         primary: '215 65% 22%', secondary: '215 35% 10%', glow: '40 92% 55%' },
  'slate-civic':     { group: 'Civic',     name: 'Federal Slate',      primary: '212 32% 28%', secondary: '215 30% 12%', glow: '45 90% 55%' },
  'sage-earth':      { group: 'Earth',     name: 'Sage × Bark',        primary: '102 18% 32%', secondary: '30 18% 12%',  glow: '36 78% 52%' },
  'olive-earth':     { group: 'Earth',     name: 'Olive × Clay',       primary: '74 28% 28%',  secondary: '24 22% 14%',  glow: '28 82% 55%' },
  'moss-earth':      { group: 'Earth',     name: 'Moss × Charcoal',    primary: '142 28% 22%', secondary: '210 12% 12%', glow: '38 85% 55%' },
  'burgundy-ed':     { group: 'Editorial', name: 'Heritage Burgundy',  primary: '352 48% 28%', secondary: '210 18% 10%', glow: '38 80% 55%' },
  'rust-ed':         { group: 'Editorial', name: 'Bureau Rust',        primary: '14 55% 32%',  secondary: '210 18% 10%', glow: '42 90% 55%' },
  'ink-only':        { group: 'Editorial', name: 'Ink Mono',           primary: '215 25% 18%', secondary: '215 25% 10%', glow: '48 90% 58%' },
  'teal-corp':       { group: 'Other',     name: 'Teal Council',       primary: '186 52% 22%', secondary: '210 22% 10%', glow: '45 92% 55%' },
  'plum-corp':       { group: 'Other',     name: 'Plum Briefing',      primary: '278 28% 28%', secondary: '215 22% 10%', glow: '42 88% 55%' },
};

window.MP_FONT_PAIRS = {
  'lora-inter':          { serif: "'Lora', Georgia, serif",          sans: "'Inter', system-ui, sans-serif" },
  'newsreader-inter':    { serif: "'Newsreader', Georgia, serif",    sans: "'Inter', system-ui, sans-serif" },
  'sourceserif-ibmplex': { serif: "'Source Serif 4', Georgia, serif", sans: "'IBM Plex Sans', system-ui, sans-serif" },
  'fraunces-grotesk':    { serif: "'Fraunces', Georgia, serif",      sans: "'Space Grotesk', system-ui, sans-serif" },
  'plex-only':           { serif: "'IBM Plex Serif', Georgia, serif", sans: "'IBM Plex Sans', system-ui, sans-serif" },
};

// Color utilities (reused from v1)
function hslStrToHex(hslStr) {
  if (!hslStr) return '#000000';
  const [h, s, l] = hslStr.split(/\s+/).map((v) => parseFloat(v));
  const sN = s / 100, lN = l / 100;
  const k = (n) => (n + h / 30) % 12;
  const a = sN * Math.min(lN, 1 - lN);
  const f = (n) => {
    const v = lN - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
    return Math.round(v * 255).toString(16).padStart(2, '0');
  };
  return `#${f(0)}${f(8)}${f(4)}`;
}
function hexToHslStr(hex) {
  let h = hex.replace('#', '');
  if (h.length === 3) h = h.split('').map(c => c + c).join('');
  const r = parseInt(h.slice(0, 2), 16) / 255;
  const g = parseInt(h.slice(2, 4), 16) / 255;
  const b = parseInt(h.slice(4, 6), 16) / 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  let hue = 0, sat = 0, lit = (max + min) / 2;
  if (max !== min) {
    const d = max - min;
    sat = lit > 0.5 ? d / (2 - max - min) : d / (max + min);
    if (max === r) hue = ((g - b) / d + (g < b ? 6 : 0));
    else if (max === g) hue = (b - r) / d + 2;
    else hue = (r - g) / d + 4;
    hue *= 60;
  }
  return `${Math.round(hue)} ${Math.round(sat * 100)}% ${Math.round(lit * 100)}%`;
}

// ────────────────────────────────────────────────────────────────────
// FOCUSED MAP — tile grid (Daily-Kos style)
// ────────────────────────────────────────────────────────────────────
const TILE_POSITIONS = {
  ME: [10, 0],
  AK: [0, 1],  VT: [9, 1], NH: [10, 1],
  WA: [1, 2], ID: [2, 2], MT: [3, 2], ND: [4, 2], MN: [5, 2], WI: [6, 2], MI: [8, 2], NY: [9, 2], MA: [10, 2],
  OR: [1, 3], NV: [2, 3], WY: [3, 3], SD: [4, 3], IA: [5, 3], IL: [6, 3], IN: [7, 3], OH: [8, 3], PA: [9, 3], NJ: [10, 3], CT: [11, 3], RI: [12, 3],
  CA: [1, 4], UT: [2, 4], CO: [3, 4], NE: [4, 4], MO: [5, 4], KY: [6, 4], WV: [7, 4], VA: [8, 4], MD: [9, 4], DE: [10, 4],
  AZ: [2, 5], NM: [3, 5], KS: [4, 5], AR: [5, 5], TN: [6, 5], NC: [7, 5], SC: [8, 5], DC: [9, 5],
  HI: [0, 6],            OK: [4, 6], LA: [5, 6], MS: [6, 6], AL: [7, 6], GA: [8, 6],
                         TX: [4, 7],                                     FL: [9, 7]
};

function FocusedMap({ states, focused, onPick, tilt }) {
  const [hover, setHover] = React.useState(null);
  const byPostal = React.useMemo(() => {
    const m = {};
    states.forEach(s => { m[s.postal] = s; });
    return m;
  }, [states]);

  const statusFill = {
    active_licensing:           'var(--st-active)',
    enacted_not_open:           'var(--st-enacted)',
    limited_pilot_or_research:  'var(--st-pilot)',
    trigger_law:                'var(--st-trigger)',
    local_deprioritization_only:'var(--st-local)',
    watchlist:                  'var(--st-watch)',
    prohibited_no_pathway:      'var(--st-none)'
  };

  const COLS = 13, ROWS = 8;
  const TW = 64;
  const GAP = 6;
  const W = COLS * (TW + GAP);
  const H = ROWS * (TW + GAP);

  return (
    <div className={`focused-map-stage ${focused ? 'is-focused' : ''}`}>
      <div className="focused-map-tilt" style={{ transform: `perspective(2400px) rotateX(${tilt}deg)` }}>
        <div className="focused-map" style={{ width: W, height: H }}>
          {Object.entries(TILE_POSITIONS).map(([postal, [col, row]]) => {
            const state = byPostal[postal];
            if (!state) return null;
            const isFocused = focused === postal;
            const isHovered = hover === postal;
            return (
              <div
                key={postal}
                className={`fmap-tile is-match ${isFocused ? 'is-focused' : ''} ${isHovered ? 'is-hover' : ''}`}
                style={{
                  position: 'absolute',
                  left: col * (TW + GAP),
                  top: row * (TW + GAP),
                  width: TW, height: TW,
                  background: statusFill[state.status],
                  borderColor: statusFill[state.status],
                }}
                onClick={() => onPick(postal)}
                onMouseEnter={() => setHover(postal)}
                onMouseLeave={() => setHover(null)}
              >
                <div className="fmap-postal">{postal}</div>
                {state.commercial_license_available && <div className="fmap-dot" />}
              </div>
            );
          })}

          {/* Hover preview — only shown when not focused */}
          {hover && byPostal[hover] && !focused && (() => {
            const s = byPostal[hover];
            const [col, row] = TILE_POSITIONS[hover];
            const px = col * (TW + GAP) + TW / 2;
            const py = row * (TW + GAP);
            const right = col > COLS - 4;
            const bottom = row > ROWS - 3;
            return (
              <div className="fmap-tip" style={{
                left: right ? px - 220 : px - 20,
                top: bottom ? py - 110 : py + TW + 10,
              }}>
                <div className="fmap-tip-name">{s.state} <span>{s.postal}</span></div>
                <window.StatusBadge status={s.status} />
                <div className="fmap-tip-hint">Click to open ↓</div>
              </div>
            );
          })()}
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// READING PANEL — centered, narrow, single-column
// ────────────────────────────────────────────────────────────────────
function ReadingPanel({ state, onClose, onPick, onCorrection }) {
  if (!state) return null;
  const enrich = state.enrich;
  const def = window.MP_STATUS_DEFS[state.status];

  const can = (key) => {
    return enrich?.can_apply?.[key]
      || (state.commercial_license_available && state.applicable_roles?.includes(key) ? 'yes' : 'no');
  };
  const qaItems = [
    ['Cultivator',                'cultivator'],
    ['Product manufacturer',      'manufacturer'],
    ['Service / Healing center',  'service_center'],
    ['Clinician / Facilitator',   'clinician'],
    ['Testing laboratory',        'lab'],
    ['Research / Hospital',       'research'],
  ];

  return (
    <article className="reading-panel">
      <header className="rp-head">
        <div className="rp-crumb">
          <button onClick={onClose} className="rp-back">← Back to map</button>
          <span className="rp-postal-tag">{state.postal}</span>
        </div>
        <h1 className="rp-title">{state.state}</h1>
        <div className="rp-status-row">
          <window.StatusBadge status={state.status} size="lg" />
          {state.commercial_license_available && (
            <span className="rp-apply-flag">✓ Applications open</span>
          )}
          <span className="rp-verified">Verified {enrich?.last_verified || '2026-05-23'}</span>
        </div>
      </header>

      <p className="rp-lede">{state.summary}</p>

      <section className="rp-section">
        <h2 className="rp-h2">Can I apply now?</h2>
        <dl className="rp-qa">
          {qaItems.map(([label, key]) => {
            const ans = can(key);
            return (
              <div key={key} className={`rp-qa-row a-${ans}`}>
                <dt>{label}</dt>
                <dd>{ans === 'yes' ? 'Yes' : ans === 'watch' ? 'Watch' : 'No'}</dd>
              </div>
            );
          })}
        </dl>
      </section>

      {enrich?.regulator && (
        <section className="rp-section">
          <h2 className="rp-h2">Regulator</h2>
          <p className="rp-body">{enrich.regulator}</p>
        </section>
      )}

      <section className="rp-section">
        <h2 className="rp-h2">License paths</h2>
        {enrich?.paths_detail?.length > 0 ? (
          <ul className="rp-paths">
            {enrich.paths_detail.map((p, i) => (
              <li key={i}>
                <div className="rp-path-head">
                  <span className="rp-path-name">{p.name}</span>
                  <a href={p.source} target="_blank" rel="noopener noreferrer" className="rp-path-src">Source ↗</a>
                </div>
                <div className="rp-path-auth">{p.auth}</div>
                <div className="rp-path-meta">For: {p.who} · {p.regulator}</div>
              </li>
            ))}
          </ul>
        ) : (
          <div className="rp-empty">
            <strong>No open license path found.</strong>
            <p>{state.user_next_step}</p>
          </div>
        )}
      </section>

      {enrich?.watch?.length > 0 && (
        <section className="rp-section">
          <h2 className="rp-h2">What to watch</h2>
          <ul className="rp-watch">
            {enrich.watch.map((w, i) => <li key={i}>{w}</li>)}
          </ul>
        </section>
      )}

      {enrich?.sources?.length > 0 && (
        <section className="rp-section">
          <h2 className="rp-h2">Sources <span className="rp-count">({enrich.sources.length})</span></h2>
          <ul className="rp-sources">
            {enrich.sources.map((s, i) => (
              <li key={i}>
                <a href={s.url} target="_blank" rel="noopener noreferrer">{s.label}</a>
                <div className="rp-url">{s.url}</div>
              </li>
            ))}
          </ul>
        </section>
      )}

      <div className="rp-disclaimer">
        <strong>Not legal advice.</strong> MycoPolicy provides public policy information.
        Controlled-substance laws can change quickly and may differ across state, local, federal, and tribal jurisdictions. Confirm requirements with the relevant regulator and qualified counsel before acting.
      </div>

      <footer className="rp-foot">
        <button className="rp-foot-btn rp-foot-secondary" onClick={() => onCorrection(state.state)}>
          Submit a correction
        </button>
        <button className="rp-foot-btn rp-foot-primary" onClick={() => document.getElementById('alerts-min')?.scrollIntoView({ behavior: 'smooth' })}>
          Get alerts for {state.state} →
        </button>
      </footer>

      <div className="rp-next">
        <span className="rp-next-label">Compare nearby:</span>
        {(state.status === 'active_licensing'
            ? ['OR', 'CO']
            : ['OR', 'CO', 'NM']
          ).filter(p => p !== state.postal).map(p => (
            <button key={p} className="rp-next-chip" onClick={() => onPick(p)}>{p}</button>
        ))}
      </div>
    </article>
  );
}

// ────────────────────────────────────────────────────────────────────
// MINIMAL ALERT FORM (compact, anchored)
// ────────────────────────────────────────────────────────────────────
function AlertsMini({ stateName }) {
  const [email, setEmail] = React.useState('');
  const [sent, setSent] = React.useState(false);
  return (
    <section id="alerts-min" className="alerts-min">
      <div className="alerts-min-inner">
        <div>
          <div className="alerts-eyebrow">Policy alerts · free</div>
          <h3 className="alerts-h3">Be ready before your state opens.</h3>
          <p className="alerts-lead">One email when there's meaningful movement — task force reports, agency rules, application windows, or trigger laws. Unsubscribe anytime.</p>
        </div>
        {sent ? (
          <div className="alerts-success">Subscribed. <em>(Prototype.)</em></div>
        ) : (
          <form onSubmit={(e) => { e.preventDefault(); setSent(true); }} className="alerts-form-mini">
            <input type="email" required placeholder="you@firm.com" value={email} onChange={e => setEmail(e.target.value)} />
            <button type="submit">Get alerts</button>
          </form>
        )}
      </div>
    </section>
  );
}

// ────────────────────────────────────────────────────────────────────
// MAIN APP
// ────────────────────────────────────────────────────────────────────
const { useState, useEffect, useMemo, useRef } = React;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const states = window.MP_STATES;

  const [focusedPostal, setFocusedPostal] = useState(null);
  const [correctionsOpen, setCorrectionsOpen] = useState(false);
  const [correctionsPrefill, setCorrectionsPrefill] = useState('');
  const readingRef = useRef(null);

  const focusedState = useMemo(
    () => states.find(s => s.postal === focusedPostal),
    [states, focusedPostal]
  );

  // Resolve palette
  const paletteKey = Array.isArray(t.palette) ? t.palette[0] : t.palette;
  const basePalette = PALETTES[paletteKey] || PALETTES['forest-civic'];
  const effective = {
    primary:   t.paletteOverrides?.primary   || basePalette.primary,
    secondary: t.paletteOverrides?.secondary || basePalette.secondary,
    glow:      t.paletteOverrides?.glow      || basePalette.glow,
  };

  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty('--mp-primary', effective.primary);
    r.style.setProperty('--mp-secondary', effective.secondary);
    r.style.setProperty('--mp-glow', effective.glow);
    r.style.setProperty('--radius', '6px');
    r.dataset.bg = t.background;
    r.dataset.reducedMotion = t.reducedMotion ? '1' : '0';
    const pair = window.MP_FONT_PAIRS[t.fontPair] || window.MP_FONT_PAIRS['lora-inter'];
    r.style.setProperty('--font-serif', pair.serif);
    r.style.setProperty('--font-sans', pair.sans);
  }, [effective.primary, effective.secondary, effective.glow, t.background, t.reducedMotion, t.fontPair]);

  const onPick = (postal) => {
    setFocusedPostal(postal);
    // smooth-scroll the reading panel into view after layout
    setTimeout(() => {
      readingRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }, 80);
  };
  const onClose = () => {
    setFocusedPostal(null);
    setTimeout(() => {
      document.getElementById('map-anchor')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }, 50);
  };
  const openCorrections = (name) => {
    setCorrectionsPrefill(name || '');
    setCorrectionsOpen(true);
  };

  return (
    <div className="app-root v2">
      <div className={`bg-layer bg-${t.background}`} aria-hidden="true" />

      {t.showLegalRibbon && (
        <div className="legal-ribbon">
          <strong>NOT LEGAL ADVICE</strong>
          Public policy information only. Psilocybin and psilocin remain federal Schedule I substances.
        </div>
      )}

      {/* Quiet header */}
      <header className="topbar topbar-quiet">
        <div className="wrap topbar-inner">
          <a href="#" className="brand" onClick={(e) => { e.preventDefault(); onClose(); }}>
            <span className="brand-mark" />
            <span>MycoPolicy</span>
            <span className="brand-sub">by MycoStrategies</span>
          </a>
          <nav className="nav nav-quiet">
            <a href="#methodology-min">Methodology</a>
            <a href="#alerts-min">Alerts</a>
            <a href="#" onClick={(e) => { e.preventDefault(); openCorrections(''); }}>Submit a correction</a>
          </nav>
        </div>
      </header>

      {/* Hero — restrained, single column */}
      <section className="hero-quiet" id="map-anchor">
        <div className="wrap-narrow">
          <div className="eyebrow"><span className="dot" />Psychedelic licensing intelligence</div>
          <h1 className="hero-quiet-h1">
            Psychedelic licensing, <em>explained</em> state by state.
          </h1>
          <p className="hero-quiet-sub">
            Where regulated mushroom and natural medicine businesses can operate, who can apply, and what changed last.
          </p>
          <p className="hero-quiet-hint">
            <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="m4 8 3 3 5-6"/><circle cx="8" cy="8" r="6.5"/></svg>
            Click any state on the map to begin reading.
          </p>
        </div>
      </section>

      {/* The map itself */}
      <section className="map-section">
        <FocusedMap
          states={states}
          focused={focusedPostal}
          onPick={onPick}
          tilt={t.mapTilt}
        />

        {/* legend — compact, sits underneath */}
        <div className="map-legend-row">
          {[
            ['active_licensing',           'Apply now'],
            ['enacted_not_open',           'Enacted, not open'],
            ['limited_pilot_or_research',  'Pilot / research'],
            ['trigger_law',                'Trigger law'],
            ['local_deprioritization_only','Local only'],
            ['watchlist',                  'Watchlist'],
            ['prohibited_no_pathway',      'No pathway'],
          ].map(([k, label]) => (
            <span key={k} className="map-legend-chip">
              <span className="map-legend-swatch" style={{
                background: ({
                  active_licensing:           'var(--st-active)',
                  enacted_not_open:           'var(--st-enacted)',
                  limited_pilot_or_research:  'var(--st-pilot)',
                  trigger_law:                'var(--st-trigger)',
                  local_deprioritization_only:'var(--st-local)',
                  watchlist:                  'var(--st-watch)',
                  prohibited_no_pathway:      'var(--st-none)'
                })[k]
              }} />
              {label}
            </span>
          ))}
          <span className="map-legend-chip">
            <span className="map-legend-dot" />
            <em>= applications open</em>
          </span>
        </div>
      </section>

      {/* Reading panel anchor */}
      <div ref={readingRef} id="reading" />

      <section className="reading-stage">
        {focusedState ? (
          <ReadingPanel
            state={focusedState}
            onClose={onClose}
            onPick={onPick}
            onCorrection={openCorrections}
          />
        ) : (
          <div className="reading-empty">
            <div className="reading-empty-rule" />
            <div className="reading-empty-text">
              <span className="reading-empty-icon">↑</span>
              Pick a state above to read its current licensing status, paths, and sources.
            </div>
          </div>
        )}
      </section>

      {/* Minimal alerts band */}
      <AlertsMini stateName={focusedState?.state} />

      {/* Tiny methodology block */}
      <section id="methodology-min" className="method-mini">
        <div className="wrap-narrow">
          <div className="method-mini-grid">
            <div>
              <div className="alerts-eyebrow">How we verify</div>
              <h3 className="alerts-h3">Source-first, source-dated.</h3>
              <p className="alerts-lead">Every page is built from official agency portals, statutes, bill text, and reputable policy trackers. Each carries a last-verified date and a hierarchy of sources. If you see something wrong, tell us.</p>
            </div>
            <ol className="method-mini-list">
              <li>Official agency pages and application portals</li>
              <li>Enacted statutes and signed bills</li>
              <li>Active bill status pages</li>
              <li>Regulator bulletins and meeting materials</li>
              <li>University, hospital, or research program pages</li>
              <li>Reputable policy trackers and legal analyses</li>
            </ol>
          </div>
        </div>
      </section>

      {/* Tiny footer */}
      <footer className="foot foot-min">
        <div className="wrap">
          <div className="foot-min-inner">
            <div className="foot-min-brand">
              <div className="brand-mark" />
              <div>
                <div className="foot-min-name">MycoPolicy</div>
                <div className="foot-min-sub">A free product of MycoStrategies</div>
              </div>
            </div>
            <div className="foot-min-links">
              <a href="/MycoPolicy v1.html">All data view ↗</a>
              <a href="#methodology-min">Methodology</a>
              <a href="#alerts-min">Alerts</a>
              <a href="#" onClick={(e) => { e.preventDefault(); openCorrections(''); }}>Corrections</a>
            </div>
            <div className="foot-min-disclaimer">
              © 2026 MycoStrategies. Verified May 23, 2026. Not legal advice.
            </div>
          </div>
        </div>
      </footer>

      <CorrectionsModal
        open={correctionsOpen}
        onClose={() => setCorrectionsOpen(false)}
        states={states}
        prefillState={correctionsPrefill}
      />

      {/* ── TWEAKS ─────────────────────────────────────────────────── */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Brand palette" />
        <PaletteGridMini value={paletteKey} onChange={(v) => setTweak({ palette: [v], paletteOverrides: {} })} />

        <TweakSection label="Map" />
        <TweakSlider
          label="Tilt"
          value={t.mapTilt}
          min={0} max={30} step={1} unit="°"
          onChange={(v) => setTweak('mapTilt', v)}
        />
        <TweakSelect
          label="Background"
          value={t.background}
          options={[
            { value: 'paper',   label: 'Paper (default)' },
            { value: 'grid',    label: 'Civic grid' },
            { value: 'topo',    label: 'Topographic' },
            { value: 'dots',    label: 'Dot grid' },
            { value: 'plain',   label: 'Plain' },
          ]}
          onChange={(v) => setTweak('background', v)}
        />

        <TweakSection label="Typography" />
        <TweakSelect
          label="Font pair"
          value={t.fontPair}
          options={[
            { value: 'lora-inter',           label: 'Lora + Inter (default)' },
            { value: 'newsreader-inter',     label: 'Newsreader + Inter' },
            { value: 'sourceserif-ibmplex',  label: 'Source Serif + Plex Sans' },
            { value: 'fraunces-grotesk',     label: 'Fraunces + Space Grotesk' },
            { value: 'plex-only',            label: 'IBM Plex (matched)' },
          ]}
          onChange={(v) => setTweak('fontPair', v)}
        />

        <TweakSection label="Chrome" />
        <TweakToggle label="Show legal ribbon"  value={t.showLegalRibbon} onChange={(v) => setTweak('showLegalRibbon', v)} />
        <TweakToggle label="Reduce motion"      value={t.reducedMotion}   onChange={(v) => setTweak('reducedMotion', v)} />
      </TweaksPanel>
    </div>
  );
}

// ── compact palette grid (single column, fits the calmer panel) ──
function PaletteGridMini({ value, onChange }) {
  const grouped = PALETTE_GROUPS.map(g => ({
    group: g,
    items: Object.entries(PALETTES).filter(([_, p]) => p.group === g).map(([k, p]) => ({ key: k, ...p }))
  }));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {grouped.map(({ group, items }) => (
        <div key={group}>
          <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'rgba(41,38,27,.5)', marginBottom: 6 }}>{group}</div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
            {items.map(p => {
              const active = value === p.key;
              return (
                <button key={p.key} type="button" onClick={() => onChange(p.key)}
                  style={{
                    appearance: 'none',
                    border: active ? '1.5px solid rgba(41,38,27,.85)' : '0.5px solid rgba(0,0,0,.12)',
                    background: active ? 'rgba(255,255,255,.9)' : 'rgba(255,255,255,.5)',
                    borderRadius: 7, padding: 6,
                    display: 'flex', gap: 7, alignItems: 'center', cursor: 'default'
                  }}>
                  <div style={{ display: 'flex', height: 26, borderRadius: 4, overflow: 'hidden', flexShrink: 0, boxShadow: '0 0 0 .5px rgba(0,0,0,.08)' }}>
                    <div style={{ width: 7, background: `hsl(${p.secondary})` }} />
                    <div style={{ width: 14, background: `hsl(${p.primary})` }} />
                    <div style={{ width: 7, background: `hsl(${p.glow})` }} />
                  </div>
                  <span style={{ fontSize: 10.5, fontWeight: 500, lineHeight: 1.2, textAlign: 'left', color: 'rgba(41,38,27,.85)' }}>{p.name}</span>
                </button>
              );
            })}
          </div>
        </div>
      ))}
    </div>
  );
}

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