import { useState, useRef, useCallback, useMemo } from "react"; // ── Design tokens ── const T = { bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32", amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8", blueDim: "#1E4060", green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458", }; // ── Block param definitions (mirrored from App.jsx) ── const BLOCK_PARAMS = { overdrive: [{ key:'drive', label:'Drive', min:0, max:100, def:30 }, { key:'tone', label:'Tone', min:0, max:100, def:50 }, { key:'level', label:'Level', min:0, max:100, def:70 }], distortion: [{ key:'drive', label:'Drive', min:0, max:100, def:50 }, { key:'tone', label:'Tone', min:0, max:100, def:40 }, { key:'level', label:'Level', min:0, max:100, def:60 }], fuzz: [{ key:'fuzz', label:'Fuzz', min:0, max:100, def:60 }, { key:'tone', label:'Tone', min:0, max:100, def:30 }, { key:'level', label:'Level', min:0, max:100, def:60 }], delay: [{ key:'time', label:'Time', min:0, max:100, def:40 }, { key:'feedback', label:'Feedback', min:0, max:100, def:30 }, { key:'mix', label:'Mix', min:0, max:100, def:35 }], echo: [{ key:'time', label:'Time', min:0, max:100, def:40 }, { key:'feedback', label:'Feedback', min:0, max:100, def:30 }, { key:'mix', label:'Mix', min:0, max:100, def:35 }], reverb: [{ key:'decay', label:'Decay', min:0, max:100, def:50 }, { key:'mix', label:'Mix', min:0, max:100, def:30 }, { key:'tone', label:'Tone', min:0, max:100, def:50 }], chorus: [{ key:'rate', label:'Rate', min:0, max:100, def:40 }, { key:'depth', label:'Depth', min:0, max:100, def:40 }, { key:'mix', label:'Mix', min:0, max:100, def:30 }], flanger: [{ key:'rate', label:'Rate', min:0, max:100, def:30 }, { key:'depth', label:'Depth', min:0, max:100, def:50 }, { key:'feedback', label:'Feedback', min:0, max:100, def:40 }, { key:'mix', label:'Mix', min:0, max:100, def:30 }], phaser: [{ key:'rate', label:'Rate', min:0, max:100, def:30 }, { key:'depth', label:'Depth', min:0, max:100, def:50 }, { key:'feedback', label:'Feedback', min:0, max:100, def:40 }, { key:'mix', label:'Mix', min:0, max:100, def:30 }], compressor: [{ key:'threshold', label:'Thresh', min:0, max:100, def:50 }, { key:'ratio', label:'Ratio', min:1, max:20, def:4 }, { key:'gain', label:'Gain', min:0, max:100, def:50 }], gate: [{ key:'threshold', label:'Thresh', min:0, max:100, def:30 }, { key:'release', label:'Release', min:0, max:100, def:50 }], eq: [{ key:'low', label:'Low', min:-12, max:12, def:0 }, { key:'mid', label:'Mid', min:-12, max:12, def:0 }, { key:'high', label:'High', min:-12, max:12, def:0 }, { key:'level', label:'Level', min:0, max:100, def:70 }], cabinet: [{ key:'level', label:'Level', min:0, max:100, def:75 }], ir: [{ key:'level', label:'Level', min:0, max:100, def:75 }], }; const BLOCK_COLORS = { overdrive: T.amber, distortion: T.red, fuzz: '#C07030', delay: T.blue, echo: T.blue, reverb: '#60B0D0', chorus: T.green, flanger: '#50C080', phaser: '#80C050', tremolo: T.green, compressor: '#D09040', gate: T.textSec, eq: '#7080D0', cabinet: '#D07090', ir: '#D07090', nam: '#E0A040', boost: T.amber, wah: '#D0A060', volume: T.textSec, tuner: '#80D0A0', }; const BLOCK_DISPLAY_NAMES = { overdrive: "OD-808", distortion: "RAT", fuzz: "Big Muff", delay: "Digital Delay", echo: "Tape Echo", reverb: "Hall Reverb", chorus: "Chorus", flanger: "Flanger", phaser: "Phase 90", tremolo: "Tremolo", compressor: "Compressor", gate: "Noise Gate", eq: "EQ", cabinet: "Cabinet", ir: "IR Loader", nam: "NAM Model", boost: "Clean Boost", wah: "Wah", volume: "Volume", tuner: "Tuner", loop: "FX Loop", }; function getBlockParams(type) { return BLOCK_PARAMS[(type || '').toLowerCase()] || [{ key:'level', label:'Level', min:0, max:100, def:50 }]; } function getBlockColor(type) { return BLOCK_COLORS[(type || '').toLowerCase()] || T.amber; } function getBlockDisplayName(block) { const key = (block.type || '').toLowerCase(); if (block.nam_model_path) { const parts = block.nam_model_path.split('/').pop()?.replace(/\.(nam|wav|aiff?)$/i, '') || ''; return parts || 'NAM Model'; } if (block.ir_file_path) { const parts = block.ir_file_path.split('/').pop()?.replace(/\.(wav|aiff?|ir)$/i, '') || ''; return parts || 'IR Loader'; } return BLOCK_DISPLAY_NAMES[key] || key.charAt(0).toUpperCase() + key.slice(1); } // ── Large touch knob (bigger version for Focus View) ── function FocusKnob({ label, value = 50, onChange, size = 72, color = T.amber, min = 0, max = 100, compact = false }) { const startRef = useRef(null); const norm = (value - min) / (max - min); const angle = -140 + norm * 280; const dotH = size * 0.38; const dotTop = size * 0.5 - dotH; const handleStart = (e) => { e.preventDefault(); const clientY = e.touches ? e.touches[0].clientY : e.clientY; startRef.current = { y: clientY, val: value }; const move = (ev) => { const cy = ev.touches ? ev.touches[0].clientY : ev.clientY; const delta = (startRef.current.y - cy) / 120; const next = Math.max(min, Math.min(max, startRef.current.val + delta * (max - min))); onChange?.(Math.round(next)); }; const up = () => { window.removeEventListener("mousemove",move);window.removeEventListener("mouseup",up); window.removeEventListener("touchmove",move);window.removeEventListener("touchend",up); }; window.addEventListener("mousemove",move);window.addEventListener("mouseup",up); window.addEventListener("touchmove",move,{passive:false});window.addEventListener("touchend",up); }; const svgSize = size + 16, cx = svgSize / 2, cy = svgSize / 2, r = size / 2 + 4; const lx = cx + r * Math.cos((-140 - 90) * (Math.PI / 180)), ly = cy + r * Math.sin((-140 - 90) * (Math.PI / 180)); const ex = cx + r * Math.cos((angle - 90) * (Math.PI / 180)), ey = cy + r * Math.sin((angle - 90) * (Math.PI / 180)); const large = norm * 280 > 180 ? 1 : 0; return (