Podcast — Episode Grid
A dark, audio-first episode grid for podcast sites — responsive cards with gradient artwork, glowing mini play buttons, duration and date badges, live playing state, plus instant sort between newest and most-played and a live episode counter.
MCP
Code
:root {
--bg: #0c0c10;
--surface: #15151c;
--surface-2: #1d1d27;
--violet: #8b5cf6;
--violet-d: #7c3aed;
--cyan: #22d3ee;
--pink: #f472b6;
--ink: #f4f4f8;
--muted: #9a9aab;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
overflow-x: hidden;
}
.glow {
position: fixed;
inset: -30% 0 auto 0;
height: 480px;
pointer-events: none;
background:
radial-gradient(600px 320px at 20% 0%, rgba(139, 92, 246, 0.28), transparent 70%),
radial-gradient(560px 320px at 85% 5%, rgba(34, 211, 238, 0.18), transparent 70%);
z-index: 0;
}
/* ---------- header ---------- */
.site {
position: relative;
z-index: 2;
max-width: 1120px;
margin: 0 auto;
padding: 22px 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.brand {
display: flex;
align-items: center;
gap: 14px;
}
.brand__mark {
width: 46px;
height: 46px;
border-radius: var(--r-md);
background: linear-gradient(135deg, var(--violet), var(--cyan));
display: flex;
align-items: flex-end;
justify-content: center;
gap: 3px;
padding: 9px;
box-shadow: 0 10px 30px -8px rgba(139, 92, 246, 0.6);
}
.brand__mark span {
width: 4px;
border-radius: 4px;
background: rgba(12, 12, 16, 0.85);
animation: bar 1.1s ease-in-out infinite;
}
.brand__mark span:nth-child(1) { height: 40%; animation-delay: 0s; }
.brand__mark span:nth-child(2) { height: 85%; animation-delay: 0.15s; }
.brand__mark span:nth-child(3) { height: 55%; animation-delay: 0.3s; }
.brand__mark span:nth-child(4) { height: 70%; animation-delay: 0.45s; }
@keyframes bar {
0%, 100% { transform: scaleY(0.5); }
50% { transform: scaleY(1); }
}
.brand__eyebrow {
margin: 0;
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--cyan);
font-weight: 600;
}
.brand__name {
margin: 0;
font-size: 20px;
font-weight: 800;
letter-spacing: -0.01em;
}
.site__nav {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.chip {
padding: 8px 14px;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
color: var(--muted);
text-decoration: none;
border: 1px solid transparent;
transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.chip:hover {
color: var(--ink);
background: var(--surface);
}
.chip--active {
color: var(--ink);
background: var(--surface-2);
border-color: var(--line);
}
.btn-sub {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 9px 16px;
border-radius: 999px;
border: 0;
cursor: pointer;
font: inherit;
font-weight: 700;
font-size: 14px;
color: var(--white);
background: linear-gradient(135deg, var(--violet-d), var(--violet));
box-shadow: 0 8px 22px -8px rgba(139, 92, 246, 0.8);
transition: transform 0.12s, box-shadow 0.15s, filter 0.15s;
}
.btn-sub:hover {
transform: translateY(-1px);
filter: brightness(1.08);
}
.btn-sub:active {
transform: translateY(0);
}
/* ---------- lead ---------- */
.wrap {
position: relative;
z-index: 1;
max-width: 1120px;
margin: 0 auto;
padding: 8px 24px 72px;
}
.lead {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
margin: 20px 0 28px;
}
.lead__title {
margin: 0;
font-size: clamp(28px, 5vw, 40px);
font-weight: 800;
letter-spacing: -0.02em;
}
.lead__sub {
margin: 8px 0 0;
color: var(--muted);
font-size: 15px;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.count {
display: inline-flex;
align-items: center;
padding: 3px 10px;
border-radius: 999px;
font-size: 12.5px;
font-weight: 700;
color: var(--cyan);
background: rgba(34, 211, 238, 0.1);
border: 1px solid rgba(34, 211, 238, 0.25);
}
.toolbar {
display: flex;
align-items: center;
gap: 12px;
}
.toolbar__label {
font-size: 13px;
font-weight: 600;
color: var(--muted);
}
.segmented {
display: inline-flex;
padding: 4px;
gap: 4px;
border-radius: 999px;
background: var(--surface);
border: 1px solid var(--line);
}
.seg {
border: 0;
cursor: pointer;
font: inherit;
font-weight: 600;
font-size: 13.5px;
color: var(--muted);
padding: 8px 16px;
border-radius: 999px;
background: transparent;
transition: color 0.15s, background 0.2s, box-shadow 0.2s;
}
.seg:hover {
color: var(--ink);
}
.seg--on {
color: var(--white);
background: linear-gradient(135deg, var(--violet-d), var(--violet));
box-shadow: 0 6px 16px -6px rgba(139, 92, 246, 0.8);
}
/* ---------- grid ---------- */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
gap: 18px;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 14px;
display: flex;
flex-direction: column;
gap: 14px;
transition: transform 0.2s ease, border-color 0.2s, box-shadow 0.25s;
animation: rise 0.4s ease both;
}
.card:hover {
transform: translateY(-5px);
border-color: var(--line-2);
box-shadow: 0 24px 48px -24px rgba(0, 0, 0, 0.8);
}
@keyframes rise {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.art {
position: relative;
aspect-ratio: 16 / 10;
border-radius: var(--r-md);
overflow: hidden;
display: flex;
align-items: flex-end;
padding: 12px;
isolation: isolate;
}
.art::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 40%, rgba(6, 6, 10, 0.55));
z-index: 1;
}
.art__badges {
position: relative;
z-index: 2;
display: flex;
gap: 6px;
align-items: center;
}
.badge {
font-size: 11px;
font-weight: 700;
padding: 4px 9px;
border-radius: 999px;
letter-spacing: 0.02em;
color: var(--white);
background: rgba(12, 12, 16, 0.55);
backdrop-filter: blur(6px);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.badge--num {
background: rgba(139, 92, 246, 0.85);
border-color: transparent;
}
.badge--new {
background: rgba(34, 211, 238, 0.9);
color: #06232a;
border-color: transparent;
}
.play {
position: absolute;
z-index: 3;
top: 12px;
right: 12px;
width: 46px;
height: 46px;
border-radius: 50%;
border: 0;
cursor: pointer;
display: grid;
place-items: center;
color: var(--white);
background: linear-gradient(135deg, var(--violet), var(--cyan));
box-shadow: 0 8px 22px -6px rgba(139, 92, 246, 0.9);
transition: transform 0.14s, box-shadow 0.2s, filter 0.15s;
}
.play:hover {
transform: scale(1.08);
filter: brightness(1.08);
}
.play:active {
transform: scale(0.96);
}
.play svg {
transition: opacity 0.15s;
}
.play .icon-pause { display: none; }
.card.playing .play {
animation: pulse 1.6s ease-in-out infinite;
}
.card.playing .play .icon-play { display: none; }
.card.playing .play .icon-pause { display: block; }
@keyframes pulse {
0%, 100% { box-shadow: 0 8px 22px -6px rgba(139, 92, 246, 0.9); }
50% { box-shadow: 0 8px 34px -2px rgba(34, 211, 238, 0.9); }
}
/* waveform strip */
.wave {
position: absolute;
z-index: 2;
left: 12px;
right: 66px;
bottom: 12px;
height: 22px;
display: flex;
align-items: flex-end;
gap: 3px;
opacity: 0;
transform: translateY(6px);
transition: opacity 0.25s, transform 0.25s;
}
.card:hover .wave,
.card.playing .wave {
opacity: 1;
transform: translateY(0);
}
.wave i {
flex: 1;
border-radius: 3px;
background: linear-gradient(180deg, var(--cyan), var(--violet));
height: 30%;
}
.card.playing .wave i {
animation: eq 0.9s ease-in-out infinite;
}
.wave i:nth-child(2n) { animation-delay: 0.1s; }
.wave i:nth-child(3n) { animation-delay: 0.25s; }
.wave i:nth-child(4n) { animation-delay: 0.4s; }
.wave i:nth-child(5n) { animation-delay: 0.15s; }
@keyframes eq {
0%, 100% { height: 25%; }
50% { height: 95%; }
}
.body {
display: flex;
flex-direction: column;
gap: 10px;
}
.card__title {
margin: 0;
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
line-height: 1.3;
}
.card__desc {
margin: 0;
font-size: 13.5px;
color: var(--muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.meta {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
font-size: 12.5px;
color: var(--muted);
padding-top: 4px;
border-top: 1px solid var(--line);
}
.meta span {
display: inline-flex;
align-items: center;
gap: 5px;
}
.meta svg {
opacity: 0.8;
}
.meta .now {
color: var(--cyan);
font-weight: 700;
margin-left: auto;
display: none;
}
.card.playing .meta .now {
display: inline-flex;
}
.card__actions {
display: flex;
gap: 8px;
}
.mini {
flex: 1;
border: 1px solid var(--line);
background: var(--surface-2);
color: var(--ink);
font: inherit;
font-weight: 600;
font-size: 13px;
padding: 9px;
border-radius: var(--r-sm);
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.mini:hover {
border-color: var(--line-2);
background: #23232f;
}
.mini:active {
transform: scale(0.98);
}
/* focus */
a:focus-visible,
button:focus-visible {
outline: 2px solid var(--cyan);
outline-offset: 2px;
border-radius: 6px;
}
/* toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--surface-2);
color: var(--ink);
border: 1px solid var(--line-2);
padding: 12px 18px;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
box-shadow: 0 18px 40px -16px rgba(0, 0, 0, 0.8);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 40;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
.toast::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 50%;
background: linear-gradient(135deg, var(--violet), var(--cyan));
vertical-align: middle;
}
@media (max-width: 520px) {
.site {
padding: 16px;
}
.btn-sub span,
.chip:not(.chip--active) {
/* keep compact */
}
.wrap {
padding: 4px 16px 56px;
}
.lead {
align-items: flex-start;
}
.toolbar {
width: 100%;
justify-content: space-between;
}
.segmented {
flex: 1;
}
.seg {
flex: 1;
text-align: center;
}
.grid {
grid-template-columns: 1fr;
}
}(function () {
"use strict";
var GRADIENTS = [
"linear-gradient(135deg, #8b5cf6, #22d3ee)",
"linear-gradient(135deg, #f472b6, #7c3aed)",
"linear-gradient(135deg, #22d3ee, #7c3aed)",
"linear-gradient(135deg, #7c3aed, #f472b6)",
"linear-gradient(135deg, #06b6d4, #8b5cf6)",
"linear-gradient(135deg, #a855f7, #22d3ee)",
"linear-gradient(135deg, #ec4899, #8b5cf6)",
"linear-gradient(135deg, #14b8a6, #7c3aed)"
];
// Fictional but realistic dataset. days = days since publish (for "newest").
var EPISODES = [
{ n: 47, title: "The Sound of Empty Rooms", desc: "Field recordist Ravi Ortega on capturing silence and why it never really exists.", dur: "58 min", days: 2, plays: 12400, season: "S4" },
{ n: 46, title: "Designing for the Ear", desc: "How sonic branding shapes the way we trust products before we ever see them.", dur: "44 min", days: 6, plays: 31200, season: "S4" },
{ n: 45, title: "Static & Signal", desc: "A deep dive into noise reduction with audio engineer Mira Kwon.", dur: "1 hr 12 min", days: 11, plays: 9800, season: "S4" },
{ n: 44, title: "The Loudness War", desc: "Why modern records keep getting louder and what it costs the music.", dur: "39 min", days: 18, plays: 54800, season: "S4" },
{ n: 43, title: "Voices in the Machine", desc: "Synthetic speech, uncanny valleys, and the ethics of cloned voices.", dur: "51 min", days: 24, plays: 42100, season: "S3" },
{ n: 42, title: "Rooms That Sing", desc: "Architectural acoustics from cathedrals to open-plan offices.", dur: "47 min", days: 31, plays: 18700, season: "S3" },
{ n: 41, title: "Analog Hearts", desc: "The stubborn romance of tape, vinyl, and hiss in a digital world.", dur: "1 hr 3 min", days: 39, plays: 67300, season: "S3" },
{ n: 40, title: "Frequency & Feeling", desc: "The neuroscience of why certain sounds give us chills.", dur: "36 min", days: 48, plays: 88900, season: "S3" },
{ n: 39, title: "The Quiet Hour", desc: "Composer Nadia Belfort on writing scores for the spaces between words.", dur: "55 min", days: 57, plays: 23400, season: "S3" },
{ n: 38, title: "Broadcast Ghosts", desc: "Chasing pirate radio stations across a fading shortwave dial.", dur: "42 min", days: 66, plays: 15600, season: "S2" },
{ n: 37, title: "Mastering the Mix", desc: "What a mastering engineer actually does in those final hours.", dur: "1 hr 8 min", days: 74, plays: 39800, season: "S2" },
{ n: 36, title: "Echoes of the City", desc: "A soundwalk through midnight streets with urban ecologist Lena Fry.", dur: "49 min", days: 83, plays: 71200, season: "S2" }
];
var ICON_CLOCK =
'<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16Zm1-13h-2v6l5 3 1-1.7-4-2.4V7Z"/></svg>';
var ICON_CAL =
'<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M7 2v2H5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2V2h-2v2H9V2H7ZM5 9h14v10H5V9Z"/></svg>';
var ICON_HEAD =
'<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M12 3a9 9 0 0 0-9 9v5a3 3 0 0 0 3 3h1v-8H5v-0a7 7 0 0 1 14 0v0h-2v8h1a3 3 0 0 0 3-3v-5a9 9 0 0 0-9-9Z"/></svg>';
var ICON_PLAY =
'<svg class="icon-play" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M8 5v14l11-7L8 5Z"/></svg>';
var ICON_PAUSE =
'<svg class="icon-pause" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M7 5h4v14H7V5Zm6 0h4v14h-4V5Z"/></svg>';
var grid = document.getElementById("grid");
var countEl = document.getElementById("count");
var sortEl = document.getElementById("sort");
var toastEl = document.getElementById("toast");
var toastTimer = null;
var currentSort = "newest";
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
function fmtPlays(p) {
if (p >= 1000) return (p / 1000).toFixed(p % 1000 === 0 ? 0 : 1) + "k";
return String(p);
}
function fmtDate(days) {
var d = new Date();
d.setDate(d.getDate() - days);
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
function waveBars() {
var html = "";
for (var i = 0; i < 26; i++) html += "<i></i>";
return html;
}
function makeCard(ep, idx) {
var el = document.createElement("article");
el.className = "card";
el.style.animationDelay = idx * 0.035 + "s";
var isNew = ep.days <= 7;
var grad = GRADIENTS[ep.n % GRADIENTS.length];
el.innerHTML =
'<div class="art" style="background:' + grad + '">' +
'<div class="art__badges">' +
'<span class="badge badge--num">Ep ' + ep.n + "</span>" +
'<span class="badge">' + ep.season + "</span>" +
(isNew ? '<span class="badge badge--new">New</span>' : "") +
"</div>" +
'<div class="wave" aria-hidden="true">' + waveBars() + "</div>" +
'<button class="play" type="button" aria-label="Play ' + ep.title.replace(/"/g, "") + '">' +
ICON_PLAY + ICON_PAUSE +
"</button>" +
"</div>" +
'<div class="body">' +
'<h3 class="card__title">' + ep.title + "</h3>" +
'<p class="card__desc">' + ep.desc + "</p>" +
'<div class="meta">' +
"<span>" + ICON_CLOCK + ep.dur + "</span>" +
"<span>" + ICON_CAL + fmtDate(ep.days) + "</span>" +
"<span>" + ICON_HEAD + fmtPlays(ep.plays) + "</span>" +
'<span class="now">Now playing</span>' +
"</div>" +
'<div class="card__actions">' +
'<button class="mini" type="button" data-act="queue">Add to queue</button>' +
'<button class="mini" type="button" data-act="share">Share</button>' +
"</div>" +
"</div>";
var playBtn = el.querySelector(".play");
playBtn.addEventListener("click", function () {
var wasPlaying = el.classList.contains("playing");
// stop everyone
var all = grid.querySelectorAll(".card.playing");
all.forEach(function (c) {
c.classList.remove("playing");
var b = c.querySelector(".play");
b.setAttribute("aria-label", "Play episode");
});
if (!wasPlaying) {
el.classList.add("playing");
playBtn.setAttribute("aria-label", "Pause " + ep.title.replace(/"/g, ""));
toast("Playing — Ep " + ep.n + ": " + ep.title);
} else {
toast("Paused");
}
});
el.querySelectorAll(".mini").forEach(function (b) {
b.addEventListener("click", function () {
if (b.dataset.act === "queue") toast("Queued Ep " + ep.n);
else toast("Link copied to clipboard");
});
});
return el;
}
function render() {
var list = EPISODES.slice();
if (currentSort === "newest") {
list.sort(function (a, b) { return a.days - b.days; });
} else {
list.sort(function (a, b) { return b.plays - a.plays; });
}
// preserve any currently playing episode number
var playingEl = grid.querySelector(".card.playing");
var playingNum = null;
if (playingEl) {
var m = playingEl.querySelector(".badge--num");
if (m) playingNum = m.textContent.replace("Ep ", "").trim();
}
grid.innerHTML = "";
list.forEach(function (ep, i) {
var card = makeCard(ep, i);
if (playingNum !== null && String(ep.n) === playingNum) {
card.classList.add("playing");
}
grid.appendChild(card);
});
countEl.textContent = list.length + " episodes";
}
sortEl.addEventListener("click", function (e) {
var btn = e.target.closest(".seg");
if (!btn) return;
var val = btn.dataset.sort;
if (val === currentSort) return;
currentSort = val;
sortEl.querySelectorAll(".seg").forEach(function (s) {
var on = s === btn;
s.classList.toggle("seg--on", on);
s.setAttribute("aria-pressed", on ? "true" : "false");
});
render();
toast(val === "newest" ? "Sorted by newest" : "Sorted by most played");
});
document.getElementById("subBtn").addEventListener("click", function () {
toast("Subscribed to Signal & Noise");
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Signal & Noise — Episodes</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="glow" aria-hidden="true"></div>
<header class="site">
<div class="brand">
<div class="brand__mark" aria-hidden="true">
<span></span><span></span><span></span><span></span>
</div>
<div class="brand__text">
<p class="brand__eyebrow">Podcast</p>
<h1 class="brand__name">Signal & Noise</h1>
</div>
</div>
<nav class="site__nav" aria-label="Primary">
<a href="#" class="chip chip--active">Episodes</a>
<a href="#" class="chip">About</a>
<a href="#" class="chip">Guests</a>
<button class="btn-sub" type="button" id="subBtn">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path
fill="currentColor"
d="M4 11a1 1 0 0 1 1-1 9 9 0 0 1 9 9 1 1 0 1 1-2 0 7 7 0 0 0-7-7 1 1 0 0 1-1-1Zm0 6a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm0-11a1 1 0 0 1 1-1c8.28 0 15 6.72 15 15a1 1 0 1 1-2 0A13 13 0 0 0 5 6a1 1 0 0 1-1-1Z"
/>
</svg>
Subscribe
</button>
</nav>
</header>
<main class="wrap">
<section class="lead">
<div>
<h2 class="lead__title">All episodes</h2>
<p class="lead__sub">
Conversations on design, sound and the systems in between.
<span class="count" id="count">12 episodes</span>
</p>
</div>
<div class="toolbar" role="group" aria-label="Sort episodes">
<span class="toolbar__label">Sort</span>
<div class="segmented" id="sort">
<button class="seg seg--on" type="button" data-sort="newest" aria-pressed="true">
Newest
</button>
<button class="seg" type="button" data-sort="popular" aria-pressed="false">
Most played
</button>
</div>
</div>
</section>
<section class="grid" id="grid" aria-label="Episode list"></section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A bold, audio-first episode grid for a fictional show called Signal & Noise. Each card pairs a gradient artwork tile with a glowing violet-to-cyan play button, an episode number badge, a season tag, and a metadata row showing duration, publish date, and play count. Hovering a card lifts it and reveals an animated waveform strip; clicking the mini play button flips a single card into a live Now Playing state with an equalizer animation while pausing any other episode.
A sticky toolbar lets you re-sort the grid between Newest and Most played with a smooth reflow, and a live counter reflects how many episodes are shown. Every control is keyboard reachable with visible focus rings, and a lightweight toast confirms actions like playing or queuing an episode.
The layout is fully responsive: a multi-column grid on desktop collapses to a single stacked column under 520px, keeping tap targets large and readable. All artwork is rendered with pure CSS gradients — no external images — so the demo stays fast and self-contained.