diff --git a/web/sandbox/studio.html b/web/sandbox/studio.html index 0d19e3b..2fe8f66 100644 --- a/web/sandbox/studio.html +++ b/web/sandbox/studio.html @@ -448,96 +448,112 @@ function setupCanvas(canvas, size) { function drawKnob(ctx, size, norm) { const cx = size / 2, cy = size / 2 + 1; - 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; + const totalR = size * 0.46; // outer edge of the whole knob + const rimW = size * 0.045; // dark rim thickness (thin) + const discR = totalR - rimW; // metallic disc fills most of inside ctx.clearRect(0, 0, size, size); - // (1) drop shadow under the whole knob — gives it weight on the panel + // (1) drop shadow under the whole knob ctx.save(); ctx.shadowColor = 'rgba(0,0,0,0.7)'; ctx.shadowBlur = 6; ctx.shadowOffsetY = 2; ctx.fillStyle = '#000'; ctx.beginPath(); - ctx.arc(cx, cy, outerR + 0.5, 0, Math.PI * 2); + ctx.arc(cx, cy, totalR + 0.5, 0, Math.PI * 2); ctx.fill(); ctx.restore(); - // (2) amber value arc OUTSIDE the bezel (faint track + bright active portion) + // (2) amber value arc OUTSIDE everything (faint full track + bright active) const startA = Math.PI * 0.78, endA = Math.PI * 2.22; ctx.lineCap = 'round'; ctx.lineWidth = 1.5; ctx.strokeStyle = 'rgba(232,160,80,0.10)'; ctx.beginPath(); - ctx.arc(cx, cy, outerR + 3, startA, endA); + ctx.arc(cx, cy, totalR + 4, startA, endA); ctx.stroke(); ctx.strokeStyle = '#e8a050'; ctx.shadowBlur = 3; ctx.shadowColor = 'rgba(232,160,80,0.55)'; ctx.beginPath(); - ctx.arc(cx, cy, outerR + 3, startA, startA + norm * (endA - startA)); + ctx.arc(cx, cy, totalR + 4, startA, startA + norm * (endA - startA)); ctx.stroke(); ctx.shadowBlur = 0; - // (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; + // (3) thin DARK RIM around the outside (the body of the knob) + const rim = ctx.createLinearGradient(0, cy - totalR, 0, cy + totalR); + rim.addColorStop(0, '#2a2826'); + rim.addColorStop(0.5, '#0a0908'); + rim.addColorStop(1, '#040404'); + ctx.strokeStyle = rim; + ctx.lineWidth = rimW; ctx.beginPath(); - ctx.arc(cx, cy, ringMid, 0, Math.PI * 2); + ctx.arc(cx, cy, totalR - rimW / 2, 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 + // (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 ); - dial.addColorStop(0, '#2a2a28'); - dial.addColorStop(0.5, '#141312'); - dial.addColorStop(1, '#040404'); - ctx.fillStyle = dial; + 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; ctx.beginPath(); - ctx.arc(cx, cy, dialR, 0, Math.PI * 2); + ctx.arc(cx, cy, discR, 0, Math.PI * 2); ctx.fill(); - // (7) faint glossy crescent on the dial's upper half - ctx.strokeStyle = 'rgba(255,255,255,0.07)'; + // (5) brushed-metal radial striations (very subtle, deterministic) + ctx.save(); + ctx.beginPath(); + ctx.arc(cx, cy, discR - 0.5, 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)})`; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(cx, cy); + ctx.lineTo(cx + Math.cos(a) * discR, cy + Math.sin(a) * discR); + ctx.stroke(); + } + ctx.restore(); + + // (6) thin shadow line where disc meets rim (depth) + ctx.strokeStyle = 'rgba(0,0,0,0.55)'; ctx.lineWidth = 0.7; ctx.beginPath(); - ctx.arc(cx, cy, dialR - 1, Math.PI * 1.18, Math.PI * 1.82); + ctx.arc(cx, cy, discR + 0.4, 0, Math.PI * 2); ctx.stroke(); - // (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.7; - ctx.lineCap = 'round'; + // (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.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.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 + const ang = startA + norm * (endA - startA); + const tipR = discR * 0.86; + const baseR = discR * 0.55; + const perp = ang + Math.PI / 2; + const half = size * 0.028; + 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)'; + 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(); } document.querySelectorAll('canvas.hw-knob').forEach((canvas) => {