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) => {