sandbox/studio: add faders sub-panel between knobs and seq
Four channel-volume faders matching the patch (KICK/HAT/MEL/MIX), in their own sub-panel with the same brushed-metal treatment as .knobs and .seq. - Track is a deep slot cut into the panel — heavy inset shadow, thin highlight at the bottom edge for chamfer feel. - Side ticks drawn with repeating-linear-gradient (1px on / 6px off). - Cap uses the same metal language as the knob disc: brushed stripes + vertical light gradient + grip line in the middle + 4-edge bevel via inset shadows + drop shadow underneath. - Click anywhere on the track jumps the cap to that value, then dragging continues. Pointer capture on the track itself so the cap follows even outside the bounds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,7 +85,7 @@
|
||||
gap: 12px;
|
||||
}
|
||||
.left-stack { display: grid; grid-template-rows: auto 1fr auto; gap: 8px; min-width: 0; }
|
||||
.right-stack { display: grid; grid-template-rows: auto auto; gap: 8px; min-width: 0; }
|
||||
.right-stack { display: grid; grid-template-rows: auto auto auto; gap: 8px; min-width: 0; }
|
||||
|
||||
/* corner screws — only on the outermost frame */
|
||||
.screw {
|
||||
@@ -235,7 +235,7 @@
|
||||
/* ===================================================================== */
|
||||
|
||||
/* shared brushed-metal panel treatment for sub-panels inside the rack */
|
||||
.knobs, .seq {
|
||||
.knobs, .seq, .faders {
|
||||
border: 1px solid var(--hw-edge);
|
||||
border-radius: 6px;
|
||||
background:
|
||||
@@ -274,6 +274,82 @@
|
||||
.hw-knob { cursor: grab; touch-action: none; }
|
||||
.hw-knob.dragging { cursor: grabbing; }
|
||||
|
||||
/* faders sub-panel */
|
||||
.faders {
|
||||
padding: 10px 10px 14px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
.fader-cell {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: 4px; flex: 1; min-width: 0;
|
||||
}
|
||||
.fader-cell .label {
|
||||
font-size: 8px; letter-spacing: 0.18em; text-transform: uppercase;
|
||||
color: var(--hw-engrave);
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,0.7), 0 -1px 0 rgba(255,220,180,0.04);
|
||||
}
|
||||
/* track is a deep slot cut into the panel */
|
||||
.fd-track {
|
||||
position: relative;
|
||||
width: 5px; height: 78px;
|
||||
background:
|
||||
linear-gradient(180deg, #050402 0%, #0a0808 50%, #060403 100%);
|
||||
border-radius: 3px;
|
||||
box-shadow:
|
||||
inset 0 1px 3px rgba(0,0,0,0.85),
|
||||
inset 0 0 0 1px rgba(0,0,0,0.6),
|
||||
inset 0 -1px 0 rgba(255,220,180,0.05),
|
||||
0 1px 0 rgba(255,220,180,0.04);
|
||||
cursor: pointer; touch-action: none;
|
||||
}
|
||||
/* tick marks on either side of the track */
|
||||
.fd-ticks {
|
||||
position: absolute; top: 4px; bottom: 4px;
|
||||
width: 16px; pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
180deg,
|
||||
rgba(255,220,180,0.20) 0px,
|
||||
rgba(255,220,180,0.20) 1px,
|
||||
transparent 1px,
|
||||
transparent 7px);
|
||||
}
|
||||
.fd-ticks.left { left: -18px; }
|
||||
.fd-ticks.right { right: -18px; }
|
||||
/* the cap — metallic, sits on the track */
|
||||
.fd-cap {
|
||||
position: absolute;
|
||||
left: 50%; transform: translate(-50%, -50%);
|
||||
width: 26px; height: 14px;
|
||||
border-radius: 2px;
|
||||
background:
|
||||
/* horizontal grip line in middle */
|
||||
linear-gradient(180deg,
|
||||
transparent 38%, rgba(0,0,0,0.55) 46%, rgba(0,0,0,0.55) 54%,
|
||||
transparent 62%),
|
||||
/* very fine brushed stripes */
|
||||
repeating-linear-gradient(0deg,
|
||||
rgba(255,250,235,0.04) 0px, rgba(255,250,235,0.04) 1px,
|
||||
rgba(0,0,0,0.05) 1px, rgba(0,0,0,0.05) 2px),
|
||||
/* vertical light gradient */
|
||||
linear-gradient(180deg, #b6b0a4 0%, #807a6e 45%, #3a352e 80%, #1a1612 100%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255,252,240,0.55),
|
||||
inset 0 -1px 0 rgba(0,0,0,0.7),
|
||||
inset 1px 0 0 rgba(255,250,235,0.15),
|
||||
inset -1px 0 0 rgba(0,0,0,0.45),
|
||||
0 2px 3px rgba(0,0,0,0.6);
|
||||
cursor: grab; touch-action: none;
|
||||
}
|
||||
.fd-cap.dragging { cursor: grabbing; }
|
||||
.fader-cell .value {
|
||||
font-size: 10px; font-variant-numeric: tabular-nums;
|
||||
color: var(--hw-fg-hi); letter-spacing: 0.05em;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* sequencer sub-panel — inherits brushed-metal background from .knobs,.seq */
|
||||
.seq {
|
||||
padding: 8px 10px;
|
||||
@@ -380,6 +456,45 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="faders">
|
||||
<div class="fader-cell">
|
||||
<span class="label">kick</span>
|
||||
<div class="fd-track" data-value="0.78">
|
||||
<div class="fd-ticks left"></div>
|
||||
<div class="fd-ticks right"></div>
|
||||
<div class="fd-cap"></div>
|
||||
</div>
|
||||
<span class="value">0.78</span>
|
||||
</div>
|
||||
<div class="fader-cell">
|
||||
<span class="label">hat</span>
|
||||
<div class="fd-track" data-value="0.55">
|
||||
<div class="fd-ticks left"></div>
|
||||
<div class="fd-ticks right"></div>
|
||||
<div class="fd-cap"></div>
|
||||
</div>
|
||||
<span class="value">0.55</span>
|
||||
</div>
|
||||
<div class="fader-cell">
|
||||
<span class="label">mel</span>
|
||||
<div class="fd-track" data-value="0.62">
|
||||
<div class="fd-ticks left"></div>
|
||||
<div class="fd-ticks right"></div>
|
||||
<div class="fd-cap"></div>
|
||||
</div>
|
||||
<span class="value">0.62</span>
|
||||
</div>
|
||||
<div class="fader-cell">
|
||||
<span class="label">mix</span>
|
||||
<div class="fd-track" data-value="0.85">
|
||||
<div class="fd-ticks left"></div>
|
||||
<div class="fd-ticks right"></div>
|
||||
<div class="fd-cap"></div>
|
||||
</div>
|
||||
<span class="value">0.85</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="seq">
|
||||
<div class="seq-row" data-pattern="1000100010001000">
|
||||
<span class="label">kicks</span>
|
||||
@@ -677,6 +792,51 @@ document.querySelectorAll('canvas.hw-knob').forEach((canvas) => {
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// faders — drag the cap or click anywhere on the track to jump
|
||||
// ===========================================================================
|
||||
document.querySelectorAll('.fader-cell').forEach((cell) => {
|
||||
const track = cell.querySelector('.fd-track');
|
||||
const cap = cell.querySelector('.fd-cap');
|
||||
const valueEl = cell.querySelector('.value');
|
||||
let value = parseFloat(track.dataset.value || '0.5');
|
||||
|
||||
function paint() {
|
||||
const trackH = track.clientHeight;
|
||||
cap.style.top = ((1 - value) * trackH) + 'px';
|
||||
if (valueEl) valueEl.textContent = value.toFixed(2);
|
||||
}
|
||||
// wait for layout
|
||||
requestAnimationFrame(paint);
|
||||
|
||||
function valueAt(clientY) {
|
||||
const rect = track.getBoundingClientRect();
|
||||
const norm = 1 - (clientY - rect.top) / rect.height;
|
||||
return Math.max(0, Math.min(1, norm));
|
||||
}
|
||||
|
||||
function startDrag(e, jumpToPointer) {
|
||||
e.preventDefault();
|
||||
cap.classList.add('dragging');
|
||||
track.setPointerCapture(e.pointerId);
|
||||
document.body.style.cursor = 'grabbing';
|
||||
if (jumpToPointer) { value = valueAt(e.clientY); paint(); }
|
||||
const onMove = (ev) => { value = valueAt(ev.clientY); paint(); };
|
||||
const onUp = () => {
|
||||
cap.classList.remove('dragging');
|
||||
document.body.style.cursor = '';
|
||||
track.removeEventListener('pointermove', onMove);
|
||||
track.removeEventListener('pointerup', onUp);
|
||||
track.removeEventListener('pointercancel', onUp);
|
||||
};
|
||||
track.addEventListener('pointermove', onMove);
|
||||
track.addEventListener('pointerup', onUp);
|
||||
track.addEventListener('pointercancel', onUp);
|
||||
}
|
||||
// click on track jumps to that position; drag continues from there
|
||||
track.addEventListener('pointerdown', (e) => startDrag(e, true));
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// step LEDs
|
||||
// ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user