sandbox/studio: metallic ring on knobs, top/bottom bars, recessed code
Three changes following the reference more closely: 1. Knobs now have a metallic outer bezel (chrome ring): vertical gradient running silver at the top → dark at the bottom, with a thin highlight on the upper crescent. The dark dial sits inside it with a slight inner shadow showing depth. Size bumped to 56px so the bezel is visible. 2. Layout split into two stacks: - left-stack: top-bar / screen / bottom-bar (vertical strip) - right-stack: knobs / seq 3. Top bar: STOP+RUN as a tight pair (no gap), buttons stretch to the bar's full height. Bottom bar matches the top bar visually and shows a "● RUNNING_" label with the pulsing amber dot, no longer inside the code area. 4. Code area is now *recessed* into the rack — no separate bordered panel. Inset shadows + slightly darker background simulate a cutout / screen window in the metal frame. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,12 +59,13 @@
|
|||||||
inset 0 0 0 1px rgba(232, 160, 80, 0.04),
|
inset 0 0 0 1px rgba(232, 160, 80, 0.04),
|
||||||
0 1px 0 rgba(255, 220, 180, 0.02),
|
0 1px 0 rgba(255, 220, 180, 0.02),
|
||||||
0 8px 24px rgba(0, 0, 0, 0.55);
|
0 8px 24px rgba(0, 0, 0, 0.55);
|
||||||
padding: 12px;
|
padding: 14px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 220px;
|
grid-template-columns: 1fr 220px;
|
||||||
grid-template-rows: auto 1fr;
|
gap: 12px;
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
.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 */
|
/* corner screws — only on the outermost frame */
|
||||||
.screw {
|
.screw {
|
||||||
@@ -93,33 +94,46 @@
|
|||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* Header bar inside the hardware unit */
|
/* Header bar inside the hardware unit */
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
.hw-header {
|
.top-bar, .bottom-bar {
|
||||||
grid-column: 1 / -1;
|
height: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
gap: 14px;
|
gap: 10px;
|
||||||
padding: 4px 26px;
|
padding: 3px 12px;
|
||||||
height: 36px;
|
|
||||||
color: var(--hw-fg-dim);
|
color: var(--hw-fg-dim);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
letter-spacing: 0.08em;
|
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 {
|
.hw-btn {
|
||||||
background: linear-gradient(180deg, #2c261f 0%, #14110d 100%);
|
background: linear-gradient(180deg, #2c261f 0%, #14110d 100%);
|
||||||
border: 1px solid var(--hw-edge);
|
border: 1px solid var(--hw-edge);
|
||||||
border-radius: 12px;
|
|
||||||
color: var(--hw-fg-hi);
|
color: var(--hw-fg-hi);
|
||||||
padding: 4px 12px;
|
padding: 0 14px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.12em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
box-shadow:
|
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),
|
inset 0 -1px 0 rgba(0,0,0,0.55),
|
||||||
0 1px 1px rgba(0,0,0,0.4);
|
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;
|
transition: color 80ms;
|
||||||
}
|
}
|
||||||
.hw-btn:hover { color: var(--hw-amber); }
|
.hw-btn:hover { color: var(--hw-amber); }
|
||||||
@@ -148,16 +162,19 @@
|
|||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* Code "screen" — left, big */
|
/* 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 {
|
.screen {
|
||||||
background: var(--hw-screen);
|
background: var(--hw-screen);
|
||||||
border: 1px solid var(--hw-edge);
|
border-radius: 4px;
|
||||||
border-radius: 6px;
|
padding: 12px 18px;
|
||||||
padding: 14px 18px 10px;
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 1px rgba(232,160,80,0.04),
|
inset 0 2px 4px rgba(0,0,0,0.7),
|
||||||
inset 0 1px 8px rgba(0,0,0,0.5);
|
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;
|
display: flex; flex-direction: column;
|
||||||
min-height: 320px;
|
min-height: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.glass {
|
.glass {
|
||||||
@@ -174,12 +191,19 @@
|
|||||||
.syn-op { color: var(--hw-syn-op); }
|
.syn-op { color: var(--hw-syn-op); }
|
||||||
.syn-arrow { color: var(--hw-amber); }
|
.syn-arrow { color: var(--hw-amber); }
|
||||||
|
|
||||||
.status-line {
|
.running-label {
|
||||||
margin-top: 8px;
|
|
||||||
color: var(--hw-amber);
|
color: var(--hw-amber);
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
letter-spacing: 0.16em;
|
letter-spacing: 0.18em;
|
||||||
text-shadow: 0 0 6px var(--hw-amber-glow);
|
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; }
|
.blink { animation: blink 1.1s step-end infinite; }
|
||||||
@keyframes blink { 50% { opacity: 0; } }
|
@keyframes blink { 50% { opacity: 0; } }
|
||||||
@@ -187,10 +211,6 @@
|
|||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* Right column */
|
/* Right column */
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
.right-col {
|
|
||||||
display: flex; flex-direction: column; gap: 10px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* knobs sub-panel inside hardware */
|
/* knobs sub-panel inside hardware */
|
||||||
.knobs {
|
.knobs {
|
||||||
@@ -284,24 +304,32 @@
|
|||||||
<span class="screw bl"></span>
|
<span class="screw bl"></span>
|
||||||
<span class="screw br"></span>
|
<span class="screw br"></span>
|
||||||
|
|
||||||
<!-- header bar, full width -->
|
<!-- left stack: top-bar / screen / bottom-bar -->
|
||||||
<div class="hw-header">
|
<div class="left-stack">
|
||||||
<button class="hw-btn"><span class="led-red"></span>STOP</button>
|
|
||||||
<button class="hw-btn"><span class="ico-play"></span>RUN</button>
|
<div class="top-bar">
|
||||||
<span>48 KHZ</span>
|
<div class="btn-group">
|
||||||
<span>TAPS: <span class="num">14</span></span>
|
<button class="hw-btn"><span class="led-red"></span>STOP</button>
|
||||||
<span>CPU: <span class="num">9%</span></span>
|
<button class="hw-btn"><span class="ico-play"></span>RUN</button>
|
||||||
<span class="right">CODE · SINTH</span>
|
</div>
|
||||||
|
<span>48 KHZ</span>
|
||||||
|
<span>TAPS: <span class="num">14</span></span>
|
||||||
|
<span>CPU: <span class="num">9%</span></span>
|
||||||
|
<span class="right">CODE · SINTH</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="screen">
|
||||||
|
<div class="glass" id="glass"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<span class="running-label"><span class="running-dot"></span>RUNNING<span class="blink">_</span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- code area -->
|
|
||||||
<section class="screen">
|
|
||||||
<div class="glass" id="glass"></div>
|
|
||||||
<div class="status-line">> RUNNING<span class="blink">_</span></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- right column with knobs + seq -->
|
<!-- right column with knobs + seq -->
|
||||||
<div class="right-col">
|
<div class="right-stack">
|
||||||
|
|
||||||
<section class="knobs">
|
<section class="knobs">
|
||||||
<div class="knob-cell">
|
<div class="knob-cell">
|
||||||
@@ -406,7 +434,7 @@ for (const line of PATCH) {
|
|||||||
// knob — restrained: dark gradient + thin amber arc + single tickmark.
|
// knob — restrained: dark gradient + thin amber arc + single tickmark.
|
||||||
// No knurled ridges, no brushed metal. Closer to the reference image.
|
// No knurled ridges, no brushed metal. Closer to the reference image.
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
const KNOB_SIZE = 50;
|
const KNOB_SIZE = 56;
|
||||||
function setupCanvas(canvas, size) {
|
function setupCanvas(canvas, size) {
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
canvas.style.width = size + 'px';
|
canvas.style.width = size + 'px';
|
||||||
@@ -420,70 +448,94 @@ 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 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);
|
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.save();
|
||||||
ctx.shadowColor = 'rgba(0,0,0,0.6)';
|
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, dialR, 0, Math.PI * 2);
|
ctx.arc(cx, cy, outerR + 0.5, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.restore();
|
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;
|
const startA = Math.PI * 0.78, endA = Math.PI * 2.22;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineWidth = 1.6;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.strokeStyle = 'rgba(232,160,80,0.10)'; // very faint full track
|
ctx.strokeStyle = 'rgba(232,160,80,0.10)';
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, dialR + 2.5, startA, endA);
|
ctx.arc(cx, cy, outerR + 3, 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.5)';
|
ctx.shadowColor = 'rgba(232,160,80,0.55)';
|
||||||
ctx.beginPath();
|
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.stroke();
|
||||||
ctx.shadowBlur = 0;
|
ctx.shadowBlur = 0;
|
||||||
|
|
||||||
// dial body — neutral charcoal black, NOT warm brown
|
// (3) METALLIC RING — vertical gradient (light top, dark bottom).
|
||||||
const grad = ctx.createRadialGradient(
|
// 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 - dialR * 0.35, cy - dialR * 0.5, 0,
|
||||||
cx, cy, dialR * 1.1
|
cx, cy, dialR * 1.1
|
||||||
);
|
);
|
||||||
grad.addColorStop(0, '#2a2a28'); // top-left highlight
|
dial.addColorStop(0, '#2a2a28');
|
||||||
grad.addColorStop(0.5, '#141312');
|
dial.addColorStop(0.5, '#141312');
|
||||||
grad.addColorStop(1, '#040404');
|
dial.addColorStop(1, '#040404');
|
||||||
ctx.fillStyle = grad;
|
ctx.fillStyle = dial;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, dialR, 0, Math.PI * 2);
|
ctx.arc(cx, cy, dialR, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// outer dark ring (separates dial from panel)
|
// (7) faint glossy crescent on the dial's upper half
|
||||||
ctx.strokeStyle = 'rgba(0,0,0,0.85)';
|
ctx.strokeStyle = 'rgba(255,255,255,0.07)';
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 0.7;
|
||||||
ctx.beginPath();
|
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();
|
ctx.stroke();
|
||||||
|
|
||||||
// glossy top crescent — short bright arc on the upper half
|
// (8) indicator tick — cream, from mid to dial edge
|
||||||
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
|
|
||||||
const ang = startA + norm * (endA - startA);
|
const ang = startA + norm * (endA - startA);
|
||||||
ctx.strokeStyle = 'rgba(245,235,215,0.92)';
|
ctx.strokeStyle = 'rgba(245,235,215,0.92)';
|
||||||
ctx.lineWidth = 1.6;
|
ctx.lineWidth = 1.7;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.beginPath();
|
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.lineTo(cx + Math.cos(ang) * dialR * 0.88, cy + Math.sin(ang) * dialR * 0.88);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user