812 lines
24 KiB
HTML
812 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>NAM Pedal — Helix Stadium UX Concept</title>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@300;400;500;600;700&display=swap');
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; -webkit-tap-highlight-color: transparent; }
|
|
|
|
:root {
|
|
--bg: #0A0A0C;
|
|
--panel: #141418;
|
|
--surface: #1C1C22;
|
|
--border: #2A2A32;
|
|
--amber: #E8A030;
|
|
--amberDim: #7A5218;
|
|
--blue: #3A7BA8;
|
|
--blueDim: #1E4060;
|
|
--green: #3AB87A;
|
|
--red: #C84040;
|
|
--text: #F0EDE6;
|
|
--textSec: #8888A0;
|
|
--textDim: #444458;
|
|
}
|
|
|
|
html, body {
|
|
height: 100%;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: 'Inter', sans-serif;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ── Main Layout ── */
|
|
.pedal {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--bg);
|
|
}
|
|
|
|
/* ── Status Bar ── */
|
|
.status-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 6px 14px;
|
|
background: var(--panel);
|
|
border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
.status-left { display: flex; align-items: center; gap: 10px; }
|
|
.status-led {
|
|
width: 8px; height: 8px; border-radius: 50%;
|
|
background: var(--green);
|
|
box-shadow: 0 0 8px var(--green);
|
|
}
|
|
.status-text { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--textSec); letter-spacing: .08em; }
|
|
.status-right { display: flex; align-items: center; gap: 14px; }
|
|
.status-chip {
|
|
font-family: 'JetBrains Mono', monospace; font-size: 10px;
|
|
padding: 3px 8px; border-radius: 4px;
|
|
background: rgba(58,123,168,.15); color: var(--blue);
|
|
border: 1px solid rgba(58,123,168,.3);
|
|
}
|
|
|
|
/* ── Scribble Strip / Info Bar ── */
|
|
.scribble-bar {
|
|
display: flex;
|
|
padding: 8px 14px;
|
|
background: #0F0F14;
|
|
border-bottom: 1px solid var(--border);
|
|
gap: 6px;
|
|
flex-shrink: 0;
|
|
}
|
|
.scribble {
|
|
flex: 1;
|
|
padding: 6px 10px;
|
|
border-radius: 5px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
text-align: center;
|
|
}
|
|
.scribble .label { font-size: 9px; color: var(--textDim); letter-spacing: .06em; text-transform: uppercase; }
|
|
.scribble .value { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 600; color: var(--amber); margin-top: 1px; }
|
|
|
|
/* ── Block Chain ── */
|
|
.chain-area {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.chain-scroll {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 14px 12px;
|
|
overflow-x: auto;
|
|
flex-shrink: 0;
|
|
align-items: stretch;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--border) transparent;
|
|
}
|
|
.chain-scroll::-webkit-scrollbar { height: 4px; }
|
|
.chain-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
.chain-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
|
|
.chain-term {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 12px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
letter-spacing: .1em;
|
|
color: var(--blue);
|
|
background: var(--blueDim);
|
|
border: 1px solid rgba(58,123,168,.4);
|
|
border-radius: 8px;
|
|
min-height: 72px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.chain-block {
|
|
min-width: 110px;
|
|
max-width: 140px;
|
|
flex-shrink: 0;
|
|
background: var(--panel);
|
|
border: 2px solid var(--border);
|
|
border-radius: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
cursor: pointer;
|
|
transition: all .15s;
|
|
position: relative;
|
|
}
|
|
.chain-block.selected {
|
|
border-color: var(--amber);
|
|
box-shadow: 0 0 12px rgba(232,160,48,.3);
|
|
}
|
|
.chain-block.bypassed { opacity: .45; }
|
|
|
|
.chain-block .block-led {
|
|
width: 6px; height: 6px; border-radius: 50%;
|
|
position: absolute;
|
|
top: 8px; right: 8px;
|
|
}
|
|
.block-led.on { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
|
.block-led.off { background: var(--textDim); }
|
|
|
|
.chain-block .block-scribble {
|
|
padding: 8px 10px 4px;
|
|
text-align: center;
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
.block-scribble .block-type {
|
|
font-size: 9px;
|
|
text-transform: uppercase;
|
|
letter-spacing: .08em;
|
|
color: var(--textDim);
|
|
margin-bottom: 2px;
|
|
}
|
|
.block-scribble .block-name {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
line-height: 1.2;
|
|
}
|
|
.block-scribble .block-sub {
|
|
font-size: 10px;
|
|
color: var(--textSec);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.chain-block .block-indicator {
|
|
height: 3px;
|
|
border-radius: 0 0 8px 8px;
|
|
margin-top: auto;
|
|
}
|
|
|
|
.chain-arrow {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
color: var(--textDim);
|
|
font-size: 18px;
|
|
padding: 0 2px;
|
|
}
|
|
|
|
/* ── Parameter Panel ── */
|
|
.param-panel {
|
|
background: var(--panel);
|
|
border-top: 1px solid var(--border);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 12px 14px 14px;
|
|
flex-shrink: 0;
|
|
}
|
|
.param-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
.param-header h3 {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: .08em;
|
|
color: var(--textSec);
|
|
}
|
|
.param-header .block-label {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 12px;
|
|
color: var(--amber);
|
|
}
|
|
|
|
.param-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 10px 20px;
|
|
}
|
|
|
|
.param-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.param-group .param-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 11px;
|
|
}
|
|
.param-label .name { color: var(--textSec); text-transform: uppercase; letter-spacing: .05em; }
|
|
.param-label .value { font-family: 'JetBrains Mono', monospace; color: var(--amber); font-weight: 600; }
|
|
|
|
.touch-slider {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 100%;
|
|
height: 32px;
|
|
border-radius: 6px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
outline: none;
|
|
cursor: pointer;
|
|
transition: border-color .15s;
|
|
}
|
|
.touch-slider::-webkit-slider-runnable-track {
|
|
height: 32px;
|
|
border-radius: 6px;
|
|
background: linear-gradient(to right, var(--amberDim) 0%, var(--amber) 100%);
|
|
background-size: var(--pct, 50%) 100%;
|
|
background-repeat: no-repeat;
|
|
background-color: var(--surface);
|
|
}
|
|
.touch-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 6px;
|
|
background: var(--amber);
|
|
border: 2px solid var(--bg);
|
|
box-shadow: 0 1px 6px rgba(0,0,0,.5);
|
|
margin-top: 1px;
|
|
cursor: pointer;
|
|
transition: transform .1s;
|
|
}
|
|
.touch-slider::-webkit-slider-thumb:active {
|
|
transform: scale(1.12);
|
|
}
|
|
|
|
.touch-slider::-moz-range-track {
|
|
height: 32px;
|
|
border-radius: 6px;
|
|
background: var(--surface);
|
|
}
|
|
.touch-slider::-moz-range-thumb {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 6px;
|
|
background: var(--amber);
|
|
border: 2px solid var(--bg);
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* ── Footswitch Bar ── */
|
|
.footswitch-bar {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 10px 12px 14px;
|
|
background: var(--panel);
|
|
border-top: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.footswitch {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 10px 6px;
|
|
border-radius: 10px;
|
|
border: 2px solid var(--border);
|
|
background: var(--surface);
|
|
cursor: pointer;
|
|
transition: all .12s;
|
|
position: relative;
|
|
min-height: 68px;
|
|
}
|
|
.footswitch:active {
|
|
transform: scale(.95);
|
|
border-color: var(--amber);
|
|
}
|
|
.footswitch.active {
|
|
border-color: var(--amber);
|
|
background: rgba(232,160,48,.1);
|
|
}
|
|
.footswitch.preset {
|
|
border-color: var(--blueDim);
|
|
}
|
|
.footswitch.preset.active {
|
|
border-color: var(--blue);
|
|
}
|
|
|
|
.footswitch .fs-scribble {
|
|
font-size: 9px;
|
|
color: var(--textDim);
|
|
text-transform: uppercase;
|
|
letter-spacing: .06em;
|
|
margin-bottom: 2px;
|
|
}
|
|
.footswitch .fs-label {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
max-width: 100%;
|
|
}
|
|
.footswitch .fs-sub {
|
|
font-size: 9px;
|
|
color: var(--textSec);
|
|
margin-top: 1px;
|
|
}
|
|
.footswitch .fs-led {
|
|
width: 5px; height: 5px; border-radius: 50%;
|
|
position: absolute;
|
|
top: 6px; right: 6px;
|
|
}
|
|
.fs-led.green { background: var(--green); }
|
|
.fs-led.amber { background: var(--amber); }
|
|
.fs-led.off { background: var(--textDim); }
|
|
|
|
.footswitch .fs-badge {
|
|
position: absolute;
|
|
top: 5px; left: 5px;
|
|
font-size: 8px;
|
|
padding: 1px 5px;
|
|
border-radius: 3px;
|
|
background: rgba(58,123,168,.2);
|
|
color: var(--blue);
|
|
}
|
|
|
|
/* ── Animations ── */
|
|
@keyframes pulse-amber { 0%,100%{opacity:1} 50%{opacity:.6} }
|
|
.recording { animation: pulse-amber 1s infinite; }
|
|
|
|
/* ── VU Meter ── */
|
|
.vu-strip {
|
|
display: flex;
|
|
gap: 3px;
|
|
align-items: flex-end;
|
|
height: 28px;
|
|
}
|
|
.vu-seg {
|
|
width: 4px;
|
|
border-radius: 1px;
|
|
transition: background .05s;
|
|
}
|
|
|
|
/* ── Focus / Active styles for touch ── */
|
|
.touch-target {
|
|
cursor: pointer;
|
|
transition: transform .1s;
|
|
}
|
|
.touch-target:active {
|
|
transform: scale(.96);
|
|
}
|
|
|
|
/* ── Preset Selector ── */
|
|
.preset-strip {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 8px 14px;
|
|
flex-shrink: 0;
|
|
background: rgba(0,0,0,.3);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.preset-chip {
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all .12s;
|
|
flex: 1;
|
|
}
|
|
.preset-chip.active {
|
|
border-color: var(--amber);
|
|
background: rgba(232,160,48,.1);
|
|
}
|
|
.preset-chip .pc-name { font-size: 11px; font-weight: 500; }
|
|
.preset-chip .pc-num { font-family: 'JetBrains Mono', monospace; font-size: 9px; color: var(--textDim); }
|
|
|
|
/* ── Responsive: slim down for smaller screens ── */
|
|
@media (max-width: 480px) {
|
|
.chain-block { min-width: 90px; max-width: 110px; }
|
|
.footswitch { min-height: 58px; padding: 8px 4px; }
|
|
.footswitch .fs-label { font-size: 11px; }
|
|
.param-grid { grid-template-columns: 1fr; gap: 8px; }
|
|
.preset-chip .pc-name { font-size: 10px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="pedal">
|
|
|
|
<!-- Status Bar -->
|
|
<div class="status-bar">
|
|
<div class="status-left">
|
|
<div class="status-led"></div>
|
|
<span class="status-text">CONNECTED</span>
|
|
<span class="status-chip">● Plexi Crunch</span>
|
|
</div>
|
|
<div class="status-right">
|
|
<div class="vu-strip" id="vuL"></div>
|
|
<span class="status-text" style="color:var(--textDim);font-size:10px;">CPU 23%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scribble Strip Info Bar -->
|
|
<div class="scribble-bar">
|
|
<div class="scribble"><div class="label">Preset</div><div class="value">Plexi Crunch</div></div>
|
|
<div class="scribble"><div class="label">Bank</div><div class="value">A · 01</div></div>
|
|
<div class="scribble"><div class="label">Routing</div><div class="value">MONO</div></div>
|
|
<div class="scribble"><div class="label">Master</div><div class="value">72%</div></div>
|
|
</div>
|
|
|
|
<!-- Preset Strip -->
|
|
<div class="preset-strip">
|
|
<div class="preset-chip" onclick="selectPreset(this)">
|
|
<div class="pc-name">Plexi Crunch</div>
|
|
<div class="pc-num">01</div>
|
|
</div>
|
|
<div class="preset-chip active" onclick="selectPreset(this)">
|
|
<div class="pc-name">Modern Hi-Gain</div>
|
|
<div class="pc-num">02</div>
|
|
</div>
|
|
<div class="preset-chip" onclick="selectPreset(this)">
|
|
<div class="pc-name">Clean Chime</div>
|
|
<div class="pc-num">03</div>
|
|
</div>
|
|
<div class="preset-chip" onclick="selectPreset(this)">
|
|
<div class="pc-name">Blues Drive</div>
|
|
<div class="pc-num">04</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Block Chain Area -->
|
|
<div class="chain-area">
|
|
<div class="chain-scroll" id="chainScroll">
|
|
<div class="chain-term">IN</div>
|
|
<div class="chain-arrow">▶</div>
|
|
|
|
<div class="chain-block selected" data-block="gate" onclick="selectBlock(this)">
|
|
<div class="block-led on"></div>
|
|
<div class="block-scribble">
|
|
<div class="block-type">Dynamics</div>
|
|
<div class="block-name">Noise Gate</div>
|
|
<div class="block-sub">Threshold 40%</div>
|
|
</div>
|
|
<div class="block-indicator" style="background:var(--green);"></div>
|
|
</div>
|
|
|
|
<div class="chain-arrow">▶</div>
|
|
|
|
<div class="chain-block" data-block="drive" onclick="selectBlock(this)">
|
|
<div class="block-led on"></div>
|
|
<div class="block-scribble">
|
|
<div class="block-type">Drive</div>
|
|
<div class="block-name">OD-808</div>
|
|
<div class="block-sub">Drive 65%</div>
|
|
</div>
|
|
<div class="block-indicator" style="background:var(--amber);"></div>
|
|
</div>
|
|
|
|
<div class="chain-arrow">▶</div>
|
|
|
|
<div class="chain-block bypassed" data-block="dist" onclick="selectBlock(this)">
|
|
<div class="block-led off"></div>
|
|
<div class="block-scribble">
|
|
<div class="block-type">Distortion</div>
|
|
<div class="block-name">RAT</div>
|
|
<div class="block-sub">— Bypassed —</div>
|
|
</div>
|
|
<div class="block-indicator" style="background:var(--textDim);"></div>
|
|
</div>
|
|
|
|
<div class="chain-arrow">▶</div>
|
|
|
|
<div class="chain-block" data-block="mod" onclick="selectBlock(this)">
|
|
<div class="block-led on"></div>
|
|
<div class="block-scribble">
|
|
<div class="block-type">Modulation</div>
|
|
<div class="block-name">Phase 90</div>
|
|
<div class="block-sub">Rate 35%</div>
|
|
</div>
|
|
<div class="block-indicator" style="background:var(--blue);"></div>
|
|
</div>
|
|
|
|
<div class="chain-arrow">▶</div>
|
|
|
|
<div class="chain-block" data-block="delay" onclick="selectBlock(this)">
|
|
<div class="block-led on"></div>
|
|
<div class="block-scribble">
|
|
<div class="block-type">Delay</div>
|
|
<div class="block-name">Digital Delay</div>
|
|
<div class="block-sub">450ms · 25%</div>
|
|
</div>
|
|
<div class="block-indicator" style="background:#A060E0;"></div>
|
|
</div>
|
|
|
|
<div class="chain-arrow">▶</div>
|
|
|
|
<div class="chain-block" data-block="reverb" onclick="selectBlock(this)">
|
|
<div class="block-led on"></div>
|
|
<div class="block-scribble">
|
|
<div class="block-type">Reverb</div>
|
|
<div class="block-name">Hall Reverb</div>
|
|
<div class="block-sub">Decay 60%</div>
|
|
</div>
|
|
<div class="block-indicator" style="background:#60A0E0;"></div>
|
|
</div>
|
|
|
|
<div class="chain-arrow">▶</div>
|
|
<div class="chain-term">OUT</div>
|
|
</div>
|
|
|
|
<!-- Parameter Panel -->
|
|
<div class="param-panel" id="paramPanel">
|
|
<div class="param-header">
|
|
<h3>Noise Gate <span class="block-label">· Dynamics</span></h3>
|
|
<span style="color:var(--green);font-size:11px;display:flex;align-items:center;gap:4px;">
|
|
<span style="width:6px;height:6px;border-radius:50%;background:var(--green);display:inline-block;"></span> Active
|
|
</span>
|
|
</div>
|
|
<div class="param-grid">
|
|
<div class="param-group">
|
|
<div class="param-label">
|
|
<span class="name">Threshold</span>
|
|
<span class="value" id="val_thresh">40</span>
|
|
</div>
|
|
<input type="range" class="touch-slider" min="0" max="100" value="40" oninput="updateSlider(this,'val_thresh')" style="--pct:40%">
|
|
</div>
|
|
<div class="param-group">
|
|
<div class="param-label">
|
|
<span class="name">Release</span>
|
|
<span class="value" id="val_rel">25</span>
|
|
</div>
|
|
<input type="range" class="touch-slider" min="0" max="100" value="25" oninput="updateSlider(this,'val_rel')" style="--pct:25%">
|
|
</div>
|
|
<div class="param-group">
|
|
<div class="param-label">
|
|
<span class="name">Depth</span>
|
|
<span class="value" id="val_dep">70</span>
|
|
</div>
|
|
<input type="range" class="touch-slider" min="0" max="100" value="70" oninput="updateSlider(this,'val_dep')" style="--pct:70%">
|
|
</div>
|
|
<div class="param-group">
|
|
<div class="param-label">
|
|
<span class="name">Mix</span>
|
|
<span class="value" id="val_mix">100</span>
|
|
</div>
|
|
<input type="range" class="touch-slider" min="0" max="100" value="100" oninput="updateSlider(this,'val_mix')" style="--pct:100%">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footswitch Bar -->
|
|
<div class="footswitch-bar">
|
|
<div class="footswitch preset" onclick="toggleFS(this)">
|
|
<span class="fs-scribble">BANK</span>
|
|
<span class="fs-label">▲ A</span>
|
|
<span class="fs-sub">Bank Up</span>
|
|
</div>
|
|
|
|
<div class="footswitch active" data-block="gate" onclick="toggleBlock(this,'gate')">
|
|
<span class="fs-scribble">GATE</span>
|
|
<span class="fs-label">Noise Gate</span>
|
|
<span class="fs-sub">Threshold 40%</span>
|
|
<div class="fs-led green"></div>
|
|
</div>
|
|
|
|
<div class="footswitch" data-block="drive" onclick="toggleBlock(this,'drive')">
|
|
<span class="fs-scribble">DRIVE</span>
|
|
<span class="fs-label">OD-808</span>
|
|
<span class="fs-sub">Drive 65%</span>
|
|
<div class="fs-led green"></div>
|
|
</div>
|
|
|
|
<div class="footswitch" data-block="dist" onclick="toggleBlock(this,'dist')">
|
|
<span class="fs-scribble">DIST</span>
|
|
<span class="fs-label" style="color:var(--textDim);">RAT</span>
|
|
<span class="fs-sub" style="color:var(--textDim);">Bypassed</span>
|
|
<div class="fs-led off"></div>
|
|
</div>
|
|
|
|
<div class="footswitch" data-block="mod" onclick="selectBlockFromFS(this,'mod')">
|
|
<span class="fs-scribble">MOD</span>
|
|
<span class="fs-label">Phase 90</span>
|
|
<span class="fs-sub">Rate 35%</span>
|
|
<div class="fs-led green"></div>
|
|
</div>
|
|
|
|
<div class="footswitch preset" onclick="toggleFS(this)">
|
|
<span class="fs-scribble">BANK</span>
|
|
<span class="fs-label">▼ A</span>
|
|
<span class="fs-sub">Bank Down</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// ── Block Chain Selection ──
|
|
function selectBlock(el) {
|
|
document.querySelectorAll('.chain-block').forEach(b => b.classList.remove('selected'));
|
|
el.classList.add('selected');
|
|
|
|
// Update footswitch bar
|
|
document.querySelectorAll('.footswitch[data-block]').forEach(f => f.classList.remove('active'));
|
|
const blockId = el.dataset.block;
|
|
const fs = document.querySelector(`.footswitch[data-block="${blockId}"]`);
|
|
if (fs) fs.classList.add('active');
|
|
|
|
// Update parameter panel (simple demo)
|
|
const name = el.querySelector('.block-name').textContent;
|
|
const type = el.querySelector('.block-type').textContent;
|
|
const header = document.querySelector('.param-header h3');
|
|
header.innerHTML = `${name} <span class="block-label">· ${type}</span>`;
|
|
|
|
// Update param panel content based on block
|
|
const params = {
|
|
gate: [
|
|
['Threshold', '40', 0, 100],
|
|
['Release', '25', 0, 100],
|
|
['Depth', '70', 0, 100],
|
|
['Mix', '100', 0, 100],
|
|
],
|
|
drive: [
|
|
['Drive', '65', 0, 100],
|
|
['Tone', '55', 0, 100],
|
|
['Level', '70', 0, 100],
|
|
['Tight', '30', 0, 100],
|
|
],
|
|
dist: [
|
|
['Distortion', '75', 0, 100],
|
|
['Filter', '50', 0, 100],
|
|
['Volume', '65', 0, 100],
|
|
['Gain', '80', 0, 100],
|
|
],
|
|
mod: [
|
|
['Rate', '35', 0, 100],
|
|
['Depth', '60', 0, 100],
|
|
['Feedback', '40', 0, 100],
|
|
['Mix', '50', 0, 100],
|
|
],
|
|
delay: [
|
|
['Time', '450', 20, 2000],
|
|
['Feedback', '25', 0, 100],
|
|
['Mix', '40', 0, 100],
|
|
['Low Cut', '80', 20, 500],
|
|
],
|
|
reverb: [
|
|
['Decay', '60', 0, 100],
|
|
['Pre-Delay', '30', 0, 200],
|
|
['Damping', '50', 0, 100],
|
|
['Mix', '35', 0, 100],
|
|
],
|
|
};
|
|
|
|
const p = params[blockId] || params.gate;
|
|
const grid = document.querySelector('.param-grid');
|
|
grid.innerHTML = p.map(([name, val, min, max], i) => {
|
|
const id = `p${i}`;
|
|
const pct = Math.round((val / max) * 100);
|
|
return `<div class="param-group">
|
|
<div class="param-label">
|
|
<span class="name">${name}</span>
|
|
<span class="value" id="${id}">${val}</span>
|
|
</div>
|
|
<input type="range" class="touch-slider" min="${min}" max="${max}" value="${val}" oninput="updateSlider(this,'${id}')" style="--pct:${pct}%">
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function updateSlider(el, valId) {
|
|
const val = el.value;
|
|
document.getElementById(valId).textContent = val;
|
|
const min = parseInt(el.min);
|
|
const max = parseInt(el.max);
|
|
const pct = Math.round(((val - min) / (max - min)) * 100);
|
|
el.style.setProperty('--pct', pct + '%');
|
|
}
|
|
|
|
// ── Footswitch Interaction ──
|
|
function toggleBlock(el, blockId) {
|
|
// Toggle bypass
|
|
const chainBlock = document.querySelector(`.chain-block[data-block="${blockId}"]`);
|
|
if (chainBlock) {
|
|
chainBlock.classList.toggle('bypassed');
|
|
const led = chainBlock.querySelector('.block-led');
|
|
const indicator = chainBlock.querySelector('.block-indicator');
|
|
if (chainBlock.classList.contains('bypassed')) {
|
|
led.className = 'block-led off';
|
|
indicator.style.background = 'var(--textDim)';
|
|
el.querySelector('.fs-label').style.color = 'var(--textDim)';
|
|
el.querySelector('.fs-sub').textContent = 'Bypassed';
|
|
el.querySelector('.fs-sub').style.color = 'var(--textDim)';
|
|
el.querySelector('.fs-led').className = 'fs-led off';
|
|
} else {
|
|
led.className = 'block-led on';
|
|
// Restore original indicator color
|
|
const colors = {gate:'var(--green)',drive:'var(--amber)',dist:'var(--red)',mod:'var(--blue)',delay:'#A060E0',reverb:'#60A0E0'};
|
|
indicator.style.background = colors[blockId] || 'var(--green)';
|
|
el.querySelector('.fs-label').style.color = '';
|
|
el.querySelector('.fs-sub').style.color = '';
|
|
// Restore sub text from chain block
|
|
const sub = chainBlock.querySelector('.block-sub');
|
|
el.querySelector('.fs-sub').textContent = sub.textContent;
|
|
el.querySelector('.fs-led').className = 'fs-led green';
|
|
}
|
|
}
|
|
el.classList.toggle('active');
|
|
}
|
|
|
|
function selectBlockFromFS(el, blockId) {
|
|
// Select block in chain
|
|
const chainBlock = document.querySelector(`.chain-block[data-block="${blockId}"]`);
|
|
if (chainBlock) selectBlock(chainBlock);
|
|
}
|
|
|
|
function toggleFS(el) {
|
|
el.classList.toggle('active');
|
|
}
|
|
|
|
function selectPreset(el) {
|
|
document.querySelectorAll('.preset-chip').forEach(c => c.classList.remove('active'));
|
|
el.classList.add('active');
|
|
}
|
|
|
|
// ── VU Meter Animation ──
|
|
(function animateVU() {
|
|
const vu = document.getElementById('vuL');
|
|
const segs = 8;
|
|
vu.innerHTML = '';
|
|
for (let i = 0; i < segs; i++) {
|
|
const seg = document.createElement('div');
|
|
seg.className = 'vu-seg';
|
|
seg.style.height = '100%';
|
|
seg.style.flex = '1';
|
|
vu.appendChild(seg);
|
|
}
|
|
function tick() {
|
|
const level = Math.random();
|
|
const active = Math.floor(level * segs);
|
|
const segs_ = vu.querySelectorAll('.vu-seg');
|
|
segs_.forEach((seg, i) => {
|
|
const on = i < active;
|
|
const color = i >= 6 ? '#C84040' : i >= 4 ? '#E8C030' : '#3AB87A';
|
|
seg.style.background = on ? color : '#2A2A32';
|
|
seg.style.boxShadow = on ? `0 0 4px ${color}` : 'none';
|
|
});
|
|
}
|
|
setInterval(tick, 100);
|
|
})();
|
|
|
|
// ── Auto-scroll to show chain fully ──
|
|
window.addEventListener('load', () => {
|
|
const scroll = document.getElementById('chainScroll');
|
|
if (scroll) scroll.scrollLeft = 120;
|
|
});
|
|
|
|
// ── Touch feedback ──
|
|
document.addEventListener('touchstart', () => {}, {passive: true});
|
|
</script>
|
|
</body>
|
|
</html>
|