diff --git a/web/sandbox/studio.html b/web/sandbox/studio.html index a73f17c..0d19e3b 100644 --- a/web/sandbox/studio.html +++ b/web/sandbox/studio.html @@ -59,12 +59,13 @@ inset 0 0 0 1px rgba(232, 160, 80, 0.04), 0 1px 0 rgba(255, 220, 180, 0.02), 0 8px 24px rgba(0, 0, 0, 0.55); - padding: 12px; + padding: 14px; display: grid; grid-template-columns: 1fr 220px; - grid-template-rows: auto 1fr; - gap: 10px; + 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; } /* corner screws — only on the outermost frame */ .screw { @@ -93,33 +94,46 @@ /* ===================================================================== */ /* Header bar inside the hardware unit */ /* ===================================================================== */ - .hw-header { - grid-column: 1 / -1; + .top-bar, .bottom-bar { + height: 30px; display: flex; - align-items: center; - gap: 14px; - padding: 4px 26px; - height: 36px; + align-items: stretch; + gap: 10px; + padding: 3px 12px; color: var(--hw-fg-dim); font-size: 10px; letter-spacing: 0.08em; + background: + linear-gradient(180deg, #1f1d1a 0%, #14130f 100%); + border: 1px solid var(--hw-edge); + border-radius: 4px; + box-shadow: + inset 0 1px 0 rgba(255,220,180,0.05), + inset 0 -1px 0 rgba(0,0,0,0.6); } + .top-bar > *, .bottom-bar > * { align-self: center; } + .top-bar .btn-group { align-self: stretch; } + + .btn-group { display: flex; align-items: stretch; height: 100%; } + .btn-group .hw-btn { border-radius: 0; border-right-width: 0; } + .btn-group .hw-btn:first-child { border-radius: 3px 0 0 3px; } + .btn-group .hw-btn:last-child { border-radius: 0 3px 3px 0; border-right-width: 1px; } + .hw-btn { background: linear-gradient(180deg, #2c261f 0%, #14110d 100%); border: 1px solid var(--hw-edge); - border-radius: 12px; color: var(--hw-fg-hi); - padding: 4px 12px; + padding: 0 14px; font-family: inherit; font-size: 10px; cursor: pointer; - letter-spacing: 0.1em; + letter-spacing: 0.12em; text-transform: uppercase; box-shadow: - inset 0 1px 0 rgba(255,220,180,0.08), + inset 0 1px 0 rgba(255,220,180,0.10), inset 0 -1px 0 rgba(0,0,0,0.55), 0 1px 1px rgba(0,0,0,0.4); - display: inline-flex; align-items: center; gap: 5px; + display: inline-flex; align-items: center; gap: 6px; transition: color 80ms; } .hw-btn:hover { color: var(--hw-amber); } @@ -148,16 +162,19 @@ /* ===================================================================== */ /* Code "screen" — left, big */ /* ===================================================================== */ + /* the code area is *recessed* into the rack — no separate bordered panel. + Achieved with deep inset shadow + slightly darker bg than the rack. */ .screen { background: var(--hw-screen); - border: 1px solid var(--hw-edge); - border-radius: 6px; - padding: 14px 18px 10px; + border-radius: 4px; + padding: 12px 18px; box-shadow: - inset 0 0 0 1px rgba(232,160,80,0.04), - inset 0 1px 8px rgba(0,0,0,0.5); + inset 0 2px 4px rgba(0,0,0,0.7), + inset 0 -1px 0 rgba(255,220,180,0.03), + inset 0 0 0 1px var(--hw-edge), + inset 0 6px 14px rgba(0,0,0,0.5); display: flex; flex-direction: column; - min-height: 320px; + min-height: 300px; overflow: hidden; } .glass { @@ -174,12 +191,19 @@ .syn-op { color: var(--hw-syn-op); } .syn-arrow { color: var(--hw-amber); } - .status-line { - margin-top: 8px; + .running-label { color: var(--hw-amber); - font-size: 11px; - letter-spacing: 0.16em; - text-shadow: 0 0 6px var(--hw-amber-glow); + font-size: 10px; + letter-spacing: 0.18em; + text-shadow: 0 0 5px var(--hw-amber-glow); + } + .running-dot { + display: inline-block; + width: 6px; height: 6px; border-radius: 50%; + background: var(--hw-amber); + box-shadow: 0 0 5px var(--hw-amber-glow); + margin-right: 6px; vertical-align: middle; + animation: led-pulse 2.2s ease-in-out infinite; } .blink { animation: blink 1.1s step-end infinite; } @keyframes blink { 50% { opacity: 0; } } @@ -187,10 +211,6 @@ /* ===================================================================== */ /* Right column */ /* ===================================================================== */ - .right-col { - display: flex; flex-direction: column; gap: 10px; - min-width: 0; - } /* knobs sub-panel inside hardware */ .knobs { @@ -284,24 +304,32 @@ - -
- - - 48 KHZ - TAPS: 14 - CPU: 9% - CODE · SINTH + +
+ +
+
+ + +
+ 48 KHZ + TAPS: 14 + CPU: 9% + CODE · SINTH +
+ +
+
+
+ +
+ RUNNING_ +
+
- -
-
-
> RUNNING_
-
- -
+
@@ -406,7 +434,7 @@ for (const line of PATCH) { // knob — restrained: dark gradient + thin amber arc + single tickmark. // No knurled ridges, no brushed metal. Closer to the reference image. // =========================================================================== -const KNOB_SIZE = 50; +const KNOB_SIZE = 56; function setupCanvas(canvas, size) { const dpr = window.devicePixelRatio || 1; canvas.style.width = size + 'px'; @@ -420,70 +448,94 @@ function setupCanvas(canvas, size) { function drawKnob(ctx, size, norm) { const cx = size / 2, cy = size / 2 + 1; - const dialR = size * 0.40; + const outerR = size * 0.46; // chrome-bezel outer edge + const ringW = size * 0.075; // metallic ring width (~4px @ 56) + const dialR = outerR - ringW; // dark dial radius + const ringMid = (outerR + dialR) / 2; ctx.clearRect(0, 0, size, size); - // soft outer drop shadow under the knob (sits ON the panel) + // (1) drop shadow under the whole knob — gives it weight on the panel ctx.save(); - ctx.shadowColor = 'rgba(0,0,0,0.6)'; + ctx.shadowColor = 'rgba(0,0,0,0.7)'; ctx.shadowBlur = 6; ctx.shadowOffsetY = 2; ctx.fillStyle = '#000'; ctx.beginPath(); - ctx.arc(cx, cy, dialR, 0, Math.PI * 2); + ctx.arc(cx, cy, outerR + 0.5, 0, Math.PI * 2); ctx.fill(); ctx.restore(); - // value arc — amber, only the active portion is drawn (no background track) + // (2) amber value arc OUTSIDE the bezel (faint track + bright active portion) const startA = Math.PI * 0.78, endA = Math.PI * 2.22; ctx.lineCap = 'round'; - ctx.lineWidth = 1.6; - ctx.strokeStyle = 'rgba(232,160,80,0.10)'; // very faint full track + ctx.lineWidth = 1.5; + ctx.strokeStyle = 'rgba(232,160,80,0.10)'; ctx.beginPath(); - ctx.arc(cx, cy, dialR + 2.5, startA, endA); + ctx.arc(cx, cy, outerR + 3, startA, endA); ctx.stroke(); ctx.strokeStyle = '#e8a050'; ctx.shadowBlur = 3; - ctx.shadowColor = 'rgba(232,160,80,0.5)'; + ctx.shadowColor = 'rgba(232,160,80,0.55)'; ctx.beginPath(); - ctx.arc(cx, cy, dialR + 2.5, startA, startA + norm * (endA - startA)); + ctx.arc(cx, cy, outerR + 3, startA, startA + norm * (endA - startA)); ctx.stroke(); ctx.shadowBlur = 0; - // dial body — neutral charcoal black, NOT warm brown - const grad = ctx.createRadialGradient( + // (3) METALLIC RING — vertical gradient (light top, dark bottom). + // This is the "chrome bezel" the reference shows. + const ringGrad = ctx.createLinearGradient(0, cy - outerR, 0, cy + outerR); + ringGrad.addColorStop(0, '#c4beb2'); + ringGrad.addColorStop(0.30, '#807a72'); + ringGrad.addColorStop(0.55, '#3a3631'); + ringGrad.addColorStop(0.85, '#1c1a17'); + ringGrad.addColorStop(1, '#0a0908'); + ctx.strokeStyle = ringGrad; + ctx.lineWidth = ringW; + ctx.beginPath(); + ctx.arc(cx, cy, ringMid, 0, Math.PI * 2); + ctx.stroke(); + + // (4) thin bright highlight on the very top of the bezel + ctx.strokeStyle = 'rgba(255,255,255,0.22)'; + ctx.lineWidth = 0.6; + ctx.beginPath(); + ctx.arc(cx, cy, outerR - 0.3, Math.PI * 1.18, Math.PI * 1.82); + ctx.stroke(); + + // (5) inner shadow where the dial sinks below the bezel + ctx.strokeStyle = 'rgba(0,0,0,0.7)'; + ctx.lineWidth = 0.8; + ctx.beginPath(); + ctx.arc(cx, cy, dialR + 0.4, 0, Math.PI * 2); + ctx.stroke(); + + // (6) dial body — pure charcoal/black, NO warm tint + const dial = ctx.createRadialGradient( cx - dialR * 0.35, cy - dialR * 0.5, 0, cx, cy, dialR * 1.1 ); - grad.addColorStop(0, '#2a2a28'); // top-left highlight - grad.addColorStop(0.5, '#141312'); - grad.addColorStop(1, '#040404'); - ctx.fillStyle = grad; + dial.addColorStop(0, '#2a2a28'); + dial.addColorStop(0.5, '#141312'); + dial.addColorStop(1, '#040404'); + ctx.fillStyle = dial; ctx.beginPath(); ctx.arc(cx, cy, dialR, 0, Math.PI * 2); ctx.fill(); - // outer dark ring (separates dial from panel) - ctx.strokeStyle = 'rgba(0,0,0,0.85)'; - ctx.lineWidth = 1; + // (7) faint glossy crescent on the dial's upper half + ctx.strokeStyle = 'rgba(255,255,255,0.07)'; + ctx.lineWidth = 0.7; ctx.beginPath(); - ctx.arc(cx, cy, dialR, 0, Math.PI * 2); + ctx.arc(cx, cy, dialR - 1, Math.PI * 1.18, Math.PI * 1.82); ctx.stroke(); - // glossy top crescent — short bright arc on the upper half - ctx.strokeStyle = 'rgba(255,255,255,0.08)'; - ctx.lineWidth = 0.8; - ctx.beginPath(); - ctx.arc(cx, cy, dialR - 1.0, Math.PI * 1.18, Math.PI * 1.82); - ctx.stroke(); - - // indicator: short cream tick from edge inward + // (8) indicator tick — cream, from mid to dial edge const ang = startA + norm * (endA - startA); ctx.strokeStyle = 'rgba(245,235,215,0.92)'; - ctx.lineWidth = 1.6; + ctx.lineWidth = 1.7; ctx.lineCap = 'round'; ctx.beginPath(); - ctx.moveTo(cx + Math.cos(ang) * dialR * 0.55, cy + Math.sin(ang) * dialR * 0.55); + ctx.moveTo(cx + Math.cos(ang) * dialR * 0.5, cy + Math.sin(ang) * dialR * 0.5); ctx.lineTo(cx + Math.cos(ang) * dialR * 0.88, cy + Math.sin(ang) * dialR * 0.88); ctx.stroke(); }