feat: Footswitch Modes — Stomp/Preset/Snapshot/Combo with scribble strips
Implements multi-mode footswitch system for the Helix Stadium-inspired pedal UI: - Footswitch mode context provider with 4 modes: stomp, preset, snapshot, combo - ModeSelector in status bar (collapsed cycling mode for mobile) - Stomp mode: footswitches toggle individual blocks on/off, scribble strips show block name + status - Preset mode: footswitches show Bank Up/Down + preset selection, scribble strips show preset name + number - Snapshot mode: footswitches recall 8 snapshots, scribble strips show snapshot name - Combo mode: first 4 stomp + bank nav + preset select + global bypass - ScribbleStrip LCD component with LED indicator and double-tap support - Double-tap gesture hook for secondary actions (tuner, snapshot save, etc.) - FootswitchBar container with 8 capacitive-touch footswitch buttons and LED rings - usePedalState hook (WebSocket + API polling, ported from frontend-react/App.jsx) - Dark pedal theme CSS (Helix-inspired) with responsive mobile layout - Full App.tsx rewrite with status bar, footswitch bar, tab bar, and all screens
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* ScribbleStrip — the LCD-like label display above each footswitch.
|
||||
*
|
||||
* Shows block/preset/snapshot name and status, mimicking the
|
||||
* Helix / Line 6 scribble strip LCDs.
|
||||
*/
|
||||
|
||||
import type { ScribbleStripData, ScribbleColor } from '../types';
|
||||
import { SCRIBBLE_COLORS } from '../types';
|
||||
import { useDoubleTap } from '../hooks/useDoubleTap';
|
||||
|
||||
interface ScribbleStripProps {
|
||||
data: ScribbleStripData;
|
||||
onPress: () => void;
|
||||
onSecondary: () => void;
|
||||
}
|
||||
|
||||
export function ScribbleStrip({ data, onPress, onSecondary }: ScribbleStripProps) {
|
||||
const { handleTap } = useDoubleTap({
|
||||
onSingleTap: onPress,
|
||||
onDoubleTap: onSecondary,
|
||||
});
|
||||
|
||||
const color = SCRIBBLE_COLORS[data.color ?? 'off'];
|
||||
const isActive = data.active ?? false;
|
||||
const isBypassed = data.bypassed ?? false;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="scribble-strip"
|
||||
data-active={isActive}
|
||||
data-bypassed={isBypassed}
|
||||
onClick={handleTap}
|
||||
style={{
|
||||
'--scribble-color': color,
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{/* LED indicator */}
|
||||
<div
|
||||
className="scribble-led"
|
||||
style={{
|
||||
background: color,
|
||||
boxShadow: isActive ? `0 0 6px ${color}` : 'none',
|
||||
opacity: isActive ? 1 : 0.25,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Label area — LCD-style */}
|
||||
<div className="scribble-label-wrap">
|
||||
<span className="scribble-label">{data.label || '—'}</span>
|
||||
{data.sublabel && (
|
||||
<span className="scribble-sublabel">{data.sublabel}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Icon (optional) */}
|
||||
{data.icon && <span className="scribble-icon">{data.icon}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user