/* ========================================================================
   LUMINA — Channels rack (LEFT): tabbed
   Tabs: Presets · Layers · Behaviors · FX · Script
   ======================================================================== */
const { useState: useStateCh, useEffect: useEffectCh, useRef: useRefCh } = React;

function useProg() {
  const [, force] = useStateCh(0);
  useEffectCh(() => window.Prog.on(() => force(n => n + 1)), []);
  return window.Prog;
}
function useMedia() {
  const [, force] = useStateCh(0);
  useEffectCh(() => window.MediaStore.on(() => force(n => n + 1)), []);
  return window.MediaStore;
}

/* ----- LAYERS TAB ----- */
function LayersTab() {
  const M = useMedia();
  const inputRef = useRefCh(null);

  const onDrop = async (e) => {
    e.preventDefault();
    for (const f of e.dataTransfer.files) await M.add(f);
  };
  const onFiles = async (e) => {
    for (const f of e.target.files) await M.add(f);
    e.target.value = "";
  };

  return (
    <div onDragOver={e => e.preventDefault()} onDrop={onDrop}
      style={{ display: "flex", flexDirection: "column", flex: 1, minHeight: 0, gap: 8 }}>
      <div style={{ display: "flex", gap: 6 }}>
        <input type="file" multiple accept="image/*,video/*" hidden ref={inputRef} onChange={onFiles} />
        <button className="nbtn compact glow pressed" style={{ flex: 1 }} onClick={() => inputRef.current.click()}>
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M12 5v14M5 12h14"/></svg>
          Import
        </button>
      </div>
      <div className="cap" style={{ marginTop: 2 }}>
        Drop PNG · JPG · MP4 anywhere
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 4, overflowY: "auto", flex: 1, paddingRight: 4 }}>
        {M.layers.length === 0 && (
          <div style={{
            padding: "20px 12px", textAlign: "center",
            border: "1.5px dashed rgba(255,255,255,0.08)",
            borderRadius: 8, color: "var(--ink-faint)", fontSize: 11
          }}>
            Empty. Drop files here<br/>or click Import.
          </div>
        )}
        {M.layers.map(l => (
          <div key={l.id}
            className={`preset-card ${l.id === M.active ? "active" : ""}`}
            style={{ padding: 6 }}
            onClick={() => M.setActive(l.id)}>
            <div className="preset-icon" style={{ width: 32, height: 32 }}>
              {l.kind === "image" ?
                <img src={l.src} style={{ width: "100%", height: "100%", objectFit: "cover" }} alt="" /> :
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--led-cyan)" strokeWidth="2">
                  <rect x="3" y="6" width="14" height="12" rx="1.5"/><path d="M17 10l4-2v8l-4-2z"/>
                </svg>}
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="preset-name" style={{ fontSize: 10, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                {l.name}
              </div>
              <div className="preset-tag" style={{ fontSize: 8 }}>
                {l.kind.toUpperCase()} · {l.w}×{l.h}
              </div>
            </div>
            <button className="nbtn compact" style={{ padding: 4, minWidth: 22 }}
              onClick={(e) => { e.stopPropagation(); M.toggle(l.id); }}>
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
                style={{ opacity: l.visible ? 1 : 0.4 }}>
                <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/>
                <circle cx="12" cy="12" r="3"/>
              </svg>
            </button>
            <button className="nbtn compact" style={{ padding: 4, minWidth: 22 }}
              onClick={(e) => { e.stopPropagation(); M.remove(l.id); }}>
              <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4">
                <path d="M6 6l12 12M18 6L6 18"/>
              </svg>
            </button>
          </div>
        ))}
      </div>
      <div className="cap" style={{ paddingTop: 6, borderTop: "1px solid rgba(255,255,255,0.04)" }}>
        Select <b>Image · Frame FX</b>, <b>Image · Burst</b>, <b>Photo Strip</b>, <b>Video · TD</b> or <b>Video · Pixel Sort</b> in Presets
      </div>
    </div>
  );
}

/* ----- BEHAVIORS TAB (Cavalry-style modulator stack) ----- */
function BehaviorRow({ b }) {
  const P = useProg();
  const T = window.BehaviorTypes[b.type];
  const [open, setOpen] = useStateCh(true);
  return (
    <div style={{
      background: "linear-gradient(180deg, var(--panel-hi), var(--panel))",
      borderRadius: 8, padding: 8, marginBottom: 6,
      boxShadow: "var(--nshadow-out-sm)",
      borderLeft: `3px solid ${T.color}`
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <Led on={b.enabled} color="amber" />
        <div style={{ flex: 1, fontFamily: "var(--font-display)", fontSize: 10.5, fontWeight: 600,
          letterSpacing: "0.1em", textTransform: "uppercase", color: T.color }}>
          {T.label}
        </div>
        <button className="nbtn compact" style={{ padding: "2px 6px", fontSize: 9 }}
          onClick={() => P.updateBehavior(b.id, { enabled: !b.enabled })}>
          {b.enabled ? "ON" : "OFF"}
        </button>
        <button className="nbtn compact" style={{ padding: "2px 6px", fontSize: 9 }}
          onClick={() => setOpen(o => !o)}>{open ? "−" : "+"}</button>
        <button className="nbtn compact danger" style={{ padding: "2px 6px", fontSize: 9 }}
          onClick={() => P.removeBehavior(b.id)}>×</button>
      </div>
      {open && (
        <div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 6 }}>
          <div style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: 6, alignItems: "center" }}>
            <span className="cap">Target</span>
            <select value={b.target}
              onChange={e => P.updateBehavior(b.id, { target: e.target.value })}
              style={paramSelectStyle}>
              {window.PARAM_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
            </select>
          </div>
          {b.type === "audio" && (
            <>
              <SegRow label="Band" value={b.band} onChange={v => P.updateBehavior(b.id, { band: v })}
                options={["bass","mid","high","energy"]} />
              <SliderRow label="Strength" min={0} max={2} step={0.01}
                value={b.strength} onChange={v => P.updateBehavior(b.id, { strength: v })} />
              <SliderRow label="Attack" min={0.001} max={1} step={0.01} unit="s"
                value={b.attack} onChange={v => P.updateBehavior(b.id, { attack: v })} />
              <SliderRow label="Release" min={0.01} max={2} step={0.01} unit="s"
                value={b.release} onChange={v => P.updateBehavior(b.id, { release: v })} />
              <SliderRow label="Curve" min={0.3} max={4} step={0.05}
                value={b.curve} onChange={v => P.updateBehavior(b.id, { curve: v })} />
            </>
          )}
          {b.type === "oscillator" && (
            <>
              <SegRow label="Wave" value={b.wave} onChange={v => P.updateBehavior(b.id, { wave: v })}
                options={["sine","tri","square","saw"]} />
              <SliderRow label="Rate" min={0.05} max={8} step={0.01} unit="Hz"
                value={b.rate} onChange={v => P.updateBehavior(b.id, { rate: v })} />
              <SliderRow label="Amp" min={0} max={1} step={0.01}
                value={b.amp} onChange={v => P.updateBehavior(b.id, { amp: v })} />
              <SliderRow label="Phase" min={0} max={Math.PI * 2} step={0.01}
                value={b.phase} onChange={v => P.updateBehavior(b.id, { phase: v })} />
            </>
          )}
          {b.type === "wiggle" && (
            <>
              <SliderRow label="Rate" min={0.05} max={10} step={0.01}
                value={b.rate} onChange={v => P.updateBehavior(b.id, { rate: v })} />
              <SliderRow label="Amp" min={0} max={1} step={0.01}
                value={b.amp} onChange={v => P.updateBehavior(b.id, { amp: v })} />
            </>
          )}
          {b.type === "time" && (
            <SliderRow label="Rate" min={-200} max={200} step={1} unit="/s"
              value={b.rate} onChange={v => P.updateBehavior(b.id, { rate: v })} />
          )}
          {b.type === "expression" && (
            <>
              <div className="cap">Expression — vars: t, audio, p</div>
              <textarea value={b.expr}
                onChange={e => P.updateBehavior(b.id, { expr: e.target.value })}
                style={{ ...codeAreaStyle, minHeight: 50 }} />
            </>
          )}
          <SegRow label="Mode" value={b.mode} onChange={v => P.updateBehavior(b.id, { mode: v })}
            options={["add","set"]} />
        </div>
      )}
    </div>
  );
}
function BehaviorsTab() {
  const P = useProg();
  const [pickerOpen, setPickerOpen] = useStateCh(false);
  return (
    <div style={{ display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }}>
      <div style={{ position: "relative" }}>
        <button className="nbtn compact glow pressed" style={{ width: "100%" }}
          onClick={() => setPickerOpen(o => !o)}>
          + Add Behavior
        </button>
        {pickerOpen && (
          <div style={popoverStyle}>
            {Object.entries(window.BehaviorTypes).map(([k, t]) => (
              <button key={k} className="nbtn compact" style={{ justifyContent: "flex-start", padding: "6px 8px" }}
                onClick={() => { window.Prog.addBehavior(k); setPickerOpen(false); }}>
                <span style={{ width: 6, height: 6, borderRadius: "50%",
                  background: t.color, boxShadow: `0 0 6px ${t.color}`, marginRight: 6 }} />
                {t.label}
              </button>
            ))}
          </div>
        )}
      </div>
      <div style={{ overflowY: "auto", flex: 1, marginTop: 8, paddingRight: 4 }}>
        {P.behaviors.length === 0 && (
          <div style={emptyHintStyle}>
            No behaviors yet.<br/>
            Add an <b>Audio Reactor</b> to drive Hue with bass,<br/>
            or a <b>Wiggle</b> on Density for organic motion.
          </div>
        )}
        {P.behaviors.map(b => <BehaviorRow key={b.id} b={b} />)}
      </div>
    </div>
  );
}

/* ----- FX TAB (TD-style post-process stack) ----- */
function FXRow({ fx, idx }) {
  const P = useProg();
  const T = window.FXTypes[fx.type];
  return (
    <div style={{
      background: "linear-gradient(180deg, var(--panel-hi), var(--panel))",
      borderRadius: 8, padding: 8, marginBottom: 6,
      boxShadow: "var(--nshadow-out-sm)",
      borderLeft: `3px solid var(--led-cyan)`
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <Led on={fx.enabled} color="cyan" />
        <span style={{ fontSize: 9, color: "var(--ink-faint)", fontFamily: "var(--font-mono)" }}>
          {String(idx+1).padStart(2,"0")}
        </span>
        <div style={{ flex: 1, fontFamily: "var(--font-display)", fontSize: 10.5,
          fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--led-cyan)" }}>
          {T.label}
        </div>
        <button className="nbtn compact" style={{ padding: "2px 6px", fontSize: 9 }}
          onClick={() => P.updateFX(fx.id, { enabled: !fx.enabled })}>
          {fx.enabled ? "ON" : "OFF"}
        </button>
        <button className="nbtn compact" style={{ padding: "2px 5px", fontSize: 9 }}
          onClick={() => idx > 0 && P.reorderFX(idx, idx - 1)}>↑</button>
        <button className="nbtn compact" style={{ padding: "2px 5px", fontSize: 9 }}
          onClick={() => idx < P.fxChain.length - 1 && P.reorderFX(idx, idx + 1)}>↓</button>
        <button className="nbtn compact danger" style={{ padding: "2px 6px", fontSize: 9 }}
          onClick={() => P.removeFX(fx.id)}>×</button>
      </div>
      <div style={{ marginTop: 6, display: "flex", flexDirection: "column", gap: 4 }}>
        {Object.entries(fx).filter(([k]) => !["id","type","enabled"].includes(k)).map(([k, v]) => {
          // ranges by key
          const r = (
            k === "amount"   ? [0, 1, 0.01] :
            k === "size"     ? [0.005, 0.2, 0.001] :
            k === "decay"    ? [0.5, 0.99, 0.005] :
            k === "zoom"     ? [-0.1, 0.1, 0.001] :
            k === "rotate"   ? [-5, 5, 0.05] :
            k === "segments" ? [2, 16, 1] :
            k === "cutoff"   ? [0, 1, 0.01] :
            k === "levels"   ? [2, 16, 1] :
            k === "angle"    ? [0, 360, 1] :
            k === "density"  ? [0, 1, 0.01] :
            k === "threshold"? [0, 1, 0.01] :
            [0, 1, 0.01]
          );
          if (typeof v !== "number") return null;
          return (
            <SliderRow key={k} label={k} min={r[0]} max={r[1]} step={r[2]}
              value={v} onChange={nv => P.updateFX(fx.id, { [k]: nv })} />
          );
        })}
      </div>
    </div>
  );
}
function FXTab() {
  const P = useProg();
  const [pick, setPick] = useStateCh(false);
  return (
    <div style={{ display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }}>
      <div style={{ position: "relative" }}>
        <button className="nbtn compact glow pressed" style={{ width: "100%" }}
          onClick={() => setPick(p => !p)}>+ Add FX</button>
        {pick && (
          <div style={{ ...popoverStyle, maxHeight: 260, overflowY: "auto" }}>
            {Object.entries(window.FXTypes).map(([k, t]) => (
              <button key={k} className="nbtn compact" style={{ justifyContent: "flex-start", padding: "6px 8px" }}
                onClick={() => { window.Prog.addFX(k); setPick(false); }}>
                <span style={{ width: 6, height: 6, borderRadius: "50%",
                  background: "var(--led-cyan)", boxShadow: "0 0 6px var(--led-cyan-glow)", marginRight: 6 }} />
                {t.label}
              </button>
            ))}
          </div>
        )}
      </div>
      <div style={{ overflowY: "auto", flex: 1, marginTop: 8, paddingRight: 4 }}>
        {P.fxChain.length === 0 && (
          <div style={emptyHintStyle}>
            No FX active.<br/>
            Try <b>Bloom + Chromatic + Echo</b><br/>
            for a classic VJ chain.
          </div>
        )}
        {P.fxChain.map((f, i) => <FXRow key={f.id} fx={f} idx={i} />)}
      </div>
    </div>
  );
}

/* ----- SCRIPT TAB ----- */
function ScriptTab() {
  const P = useProg();
  const [v, setV] = useStateCh(P.scriptCode);
  return (
    <div style={{ display: "flex", flexDirection: "column", flex: 1, minHeight: 0, gap: 8 }}>
      <div className="cap">Per-frame script · vars: <code>t</code>, <code>audio</code>, <code>p</code>, <code>prog</code></div>
      <textarea value={v} onChange={e => setV(e.target.value)}
        style={{ ...codeAreaStyle, flex: 1, minHeight: 200 }} />
      {P.scriptError && (
        <div style={{
          padding: "6px 8px", background: "rgba(255,59,59,0.12)",
          border: "1px solid rgba(255,59,59,0.4)", borderRadius: 6,
          color: "var(--led-red)", fontFamily: "var(--font-mono)", fontSize: 10
        }}>{P.scriptError}</div>
      )}
      <div style={{ display: "flex", gap: 6 }}>
        <button className="nbtn compact glow pressed" style={{ flex: 1 }}
          onClick={() => P.setScript(v)}>▶ Apply</button>
        <button className="nbtn compact"
          onClick={() => { const s = "// p.hue = (p.hue + audio.bass*8) % 360;\n// p.intensity = 0.6 + audio.energy*1.4;\n"; setV(s); P.setScript(s); }}>
          Examples
        </button>
        <button className="nbtn compact"
          onClick={() => { setV(""); P.setScript(""); }}>Clear</button>
      </div>
      <div className="cap" style={{ fontSize: 9, lineHeight: 1.5 }}>
        SNIPPETS:<br/>
        <code style={codeInlineStyle}>p.hue = (t * 30 + audio.bass*100) % 360</code><br/>
        <code style={codeInlineStyle}>p.intensity = 0.6 + audio.energy * 1.4</code><br/>
        <code style={codeInlineStyle}>p.density = 0.3 + Math.sin(t * 2) * 0.3</code>
      </div>
    </div>
  );
}

/* ----- CLONER TAB ----- */
function ClonerTab() {
  const P = useProg();
  const c = P.cloner;
  const upd = (k, v) => { c[k] = v; P.emit(); };
  return (
    <div style={{ display: "flex", flexDirection: "column", flex: 1, gap: 10 }}>
      <div className="dock-section">
        <div className="dock-label" style={{ display: "flex", justifyContent: "space-between" }}>
          Cloner
          <Flip value={c.enabled} onChange={v => upd("enabled", v)} />
        </div>
        <SegRow label="Layout" value={c.layout} onChange={v => upd("layout", v)}
          options={["grid","radial","linear"]} />
        <SliderRow label="Count" min={2} max={16} step={1}
          value={c.count} onChange={v => upd("count", v)} />
        <SliderRow label="Spread" min={0.1} max={1.5} step={0.01}
          value={c.spread} onChange={v => upd("spread", v)} />
        <SliderRow label="Scale" min={0.1} max={1} step={0.01}
          value={c.scale} onChange={v => upd("scale", v)} />
        <SliderRow label="Hue shift" min={0} max={180} step={1}
          value={c.hueShift} onChange={v => upd("hueShift", v)} />
      </div>
      <div className="cap" style={{ fontSize: 9, lineHeight: 1.5 }}>
        Replicates the active visualizer in a grid/radial array,
        shifting hue across instances. Works with any preset.
      </div>
    </div>
  );
}

/* ----- shared row helpers ----- */
function SegRow({ label, value, onChange, options }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: 6, alignItems: "center" }}>
      <span className="cap">{label}</span>
      <div style={{ display: "flex", background: "linear-gradient(180deg,#0a0a0c,#16161a)",
        borderRadius: 6, padding: 2, boxShadow: "var(--nshadow-in-sm)" }}>
        {options.map(o => (
          <button key={o}
            onClick={() => onChange(o)}
            style={{
              flex: 1, padding: "3px 4px", border: "none",
              background: value === o ? "linear-gradient(180deg, var(--panel-hi), var(--panel))" : "transparent",
              color: value === o ? "var(--accent)" : "var(--ink-faint)",
              textShadow: value === o ? "0 0 4px var(--accent-glow)" : "none",
              borderRadius: 4, fontSize: 8.5, cursor: "pointer",
              fontFamily: "var(--font-display)", letterSpacing: "0.1em", textTransform: "uppercase",
              fontWeight: 600,
            }}>{o}</button>
        ))}
      </div>
    </div>
  );
}
function SliderRow({ label, value, onChange, min, max, step, unit = "" }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "60px 1fr 36px", gap: 6, alignItems: "center" }}>
      <span className="cap" style={{ textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}>{label}</span>
      <HSlider value={value} onChange={onChange} min={min} max={max} />
      <span style={{ fontFamily: "var(--font-mono)", fontSize: 9, color: "var(--accent)",
        textAlign: "right", textShadow: "0 0 3px var(--accent-glow)" }}>
        {step >= 1 ? value.toFixed(0) : value.toFixed(2)}{unit}
      </span>
    </div>
  );
}
const paramSelectStyle = {
  background: "linear-gradient(180deg,#0a0a0c,#16161a)",
  border: "none", borderRadius: 6, padding: "4px 6px",
  boxShadow: "var(--nshadow-in-sm)",
  color: "var(--accent)", textShadow: "0 0 3px var(--accent-glow)",
  fontFamily: "var(--font-mono)", fontSize: 10, outline: "none",
};
const codeAreaStyle = {
  background: "linear-gradient(180deg,#06080a,#0a0d10)",
  border: "1px solid #050708",
  borderRadius: 6, padding: "8px 10px",
  color: "var(--led-cyan)",
  textShadow: "0 0 3px var(--led-cyan-glow)",
  fontFamily: "var(--font-mono)", fontSize: 11,
  outline: "none", resize: "vertical",
  boxShadow: "var(--nshadow-in-sm)",
  letterSpacing: "0.02em", lineHeight: 1.5,
};
const codeInlineStyle = {
  background: "rgba(0,0,0,0.4)", padding: "1px 5px", borderRadius: 3,
  color: "var(--led-cyan)", fontSize: 9, display: "inline-block", marginBottom: 2,
  textShadow: "0 0 3px var(--led-cyan-glow)",
};
const popoverStyle = {
  position: "absolute", top: "100%", left: 0, right: 0,
  marginTop: 4, zIndex: 20,
  background: "linear-gradient(180deg, var(--panel-hi), var(--panel))",
  borderRadius: 8, padding: 6,
  display: "flex", flexDirection: "column", gap: 3,
  boxShadow: "0 10px 30px rgba(0,0,0,0.8), var(--nshadow-out-sm)",
};
const emptyHintStyle = {
  padding: "20px 12px", textAlign: "center", color: "var(--ink-faint)",
  fontSize: 11, lineHeight: 1.6,
};

/* ----- ChannelsRack: tab host ----- */
function ChannelsRack({ vizId, setVizId }) {
  const [tab, setTab] = useStateCh("presets");
  const [vizSearch, setVizSearch] = useStateCh("");
  const M = useMedia();
  const P = useProg();
  const tabs = [
    { k: "presets",   label: "Presets",   badge: window.VISUALIZERS.length },
    { k: "layers",    label: "Layers",    badge: M.layers.length },
    { k: "behave",    label: "Behave",    badge: P.behaviors.length },
    { k: "fx",        label: "FX",        badge: P.fxChain.length },
    { k: "cloner",    label: "Cloner",    badge: P.cloner.enabled ? "●" : 0 },
    { k: "script",    label: "Script",    badge: P.scriptCode.trim() ? "JS" : 0 },
  ];
  return (
    <div className="panel" style={{ overflow: "hidden", display: "flex", flexDirection: "column" }}>
      <Screws />
      <div className="panel-title">
        <span style={{ display: "inline-block", width: 6, height: 6, borderRadius: "50%",
          background: "var(--led-cyan)", boxShadow: "0 0 6px var(--led-cyan-glow)" }} />
        Channels
      </div>

      <div style={{
        display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 3,
        marginBottom: 8, padding: 3,
        background: "linear-gradient(180deg,#0a0a0c,#16161a)",
        borderRadius: 8, boxShadow: "var(--nshadow-in-sm)"
      }}>
        {tabs.map(t => (
          <button key={t.k}
            onClick={() => setTab(t.k)}
            style={{
              border: "none", padding: "5px 4px",
              background: tab === t.k ? "linear-gradient(180deg, var(--panel-hi), var(--panel))" : "transparent",
              color: tab === t.k ? "var(--accent)" : "var(--ink-faint)",
              textShadow: tab === t.k ? "0 0 4px var(--accent-glow)" : "none",
              borderRadius: 5,
              fontFamily: "var(--font-display)",
              fontSize: 9, letterSpacing: "0.08em", textTransform: "uppercase",
              fontWeight: 700, cursor: "pointer",
              display: "flex", alignItems: "center", justifyContent: "center", gap: 4,
            }}>
            {t.label}
            {t.badge !== 0 && (
              <span style={{
                background: tab === t.k ? "var(--accent)" : "rgba(255,255,255,0.08)",
                color: tab === t.k ? "#1a1a1e" : "var(--ink-faint)",
                fontSize: 8, padding: "1px 4px", borderRadius: 4,
                fontFamily: "var(--font-mono)", lineHeight: 1
              }}>{t.badge}</span>
            )}
          </button>
        ))}
      </div>

      {tab === "presets" && (
        <div style={{ display: "flex", flexDirection: "column", gap: 6, overflowY: "auto", flex: 1, paddingRight: 4 }}>
          <input
            value={vizSearch}
            onChange={e => setVizSearch(e.target.value)}
            placeholder={`Search ${window.VISUALIZERS.length} visualizers…`}
            style={{
              padding: "6px 9px", borderRadius: 6, border: "none", outline: "none",
              background: "linear-gradient(180deg,#0a0a0c,#16161a)",
              color: "var(--ink)", fontFamily: "var(--font-mono)", fontSize: 10,
              boxShadow: "var(--nshadow-in-sm)", marginBottom: 2,
            }} />
          {window.VISUALIZERS
            .filter(v => {
              const q = vizSearch.toLowerCase().trim();
              if (!q) return true;
              return v.name.toLowerCase().includes(q) || (v.tag || "").toLowerCase().includes(q);
            })
            .map(v => (
            <div key={v.id}
              className={`preset-card ${v.id === vizId ? "active" : ""}`}
              onClick={() => setVizId(v.id)}>
              <div className="preset-icon" style={{ color: v.id === vizId ? "var(--accent)" : "var(--ink-dim)" }}>
                <span style={{ width: 22, height: 22, display: "block" }}>{v.icon}</span>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="preset-name" style={{ fontSize: 11 }}>{v.name}</div>
                <div className="preset-tag">{v.tag}</div>
              </div>
              <Led on={v.id === vizId} color="amber" />
            </div>
          ))}
        </div>
      )}
      {tab === "layers"  && <LayersTab />}
      {tab === "behave"  && <BehaviorsTab />}
      {tab === "fx"      && <FXTab />}
      {tab === "cloner"  && <ClonerTab />}
      {tab === "script"  && <ScriptTab />}
    </div>
  );
}

window.ChannelsRack = ChannelsRack;
