Draw the title, waveform and logo as bevelled chrome in WebGL

A distance transform turns the glyphs and waveform bars into a height field
with bevelled edges and some depth, and a shader lights them as metal: a
studio of softboxes, a dark horizon and the card's colour and art as the
floor, colour split at steep edges, a key light that follows the pointer
(drifts on phones) and flares on kicks, and a sweep over each new title.
One offscreen context serves every plate. The title turns slightly towards
the pointer. Display text uses Bruno Ace SC, bundled; "simple" visuals keep
plain text and the drawn waveform.
This commit is contained in:
nvrl committed 2026-11-01 20:06:32 +01:00
1 parent 62b28bd30e
commit cfccfcaac6
7 files changed
+778 -13

No files matched your search

+27 -9
View File
@@ -18,7 +18,8 @@
--radius-l: 18px;
--shadow: 0 16px 40px #000a;
--font: system-ui, -apple-system, "Segoe UI", sans-serif;
--font-display: var(--font);
/* One weight only: a synthetic bold would smear it, so display text stays 400. */
--font-display: "Bruno Ace SC", var(--font);
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
--fast: 0.15s;
}
@@ -73,8 +74,8 @@ button {
}
body.zen #bar { display: none; }
.logo { font-weight: 600; display: inline-flex; align-items: center; gap: 0.4em; white-space: nowrap; justify-self: start; }
.logo b { color: var(--accent); font-weight: 700; }
.logo { position: relative; font: 400 0.95rem var(--font-display); letter-spacing: 0.03em; display: inline-flex; align-items: center; gap: 0.4em; white-space: nowrap; justify-self: start; }
.logo b { position: relative; color: var(--accent); font-weight: 400; }
.logo svg { width: 1.3em; height: 1.3em; color: var(--accent); flex: none; }
.bar-end { justify-self: end; display: flex; align-items: center; gap: 0.4rem; }
@@ -187,7 +188,7 @@ body.zen #bar { display: none; }
.sheet:focus-visible { outline: none; }
@keyframes drawer { from { transform: translateX(100%); } }
.sheet-handle { display: none; }
.sheet h2 { margin: 0; font: 800 1.25rem/1.2 var(--font-display); }
.sheet h2 { margin: 0; font: 400 1.2rem/1.2 var(--font-display); letter-spacing: 0.03em; }
.sheet fieldset { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.sheet legend, .sheet h3 {
padding: 0;
@@ -518,12 +519,29 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
@keyframes spin { to { transform: rotate(360deg); } }
.title {
font: 800 clamp(1.3rem, 4.2vw, 1.9rem)/1.1 var(--font-display);
letter-spacing: -0.01em;
position: relative;
z-index: 1; /* over the drop's spill */
font: 400 clamp(1.25rem, 3.4vw, 1.65rem)/1.2 var(--font-display);
letter-spacing: 0.02em;
margin: 0;
overflow-wrap: anywhere;
text-wrap: balance;
}
/* Chrome (chrome.ts): a canvas over the text draws it as metal; the text stays
for selection and screen readers, but transparent. The canvas reaches past the
text for the glow and the shadow. */
.chrome-plate {
position: absolute;
inset: calc(-1 * var(--pad, 16px));
width: calc(100% + 2 * var(--pad, 16px));
height: calc(100% + 2 * var(--pad, 16px));
pointer-events: none;
opacity: 0;
transition: opacity 0.35s;
}
.chromed { color: transparent !important; }
.chromed > .chrome-plate { opacity: 1; }
.why {
margin: 0;
min-height: 0;
@@ -791,7 +809,7 @@ body.zen #up-next { display: none; }
}
.start-play svg { width: 2.6rem; height: 2.6rem; margin-left: 0.3rem; }
@keyframes beckon { to { box-shadow: 0 0 0 2rem transparent; } }
.start-track { margin: 0.4rem 0 0; font-weight: 700; font-size: 1.1rem; text-align: center; padding: 0 1rem; }
.start-track { margin: 0.4rem 0 0; font: 400 1.1rem/1.25 var(--font-display); letter-spacing: 0.02em; text-align: center; padding: 0 1rem; }
.start-hint { margin: 0; color: var(--muted); font-size: 0.85rem; }
/* Radio sheet: a bottom sheet on phones, a centred box elsewhere. */
@@ -825,7 +843,7 @@ body.zen #up-next { display: none; }
.icon-btn { width: 2.2rem; height: 2.2rem; padding: 0; display: grid; place-items: center; }
.icon-btn svg { width: 1rem; height: 1rem; }
.station-head { display: flex; gap: 1rem; align-items: center; }
.station-head h2 { margin: 0; font: 800 1.4rem/1.1 var(--font-display); overflow-wrap: anywhere; }
.station-head h2 { margin: 0; font: 400 1.3rem/1.15 var(--font-display); overflow-wrap: anywhere; }
.station-kicker { margin: 0 0 0.2rem; font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); }
#station-sub { margin: 0.3rem 0 0.4rem; color: var(--muted); font-size: 0.85rem; }
#station-tags { justify-content: flex-start; }
@@ -938,7 +956,7 @@ button.primary:disabled { opacity: 0.35; cursor: default; }
flex-direction: column;
gap: 0.9rem;
}
.login-box h1 { margin: 0; font-size: 2.4rem; display: flex; align-items: center; gap: 0.3em; }
.login-box h1 { margin: 0; font: 400 2.2rem/1.1 var(--font-display); letter-spacing: 0.02em; display: flex; align-items: center; gap: 0.3em; }
.login-box h1 b { color: var(--accent); }
/* A wheel up: the plate is pulled back once, then rests. */
.login-mark { width: 1.2em; height: 1.2em; color: var(--accent); flex: none; animation: wheel-up 1.1s 0.3s cubic-bezier(0.3, 0, 0.2, 1) both; }