diff --git a/web/sandbox/studio.html b/web/sandbox/studio.html
index 2fe8f66..16a931b 100644
--- a/web/sandbox/studio.html
+++ b/web/sandbox/studio.html
@@ -491,69 +491,117 @@ function drawKnob(ctx, size, norm) {
ctx.arc(cx, cy, totalR - rimW / 2, 0, Math.PI * 2);
ctx.stroke();
- // (4) BIG METALLIC DISC — fills most of the inside (the "circulito metalico
- // que ocupa casi todo"). Radial gradient, brushed-metal feel.
- const disc = ctx.createRadialGradient(
- cx, cy - discR * 0.25, 0,
- cx, cy + discR * 0.4, discR * 1.4
- );
- disc.addColorStop(0, '#b6b0a6'); // lit upper-center
- disc.addColorStop(0.45, '#8a847a');
- disc.addColorStop(0.85, '#46423d');
- disc.addColorStop(1, '#2a2722'); // bottom dark
- ctx.fillStyle = disc;
+ // (4a) METAL — base radial gradient (matte foundation, slightly dark at edge)
+ const baseGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, discR);
+ baseGrad.addColorStop(0, '#787268');
+ baseGrad.addColorStop(0.7, '#5a544c');
+ baseGrad.addColorStop(1, '#36322d');
+ ctx.fillStyle = baseGrad;
ctx.beginPath();
ctx.arc(cx, cy, discR, 0, Math.PI * 2);
ctx.fill();
- // (5) brushed-metal radial striations (very subtle, deterministic)
+ // (4b) CONCENTRIC BRUSHING — lathe-turned aluminium look (signature on
+ // Moog/Roland synths). Pseudo-random alpha per ring, alternating
+ // bright/dark bands, deterministic so the rings don't shimmer.
ctx.save();
ctx.beginPath();
- ctx.arc(cx, cy, discR - 0.5, 0, Math.PI * 2);
+ ctx.arc(cx, cy, discR - 0.3, 0, Math.PI * 2);
ctx.clip();
- for (let i = 0; i < 96; i++) {
- const a = (i / 96) * Math.PI * 2;
- const alpha = 0.025 + ((i * 9301 + 49297) % 233) / 233 * 0.04;
- ctx.strokeStyle = `rgba(255, 240, 220, ${alpha.toFixed(3)})`;
+ for (let r = discR; r > 0.5; r -= 0.55) {
+ const seed = Math.floor(r * 17.31);
+ const n = ((seed * 9301 + 49297) % 233) / 233;
+ const a = 0.04 + n * 0.10;
+ ctx.strokeStyle = n > 0.55
+ ? `rgba(255, 245, 225, ${a.toFixed(3)})`
+ : `rgba(10, 8, 6, ${(a * 0.85).toFixed(3)})`;
ctx.lineWidth = 0.5;
ctx.beginPath();
- ctx.moveTo(cx, cy);
- ctx.lineTo(cx + Math.cos(a) * discR, cy + Math.sin(a) * discR);
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.stroke();
}
ctx.restore();
- // (6) thin shadow line where disc meets rim (depth)
- ctx.strokeStyle = 'rgba(0,0,0,0.55)';
+ // (4c) VERTICAL LIGHTING — light from above. Strong on top, dark below.
+ const vert = ctx.createLinearGradient(0, cy - discR, 0, cy + discR);
+ vert.addColorStop(0, 'rgba(255, 248, 230, 0.32)');
+ vert.addColorStop(0.40, 'rgba(255, 248, 230, 0.04)');
+ vert.addColorStop(0.55, 'rgba(0, 0, 0, 0.05)');
+ vert.addColorStop(1, 'rgba(0, 0, 0, 0.45)');
+ ctx.fillStyle = vert;
+ ctx.beginPath();
+ ctx.arc(cx, cy, discR, 0, Math.PI * 2);
+ ctx.fill();
+
+ // (4d) HORIZONTAL SPECULAR BAND — the bright reflection of the light
+ // source running across the upper part of the disc.
+ const band = ctx.createLinearGradient(0, cy - discR * 0.65, 0, cy - discR * 0.05);
+ band.addColorStop(0, 'rgba(255, 252, 240, 0)');
+ band.addColorStop(0.5, 'rgba(255, 252, 240, 0.42)');
+ band.addColorStop(1, 'rgba(255, 252, 240, 0)');
+ ctx.fillStyle = band;
+ ctx.beginPath();
+ ctx.arc(cx, cy, discR, 0, Math.PI * 2);
+ ctx.fill();
+
+ // (4e) RIM LIGHT FROM BELOW — subtle glow on the bottom edge (typical of
+ // studio gear photographed under softboxes).
+ const rimLight = ctx.createLinearGradient(0, cy + discR * 0.55, 0, cy + discR);
+ rimLight.addColorStop(0, 'rgba(255, 230, 200, 0)');
+ rimLight.addColorStop(1, 'rgba(255, 230, 200, 0.14)');
+ ctx.fillStyle = rimLight;
+ ctx.beginPath();
+ ctx.arc(cx, cy, discR, 0, Math.PI * 2);
+ ctx.fill();
+
+ // (5) BEVEL — bright crescent on the upper edge
+ ctx.strokeStyle = 'rgba(255, 252, 240, 0.55)';
+ ctx.lineWidth = 0.9;
+ ctx.beginPath();
+ ctx.arc(cx, cy, discR - 0.5, Math.PI * 1.10, Math.PI * 1.90);
+ ctx.stroke();
+
+ // (5b) BEVEL — dark crescent on the lower edge (shadow side of the bevel)
+ ctx.strokeStyle = 'rgba(0, 0, 0, 0.50)';
+ ctx.lineWidth = 0.9;
+ ctx.beginPath();
+ ctx.arc(cx, cy, discR - 0.5, Math.PI * 0.10, Math.PI * 0.90);
+ ctx.stroke();
+
+ // (6) inset shadow line where disc meets rim
+ ctx.strokeStyle = 'rgba(0, 0, 0, 0.65)';
ctx.lineWidth = 0.7;
ctx.beginPath();
- ctx.arc(cx, cy, discR + 0.4, 0, Math.PI * 2);
+ ctx.arc(cx, cy, discR + 0.3, 0, Math.PI * 2);
ctx.stroke();
- // (7) bright highlight crescent on the top edge of the disc
- ctx.strokeStyle = 'rgba(255,255,255,0.30)';
- ctx.lineWidth = 0.8;
- ctx.beginPath();
- ctx.arc(cx, cy, discR - 0.5, Math.PI * 1.18, Math.PI * 1.82);
- ctx.stroke();
-
- // (8) small DARK NOTCH indicator on the disc, rotates with value
+ // (7) INDICATOR — small engraved dark notch on the disc, rotates with value.
+ // Drawn as a tapered triangle with a subtle bottom highlight to look
+ // like it's incised into the metal rather than printed on top.
const ang = startA + norm * (endA - startA);
const tipR = discR * 0.86;
- const baseR = discR * 0.55;
+ const baseR = discR * 0.50;
const perp = ang + Math.PI / 2;
- const half = size * 0.028;
+ const half = size * 0.026;
const tipX = cx + Math.cos(ang) * tipR;
const tipY = cy + Math.sin(ang) * tipR;
const bX = cx + Math.cos(ang) * baseR;
const bY = cy + Math.sin(ang) * baseR;
- ctx.fillStyle = 'rgba(8,7,6,0.95)';
+ // dark fill (the engraved groove)
+ ctx.fillStyle = 'rgba(6, 5, 4, 0.92)';
ctx.beginPath();
ctx.moveTo(tipX, tipY);
ctx.lineTo(bX + Math.cos(perp) * half, bY + Math.sin(perp) * half);
ctx.lineTo(bX - Math.cos(perp) * half, bY - Math.sin(perp) * half);
ctx.closePath();
ctx.fill();
+ // tiny bright lower lip (light catches the inner edge of the groove)
+ ctx.strokeStyle = 'rgba(255, 250, 230, 0.25)';
+ ctx.lineWidth = 0.4;
+ ctx.beginPath();
+ ctx.moveTo(bX + Math.cos(perp) * half, bY + Math.sin(perp) * half);
+ ctx.lineTo(tipX, tipY);
+ ctx.stroke();
}
document.querySelectorAll('canvas.hw-knob').forEach((canvas) => {