sandbox/studio: knob now has large metallic disc filling the body
Reading the close-up: the knob is dominated by a big metallic chrome disc that occupies most of its surface. The dark part is just a thin outer rim. The indicator is a small dark notch on the disc that rotates with the value (the disc itself stays vertically lit). - Thin dark rim around the outside (~3px @ 56). - Large metallic disc fills the inside (radial gradient, brushed-metal striations from center outward, top crescent highlight). - Indicator is a small dark triangular notch on the disc, base near the center, tip toward the rim, rotated to the value angle. - Amber arc kept outside everything as a value-progress meter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -448,96 +448,112 @@ function setupCanvas(canvas, size) {
|
|||||||
|
|
||||||
function drawKnob(ctx, size, norm) {
|
function drawKnob(ctx, size, norm) {
|
||||||
const cx = size / 2, cy = size / 2 + 1;
|
const cx = size / 2, cy = size / 2 + 1;
|
||||||
const outerR = size * 0.46; // chrome-bezel outer edge
|
const totalR = size * 0.46; // outer edge of the whole knob
|
||||||
const ringW = size * 0.075; // metallic ring width (~4px @ 56)
|
const rimW = size * 0.045; // dark rim thickness (thin)
|
||||||
const dialR = outerR - ringW; // dark dial radius
|
const discR = totalR - rimW; // metallic disc fills most of inside
|
||||||
const ringMid = (outerR + dialR) / 2;
|
|
||||||
ctx.clearRect(0, 0, size, size);
|
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.save();
|
||||||
ctx.shadowColor = 'rgba(0,0,0,0.7)';
|
ctx.shadowColor = 'rgba(0,0,0,0.7)';
|
||||||
ctx.shadowBlur = 6;
|
ctx.shadowBlur = 6;
|
||||||
ctx.shadowOffsetY = 2;
|
ctx.shadowOffsetY = 2;
|
||||||
ctx.fillStyle = '#000';
|
ctx.fillStyle = '#000';
|
||||||
ctx.beginPath();
|
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.fill();
|
||||||
ctx.restore();
|
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;
|
const startA = Math.PI * 0.78, endA = Math.PI * 2.22;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.strokeStyle = 'rgba(232,160,80,0.10)';
|
ctx.strokeStyle = 'rgba(232,160,80,0.10)';
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, outerR + 3, startA, endA);
|
ctx.arc(cx, cy, totalR + 4, startA, endA);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.strokeStyle = '#e8a050';
|
ctx.strokeStyle = '#e8a050';
|
||||||
ctx.shadowBlur = 3;
|
ctx.shadowBlur = 3;
|
||||||
ctx.shadowColor = 'rgba(232,160,80,0.55)';
|
ctx.shadowColor = 'rgba(232,160,80,0.55)';
|
||||||
ctx.beginPath();
|
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.stroke();
|
||||||
ctx.shadowBlur = 0;
|
ctx.shadowBlur = 0;
|
||||||
|
|
||||||
// (3) METALLIC RING — vertical gradient (light top, dark bottom).
|
// (3) thin DARK RIM around the outside (the body of the knob)
|
||||||
// This is the "chrome bezel" the reference shows.
|
const rim = ctx.createLinearGradient(0, cy - totalR, 0, cy + totalR);
|
||||||
const ringGrad = ctx.createLinearGradient(0, cy - outerR, 0, cy + outerR);
|
rim.addColorStop(0, '#2a2826');
|
||||||
ringGrad.addColorStop(0, '#c4beb2');
|
rim.addColorStop(0.5, '#0a0908');
|
||||||
ringGrad.addColorStop(0.30, '#807a72');
|
rim.addColorStop(1, '#040404');
|
||||||
ringGrad.addColorStop(0.55, '#3a3631');
|
ctx.strokeStyle = rim;
|
||||||
ringGrad.addColorStop(0.85, '#1c1a17');
|
ctx.lineWidth = rimW;
|
||||||
ringGrad.addColorStop(1, '#0a0908');
|
|
||||||
ctx.strokeStyle = ringGrad;
|
|
||||||
ctx.lineWidth = ringW;
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, ringMid, 0, Math.PI * 2);
|
ctx.arc(cx, cy, totalR - rimW / 2, 0, Math.PI * 2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// (4) thin bright highlight on the very top of the bezel
|
// (4) BIG METALLIC DISC — fills most of the inside (the "circulito metalico
|
||||||
ctx.strokeStyle = 'rgba(255,255,255,0.22)';
|
// que ocupa casi todo"). Radial gradient, brushed-metal feel.
|
||||||
ctx.lineWidth = 0.6;
|
const disc = ctx.createRadialGradient(
|
||||||
ctx.beginPath();
|
cx, cy - discR * 0.25, 0,
|
||||||
ctx.arc(cx, cy, outerR - 0.3, Math.PI * 1.18, Math.PI * 1.82);
|
cx, cy + discR * 0.4, discR * 1.4
|
||||||
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
|
|
||||||
);
|
);
|
||||||
dial.addColorStop(0, '#2a2a28');
|
disc.addColorStop(0, '#b6b0a6'); // lit upper-center
|
||||||
dial.addColorStop(0.5, '#141312');
|
disc.addColorStop(0.45, '#8a847a');
|
||||||
dial.addColorStop(1, '#040404');
|
disc.addColorStop(0.85, '#46423d');
|
||||||
ctx.fillStyle = dial;
|
disc.addColorStop(1, '#2a2722'); // bottom dark
|
||||||
|
ctx.fillStyle = disc;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, dialR, 0, Math.PI * 2);
|
ctx.arc(cx, cy, discR, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// (7) faint glossy crescent on the dial's upper half
|
// (5) brushed-metal radial striations (very subtle, deterministic)
|
||||||
ctx.strokeStyle = 'rgba(255,255,255,0.07)';
|
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.lineWidth = 0.7;
|
||||||
ctx.beginPath();
|
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();
|
ctx.stroke();
|
||||||
|
|
||||||
// (8) indicator tick — cream, from mid to dial edge
|
// (7) bright highlight crescent on the top edge of the disc
|
||||||
const ang = startA + norm * (endA - startA);
|
ctx.strokeStyle = 'rgba(255,255,255,0.30)';
|
||||||
ctx.strokeStyle = 'rgba(245,235,215,0.92)';
|
ctx.lineWidth = 0.8;
|
||||||
ctx.lineWidth = 1.7;
|
|
||||||
ctx.lineCap = 'round';
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(cx + Math.cos(ang) * dialR * 0.5, cy + Math.sin(ang) * dialR * 0.5);
|
ctx.arc(cx, cy, discR - 0.5, Math.PI * 1.18, Math.PI * 1.82);
|
||||||
ctx.lineTo(cx + Math.cos(ang) * dialR * 0.88, cy + Math.sin(ang) * dialR * 0.88);
|
|
||||||
ctx.stroke();
|
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) => {
|
document.querySelectorAll('canvas.hw-knob').forEach((canvas) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user