Back to CSS 3D Modular Synth Controls CSS + JS
Share
HTML
<section class="cd-syn" aria-label="Modular synth control panel demo">
  <div class="card">
    <div class="panel">

      <div class="readout">
        <div class="readout-item">
          <div class="readout-key">Mode</div>
          <div class="readout-val" data-cd-syn-mode>STUDIO</div>
        </div>
        <div class="readout-item">
          <div class="readout-key">Master</div>
          <div class="readout-val" data-cd-syn-master>87</div>
        </div>
        <div class="readout-item">
          <div class="readout-key">BPM</div>
          <div class="readout-val" data-cd-syn-bpm>124</div>
        </div>
        <div class="readout-item">
          <div class="readout-key">Status</div>
          <div class="readout-val live">LIVE</div>
        </div>
      </div>

      <div class="main-layout">
        <div class="left-controls">

          <div class="section">
            <div class="section-label">Transport</div>
            <div class="btn-row">
              <button class="push-btn" type="button" data-action="play">
                <span class="push-btn-body"><span class="btn-led"></span><span class="push-btn-label">Play</span></span>
              </button>
              <button class="push-btn active" type="button" data-action="rec">
                <span class="push-btn-body"><span class="btn-led"></span><span class="push-btn-label">Rec</span></span>
              </button>
              <button class="push-btn" type="button" data-action="stop">
                <span class="push-btn-body"><span class="btn-led"></span><span class="push-btn-label">Stop</span></span>
              </button>
              <button class="push-btn active" type="button" data-action="loop">
                <span class="push-btn-body"><span class="btn-led"></span><span class="push-btn-label">Loop</span></span>
              </button>
              <button class="push-btn" type="button" data-action="fx">
                <span class="push-btn-body"><span class="btn-led"></span><span class="push-btn-label">FX</span></span>
              </button>
            </div>
          </div>

          <div class="section">
            <div class="section-label">Routing</div>
            <div class="toggle-row">
              <div class="toggle-unit">
                <button class="toggle-sw on" type="button" data-toggle="mono" style="--led-col:#00e8cc">
                  <span class="toggle-thumb"></span>
                  <span class="toggle-sw-led"></span>
                </button>
                <span class="toggle-label">Mono</span>
              </div>
              <div class="toggle-unit">
                <button class="toggle-sw" type="button" data-toggle="reverb" style="--led-col:#c060ff">
                  <span class="toggle-thumb"></span>
                  <span class="toggle-sw-led"></span>
                </button>
                <span class="toggle-label">Reverb</span>
              </div>
              <div class="toggle-unit">
                <button class="toggle-sw on" type="button" data-toggle="eq" style="--led-col:#ffaa00">
                  <span class="toggle-thumb"></span>
                  <span class="toggle-sw-led"></span>
                </button>
                <span class="toggle-label">EQ</span>
              </div>
              <div class="toggle-unit">
                <button class="toggle-sw" type="button" data-toggle="comp" style="--led-col:#ff3840">
                  <span class="toggle-thumb"></span>
                  <span class="toggle-sw-led"></span>
                </button>
                <span class="toggle-label">Comp</span>
              </div>
              <div class="toggle-unit">
                <button class="toggle-sw on" type="button" data-toggle="gate" style="--led-col:#00e060">
                  <span class="toggle-thumb"></span>
                  <span class="toggle-sw-led"></span>
                </button>
                <span class="toggle-label">Gate</span>
              </div>
            </div>
          </div>

          <div class="section">
            <div class="section-label">Channel Mix</div>
            <div class="fader-row" data-cd-syn-faderrow></div>
          </div>

        </div>

        <div class="right-controls">
          <div class="section-label rl">Level</div>
          <div class="vu-row" data-cd-syn-vurow></div>
        </div>

      </div>

    </div>
  </div>
</section>
CSS
/* ─── 06 Modular Synth Controls — audio control panel ──────── */
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&display=swap');

.cd-syn {
  --cd-syn-panel: #1a1c22;
  --cd-syn-inset: #14161b;
  --cd-syn-text-dim: rgba(120,140,180,0.4);
  --cd-syn-cyan: #00e8cc;
  --cd-syn-amber: #ffaa00;
  --cd-syn-red: #ff3840;
  --cd-syn-green: #00e060;
  --cd-syn-purple: #c060ff;
  --cd-syn-bg: #11131a;

  position: relative;
  width: 100%;
  min-height: 580px;
  background: var(--cd-syn-bg);
  font-family: 'Share Tech Mono', ui-monospace, monospace;
  overflow: hidden;
  box-sizing: border-box;
}

.cd-syn *,
.cd-syn *::before,
.cd-syn *::after { box-sizing: border-box; margin: 0; padding: 0; }

.cd-syn .card {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
}

.cd-syn .panel {
  width: 100%;
  max-width: 640px;
  background: var(--cd-syn-panel);
  border-radius: 16px;
  padding: 28px;
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.05),
    0 2px 0 rgba(255,255,255,0.04),
    0 4px 24px rgba(0,0,0,0.6),
    0 1px 0 rgba(255,255,255,0.06) inset,
    0 -1px 0 rgba(0,0,0,0.4) inset;
  position: relative;
}
.cd-syn .panel::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 16px;
  background-image: repeating-linear-gradient(90deg, transparent 0px, transparent 2px, rgba(255,255,255,0.007) 2px, rgba(255,255,255,0.007) 3px);
  pointer-events: none;
}

.cd-syn .readout {
  background: var(--cd-syn-inset);
  border-radius: 8px;
  padding: 11px 16px;
  box-shadow: inset 0 2px 6px rgba(0,0,0,0.5);
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 18px;
  gap: 8px;
}
.cd-syn .readout-item { text-align: center; }
.cd-syn .readout-key {
  font-size: 7px;
  letter-spacing: 3px;
  color: var(--cd-syn-text-dim);
  text-transform: uppercase;
  margin-bottom: 3px;
}
.cd-syn .readout-val {
  font-family: 'Share Tech Mono', monospace;
  font-size: 13px;
  letter-spacing: 2px;
  color: var(--cd-syn-cyan);
  text-shadow: 0 0 10px rgba(0,232,204,0.5);
}
.cd-syn .readout-val.live {
  color: var(--cd-syn-green);
  text-shadow: 0 0 10px rgba(0,224,96,0.5);
}

.cd-syn .section { margin-bottom: 22px; }
.cd-syn .section:last-child { margin-bottom: 0; }
.cd-syn .section-label {
  font-family: 'Share Tech Mono', monospace;
  font-size: 9px;
  letter-spacing: 3px;
  color: var(--cd-syn-text-dim);
  text-transform: uppercase;
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.cd-syn .section-label::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(255,255,255,0.06);
}
.cd-syn .section-label.rl { margin-bottom: 10px; }

.cd-syn .main-layout {
  display: flex;
  gap: 22px;
  align-items: flex-start;
}
.cd-syn .left-controls { flex: 1; }
.cd-syn .right-controls { display: flex; flex-direction: column; gap: 4px; }

/* Push buttons */
.cd-syn .btn-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.cd-syn .push-btn {
  position: relative;
  cursor: pointer;
  border: none;
  background: none;
  padding: 0;
  outline: none;
}
.cd-syn .push-btn-body {
  width: 72px;
  height: 50px;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  position: relative;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
  background: var(--cd-syn-btn-color);
  box-shadow:
    0 6px 0 var(--cd-syn-btn-shadow),
    0 7px 12px rgba(0,0,0,0.5),
    inset 0 1px 0 rgba(255,255,255,0.18),
    inset 0 -1px 0 rgba(0,0,0,0.3);
}
.cd-syn .push-btn:active .push-btn-body,
.cd-syn .push-btn.pressed .push-btn-body {
  transform: translateY(4px);
  box-shadow:
    0 2px 0 var(--cd-syn-btn-shadow),
    0 3px 6px rgba(0,0,0,0.5),
    inset 0 2px 4px rgba(0,0,0,0.3),
    inset 0 1px 0 rgba(255,255,255,0.08);
}
.cd-syn .push-btn-label {
  font-family: 'Share Tech Mono', monospace;
  font-size: 8px;
  letter-spacing: 1.5px;
  color: rgba(255,255,255,0.85);
  text-transform: uppercase;
}
.cd-syn .btn-led {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(0,0,0,0.4);
  transition: background 0.2s, box-shadow 0.2s;
}
.cd-syn .push-btn.active .btn-led {
  background: var(--cd-syn-led-color);
  box-shadow: 0 0 6px var(--cd-syn-led-color), 0 0 14px var(--cd-syn-led-color);
}
.cd-syn .push-btn:nth-child(1) { --cd-syn-btn-color: #1a2838; --cd-syn-btn-shadow: #0e1820; --cd-syn-led-color: #00e8cc; }
.cd-syn .push-btn:nth-child(2) { --cd-syn-btn-color: #281a10; --cd-syn-btn-shadow: #180e08; --cd-syn-led-color: #ffaa00; }
.cd-syn .push-btn:nth-child(3) { --cd-syn-btn-color: #281818; --cd-syn-btn-shadow: #180808; --cd-syn-led-color: #ff3840; }
.cd-syn .push-btn:nth-child(4) { --cd-syn-btn-color: #182818; --cd-syn-btn-shadow: #081808; --cd-syn-led-color: #00e060; }
.cd-syn .push-btn:nth-child(5) { --cd-syn-btn-color: #1e1a30; --cd-syn-btn-shadow: #100e20; --cd-syn-led-color: #c060ff; }

/* Toggles */
.cd-syn .toggle-row { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
.cd-syn .toggle-unit { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.cd-syn .toggle-label {
  font-size: 8px;
  letter-spacing: 2px;
  color: var(--cd-syn-text-dim);
  text-transform: uppercase;
}
.cd-syn .toggle-sw {
  position: relative;
  width: 60px;
  height: 28px;
  cursor: pointer;
  border-radius: 6px;
  background: var(--cd-syn-inset);
  border: none;
  padding: 3px;
  box-shadow:
    inset 0 2px 6px rgba(0,0,0,0.6),
    inset 0 1px 2px rgba(0,0,0,0.8),
    0 1px 0 rgba(255,255,255,0.06);
}
.cd-syn .toggle-thumb {
  display: block;
  width: 26px;
  height: 22px;
  border-radius: 4px;
  background: linear-gradient(180deg, #2e3248 0%, #22263a 100%);
  box-shadow:
    0 3px 0 #12141e,
    0 4px 8px rgba(0,0,0,0.5),
    inset 0 1px 0 rgba(255,255,255,0.12);
  transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.cd-syn .toggle-sw.on .toggle-thumb { transform: translateX(28px); }
.cd-syn .toggle-sw-led {
  position: absolute;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  top: 50%;
  right: 7px;
  transform: translateY(-50%);
  background: rgba(0,0,0,0.5);
  transition: background 0.2s, box-shadow 0.2s;
}
.cd-syn .toggle-sw.on .toggle-sw-led {
  background: var(--led-col);
  box-shadow: 0 0 6px var(--led-col);
}

/* Faders */
.cd-syn .fader-row { display: flex; gap: 18px; align-items: flex-end; flex-wrap: wrap; }
.cd-syn .fader-unit { display: flex; flex-direction: column; align-items: center; gap: 7px; }
.cd-syn .fader-track-wrap {
  width: 28px;
  height: 120px;
  background: var(--cd-syn-inset);
  border-radius: 6px;
  box-shadow:
    inset 0 2px 6px rgba(0,0,0,0.6),
    inset 0 1px 3px rgba(0,0,0,0.8),
    0 1px 0 rgba(255,255,255,0.05);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cd-syn .fader-track-wrap::before {
  content: '';
  position: absolute;
  left: 4px;
  right: 4px;
  top: 10px;
  bottom: 10px;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,0.06) 0px, rgba(255,255,255,0.06) 1px, transparent 1px, transparent 10px);
  pointer-events: none;
}
.cd-syn .fader-thumb {
  width: 24px;
  height: 20px;
  border-radius: 4px;
  background: linear-gradient(180deg, #3a3f58 0%, #2a2e44 100%);
  box-shadow:
    0 3px 0 #141620,
    0 4px 8px rgba(0,0,0,0.5),
    inset 0 1px 0 rgba(255,255,255,0.15);
  position: absolute;
  cursor: ns-resize;
  user-select: none;
  touch-action: none;
  z-index: 2;
}
.cd-syn .fader-fill {
  position: absolute;
  bottom: 10px;
  left: 10px;
  right: 10px;
  border-radius: 2px;
  background: linear-gradient(to top, var(--cd-syn-fader-color), transparent);
  opacity: 0.5;
  pointer-events: none;
}
.cd-syn .fader-label {
  font-size: 8px;
  letter-spacing: 2px;
  color: var(--cd-syn-text-dim);
  text-transform: uppercase;
}
.cd-syn .fader-value {
  font-size: 10px;
  font-family: 'Share Tech Mono', monospace;
  letter-spacing: 1px;
  min-height: 16px;
  color: var(--cd-syn-fader-color);
}

/* VU */
.cd-syn .vu-row { display: flex; gap: 9px; align-items: flex-end; margin-left: 8px; }
.cd-syn .vu-bar-wrap { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.cd-syn .vu-track {
  width: 13px;
  height: 120px;
  background: var(--cd-syn-inset);
  border-radius: 4px;
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.6);
  display: flex;
  flex-direction: column-reverse;
  overflow: hidden;
  gap: 1px;
  padding: 3px;
}
.cd-syn .vu-seg {
  width: 100%;
  height: 7px;
  border-radius: 2px;
  background: var(--cd-syn-seg-color);
  opacity: 0.15;
  transition: opacity 0.08s;
  flex-shrink: 0;
}
.cd-syn .vu-seg.lit { opacity: 1; box-shadow: 0 0 6px var(--cd-syn-seg-color); }
.cd-syn .vu-label {
  font-size: 8px;
  letter-spacing: 2px;
  color: var(--cd-syn-text-dim);
  text-transform: uppercase;
}

@media (max-width: 720px) {
  .cd-syn .main-layout { flex-direction: column; }
  .cd-syn .right-controls { width: 100%; }
  .cd-syn .vu-row { justify-content: flex-start; margin-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .cd-syn .toggle-thumb { transition: none !important; }
  .cd-syn .vu-seg { transition: none !important; }
}
JS
(() => {
  const root = document.querySelector('.cd-syn');
  if (!root) return;

  // Push buttons
  const modeReadout = root.querySelector('[data-cd-syn-mode]');
  root.querySelectorAll('.push-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      btn.classList.toggle('active');
      const action = btn.dataset.action;
      const modes = { play: 'PLAY', rec: 'RECORD', stop: 'STOPPED', loop: 'LOOP', fx: 'FX ON' };
      if (modes[action] && modeReadout) modeReadout.textContent = modes[action];
    });
  });

  // Toggles
  root.querySelectorAll('.toggle-sw').forEach(sw => {
    sw.addEventListener('click', () => sw.classList.toggle('on'));
  });

  // Faders
  const faderRow = root.querySelector('[data-cd-syn-faderrow]');
  const masterReadout = root.querySelector('[data-cd-syn-master]');
  const faderData = [
    { label: 'CH.1', color: 'var(--cd-syn-cyan)',   val: 87 },
    { label: 'CH.2', color: 'var(--cd-syn-cyan)',   val: 62 },
    { label: 'CH.3', color: 'var(--cd-syn-amber)',  val: 74 },
    { label: 'CH.4', color: 'var(--cd-syn-amber)',  val: 90 },
    { label: 'SUB',  color: 'var(--cd-syn-purple)', val: 55 },
    { label: 'MST',  color: 'var(--cd-syn-green)',  val: 87 },
  ];
  const TRACK_H = 120, PADDING = 10, THUMB_H = 20;
  const TRAVEL = TRACK_H - PADDING * 2 - THUMB_H;

  faderData.forEach((fd, fi) => {
    const unit = document.createElement('div');
    unit.className = 'fader-unit';
    unit.innerHTML = `
      <div class="fader-value" style="--cd-syn-fader-color:${fd.color}">${fd.val}</div>
      <div class="fader-track-wrap">
        <div class="fader-fill" style="--cd-syn-fader-color:${fd.color}"></div>
        <div class="fader-thumb"></div>
      </div>
      <div class="fader-label">${fd.label}</div>
    `;
    faderRow.appendChild(unit);

    const thumb = unit.querySelector('.fader-thumb');
    const fill = unit.querySelector('.fader-fill');
    const valEl = unit.querySelector('.fader-value');

    function setVal(v) {
      v = Math.max(0, Math.min(100, v));
      fd.val = Math.round(v);
      const pct = v / 100;
      thumb.style.top = (PADDING + TRAVEL * (1 - pct)) + 'px';
      fill.style.height = (TRAVEL * pct + THUMB_H / 2) + 'px';
      valEl.textContent = fd.val;
      if (fi === 5 && masterReadout) masterReadout.textContent = fd.val;
    }
    setVal(fd.val);

    let dragging = false, startY = 0, startVal = 0;
    thumb.addEventListener('mousedown', e => {
      dragging = true; startY = e.clientY; startVal = fd.val; e.preventDefault();
    });
    root.addEventListener('mousemove', e => {
      if (!dragging) return;
      setVal(startVal + ((startY - e.clientY) / TRAVEL) * 100);
    });
    root.addEventListener('mouseup', () => { dragging = false; });
    root.addEventListener('mouseleave', () => { dragging = false; });
    thumb.addEventListener('touchstart', e => {
      dragging = true; startY = e.touches[0].clientY; startVal = fd.val;
    }, { passive: true });
    root.addEventListener('touchmove', e => {
      if (!dragging) return;
      setVal(startVal + ((startY - e.touches[0].clientY) / TRAVEL) * 100);
    }, { passive: true });
    root.addEventListener('touchend', () => { dragging = false; });
  });

  // VU meters
  const vuRow = root.querySelector('[data-cd-syn-vurow]');
  const vuData = [
    { label: 'L', color: '#00e8cc' },
    { label: 'R', color: '#00e8cc' },
  ];
  const N_SEGS = 12;
  const vuIntervals = [];

  vuData.forEach((vu, vi) => {
    const wrap = document.createElement('div');
    wrap.className = 'vu-bar-wrap';
    const track = document.createElement('div');
    track.className = 'vu-track';
    for (let s = 0; s < N_SEGS; s++) {
      const seg = document.createElement('div');
      seg.className = 'vu-seg';
      const segColor = s >= N_SEGS - 2 ? '#ff3840' : s >= N_SEGS - 4 ? '#ffaa00' : vu.color;
      seg.style.setProperty('--cd-syn-seg-color', segColor);
      track.appendChild(seg);
    }
    const lbl = document.createElement('div');
    lbl.className = 'vu-label';
    lbl.textContent = vu.label;
    wrap.appendChild(track);
    wrap.appendChild(lbl);
    vuRow.appendChild(wrap);

    const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (prefersReduced) return;

    function animateVU() {
      const segs = track.querySelectorAll('.vu-seg');
      const base = 6;
      const variation = Math.random() > 0.9 ? Math.floor(Math.random() * 3) : 0;
      const level = base + variation;
      segs.forEach((seg, si) => {
        seg.classList.toggle('lit', si < level);
      });
      vuIntervals[vi] = setTimeout(animateVU, 80 + Math.random() * 60);
    }
    setTimeout(animateVU, vi * 40);
  });

  // BPM blink
  const bpmReadout = root.querySelector('[data-cd-syn-bpm]');
  if (bpmReadout) {
    setInterval(() => {
      bpmReadout.textContent = 124 + (Math.random() > 0.96 ? 1 : 0);
    }, 500);
  }
})();