Mobile version is taking shape

This commit is contained in:
2025-10-06 11:16:57 +02:00
parent 7559a2bfb5
commit 71e01488dc
3 changed files with 89 additions and 17 deletions

View File

@ -36,6 +36,7 @@ function App() {
const [customTile, setCustomTile] = useState<TileState>(() => createTileStateFromCurrent('t*(8&t>>9)'))
const [showWarning, setShowWarning] = useState(true)
const [showHelp, setShowHelp] = useState(false)
const [mobileHeaderTab, setMobileHeaderTab] = useState<'global' | 'options' | 'modulate'>('global')
const playbackManagerRef = useRef<PlaybackManager | null>(null)
const downloadServiceRef = useRef<DownloadService>(new DownloadService())
const switchTimerRef = useRef<number | null>(null)
@ -542,44 +543,110 @@ function App() {
{showWarning && <AudioContextWarning onDismiss={handleDismissWarning} />}
{showHelp && <HelpModal onClose={() => setShowHelp(false)} />}
<header className="bg-black border-b-2 border-white px-2 lg:px-6 py-2 lg:py-3">
<div className="flex flex-col lg:flex-row items-start lg:items-center gap-2 lg:gap-6">
<div className="flex items-center justify-between w-full lg:w-auto gap-2">
{/* Mobile header */}
<div className="lg:hidden">
<div className="flex items-center justify-between mb-2">
<h1
onClick={() => setShowHelp(true)}
className="font-mono text-[10px] md:text-sm tracking-[0.3em] text-white flex-shrink-0 cursor-pointer hover:opacity-70 transition-opacity"
className="font-mono text-[10px] tracking-[0.3em] text-white cursor-pointer hover:opacity-70 transition-opacity"
>
BRUITISTE
</h1>
<div className="flex gap-2 lg:hidden">
<div className="flex gap-1 border-2 border-white">
<button
onClick={() => setMobileHeaderTab('global')}
className={`px-2 py-1 font-mono text-[8px] tracking-[0.15em] transition-colors ${
mobileHeaderTab === 'global'
? 'bg-white text-black'
: 'bg-black text-white hover:bg-white/10'
}`}
>
GLOBAL
</button>
<button
onClick={() => setMobileHeaderTab('options')}
className={`px-2 py-1 font-mono text-[8px] tracking-[0.15em] transition-colors ${
mobileHeaderTab === 'options'
? 'bg-white text-black'
: 'bg-black text-white hover:bg-white/10'
}`}
>
OPTIONS
</button>
<button
onClick={() => setMobileHeaderTab('modulate')}
className={`px-2 py-1 font-mono text-[8px] tracking-[0.15em] transition-colors ${
mobileHeaderTab === 'modulate'
? 'bg-white text-black'
: 'bg-black text-white hover:bg-white/10'
}`}
>
MODULATE
</button>
</div>
</div>
{mobileHeaderTab === 'global' && (
<div className="flex gap-2">
<button
onClick={handleStop}
disabled={!playing}
className="px-2 py-1 bg-black text-white border-2 border-white font-mono text-[9px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
className="flex-1 px-2 py-2 bg-black text-white border-2 border-white font-mono text-[9px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
>
<Square size={12} strokeWidth={2} fill="currentColor" />
<Square size={12} strokeWidth={2} fill="currentColor" className="mx-auto" />
</button>
<button
onClick={handleRandom}
className="px-2 py-1 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
className="flex-1 px-2 py-2 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
>
<Dices size={12} strokeWidth={2} />
<Dices size={12} strokeWidth={2} className="mx-auto" />
</button>
<button
onClick={handleRandomizeAllParams}
className="px-2 py-1 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
className="flex-1 px-2 py-2 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
>
<Sparkles size={12} strokeWidth={2} />
<Sparkles size={12} strokeWidth={2} className="mx-auto" />
</button>
<button
onClick={handleDownloadAll}
disabled={downloading}
className="px-2 py-1 bg-black text-white border-2 border-white font-mono text-[9px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
className="flex-1 px-2 py-2 bg-black text-white border-2 border-white font-mono text-[9px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
>
<Archive size={12} strokeWidth={2} />
<Archive size={12} strokeWidth={2} className="mx-auto" />
</button>
</div>
</div>
<div className="w-full lg:flex-1">
)}
{mobileHeaderTab === 'options' && (
<EngineControls
values={engineValues}
onChange={handleEngineChange}
onMapClick={handleParameterMapClick}
getMappedLFOs={getMappedLFOs}
showOnlySliders
/>
)}
{mobileHeaderTab === 'modulate' && (
<EngineControls
values={engineValues}
onChange={handleEngineChange}
onMapClick={handleParameterMapClick}
getMappedLFOs={getMappedLFOs}
showOnlyKnobs
/>
)}
</div>
{/* Desktop header */}
<div className="hidden lg:flex items-center gap-6">
<h1
onClick={() => setShowHelp(true)}
className="font-mono text-sm tracking-[0.3em] text-white flex-shrink-0 cursor-pointer hover:opacity-70 transition-opacity"
>
BRUITISTE
</h1>
<div className="flex-1">
<EngineControls
values={engineValues}
onChange={handleEngineChange}
@ -587,7 +654,7 @@ function App() {
getMappedLFOs={getMappedLFOs}
/>
</div>
<div className="hidden lg:flex gap-2 lg:gap-4 flex-shrink-0">
<div className="flex gap-4 flex-shrink-0">
<button
onClick={handleStop}
disabled={!playing}

View File

@ -16,7 +16,7 @@ interface EffectsBarProps {
export function EffectsBar({ values, onChange, onMapClick, getMappedLFOs }: EffectsBarProps) {
const [expandedEffect, setExpandedEffect] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<string>(EFFECTS[0].id)
const [isCollapsed, setIsCollapsed] = useState(false)
const [isCollapsed, setIsCollapsed] = useState(true)
const randomizeEffect = (effect: typeof EFFECTS[number]) => {
effect.parameters.forEach(param => {
if (param.id.endsWith('Enable')) return

View File

@ -8,11 +8,13 @@ interface EngineControlsProps {
onChange: (parameterId: string, value: number) => void
onMapClick?: (paramId: string, lfoIndex: number) => void
getMappedLFOs?: (paramId: string) => number[]
showOnlySliders?: boolean
showOnlyKnobs?: boolean
}
const KNOB_PARAMS = ['masterVolume', 'pitch', 'a', 'b', 'c', 'd']
export function EngineControls({ values, onChange, onMapClick, getMappedLFOs }: EngineControlsProps) {
export function EngineControls({ values, onChange, onMapClick, getMappedLFOs, showOnlySliders, showOnlyKnobs }: EngineControlsProps) {
const formatValue = (id: string, value: number): string => {
switch (id) {
case 'sampleRate':
@ -33,6 +35,9 @@ export function EngineControls({ values, onChange, onMapClick, getMappedLFOs }:
{ENGINE_CONTROLS[0].parameters.map(param => {
const useKnob = KNOB_PARAMS.includes(param.id)
if (showOnlySliders && useKnob) return null
if (showOnlyKnobs && !useKnob) return null
if (useKnob) {
return (
<Knob