Music — Vinyl / Analog Retro Landing
A warm, grainy landing page for a vinyl reissue label and record shop. A CSS-drawn turntable with a spinning record, swinging tonearm and crackle equalizer anchors a vintage serif hero, backed by a featured-pressings grid whose sleeve discs spin on hover, a genre tab filter, a why-analog feature trio, a turntable equipment band and a paper cut-out newsletter. Vanilla JS simulates playback with timers, like toggles, cart and form validation.
MCP
代码
:root {
--bg: #241a12;
--bg-2: #2e2118;
--surface: #3a2a1d;
--surface-2: #46341f;
--text: #f3e7d3;
--muted: #c0a888;
--line: rgba(243, 231, 211, 0.12);
--line-2: rgba(243, 231, 211, 0.22);
--accent: #8c2f22; /* oxblood */
--accent-2: #d99a4e; /* amber */
--paper: #efe2c9;
--ink: #2a1c10;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--r-full: 999px;
--shadow: 0 18px 50px -22px rgba(0, 0, 0, 0.7);
--shadow-sm: 0 8px 22px -12px rgba(0, 0, 0, 0.6);
--display: "Playfair Display", Georgia, serif;
--body: "Inter", system-ui, sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: var(--body);
background:
radial-gradient(1200px 600px at 80% -10%, rgba(217, 154, 78, 0.14), transparent 60%),
radial-gradient(900px 500px at -10% 20%, rgba(140, 47, 34, 0.18), transparent 55%),
var(--bg);
color: var(--text);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
overflow-x: hidden;
}
/* Grain overlay */
.grain {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.5;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E");
}
img { max-width: 100%; }
a { color: inherit; text-decoration: none; }
.eyebrow {
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--accent-2);
font-weight: 700;
margin: 0 0 0.7rem;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
/* ===== Buttons ===== */
.btn {
display: inline-flex;
align-items: center;
gap: 0.55rem;
font-family: var(--body);
font-weight: 700;
font-size: 0.92rem;
border: 1px solid transparent;
border-radius: var(--r-full);
padding: 0.78rem 1.4rem;
cursor: pointer;
transition: transform 0.18s ease, background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
letter-spacing: 0.01em;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; }
.btn-solid {
background: linear-gradient(180deg, #a23a2b, var(--accent));
color: var(--paper);
box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.btn-solid:hover { transform: translateY(-2px); box-shadow: 0 16px 30px -14px rgba(140, 47, 34, 0.8); }
.btn-ghost {
background: rgba(243, 231, 211, 0.04);
color: var(--text);
border-color: var(--line-2);
}
.btn-ghost:hover { background: rgba(243, 231, 211, 0.09); border-color: var(--accent-2); }
.play-ico {
width: 0; height: 0;
border-style: solid;
border-width: 6px 0 6px 10px;
border-color: transparent transparent transparent currentColor;
display: inline-block;
}
[aria-pressed="true"] .play-ico {
border: none;
width: 9px; height: 11px;
border-left: 3px solid currentColor;
border-right: 3px solid currentColor;
box-sizing: content-box;
}
/* ===== Header ===== */
.site-head {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: center;
gap: 1.5rem;
padding: 0.9rem clamp(1rem, 4vw, 3rem);
background: rgba(36, 26, 18, 0.72);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
}
.brand { display: inline-flex; align-items: center; gap: 0.6rem; font-family: var(--display); font-weight: 800; font-size: 1.15rem; }
.brand-mark {
width: 30px; height: 30px;
display: grid; place-items: center;
border-radius: var(--r-full);
background: radial-gradient(circle at 50% 50%, var(--accent-2) 0 14%, #1c130b 15% 100%);
border: 1px solid var(--line-2);
}
.brand-mark.sm { width: 24px; height: 24px; }
.brand-disc {
width: 9px; height: 9px;
border-radius: var(--r-full);
background: var(--accent);
box-shadow: 0 0 0 2px var(--bg);
}
.nav { display: flex; gap: 1.4rem; margin-left: auto; font-size: 0.9rem; font-weight: 600; color: var(--muted); }
.nav a { transition: color 0.18s ease; }
.nav a:hover { color: var(--text); }
.head-cta { margin-left: 0.4rem; padding: 0.55rem 1.1rem; font-size: 0.85rem; }
/* ===== Hero ===== */
.hero {
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: clamp(2rem, 5vw, 4rem);
align-items: center;
padding: clamp(2.5rem, 6vw, 5.5rem) clamp(1rem, 4vw, 3rem) clamp(2rem, 4vw, 3rem);
max-width: 1200px;
margin: 0 auto;
}
.hero-title {
font-family: var(--display);
font-weight: 900;
font-size: clamp(3rem, 8vw, 5.6rem);
line-height: 0.98;
margin: 0.2rem 0 1.1rem;
letter-spacing: -0.01em;
}
.hero-title em {
font-style: italic;
color: var(--accent-2);
text-shadow: 0 2px 24px rgba(217, 154, 78, 0.3);
}
.hero-sub {
color: var(--muted);
font-size: 1.08rem;
max-width: 30rem;
margin: 0 0 1.7rem;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.8rem; margin-bottom: 2.2rem; }
.hero-stats {
display: flex;
gap: clamp(1.2rem, 4vw, 2.6rem);
margin: 0;
padding-top: 1.4rem;
border-top: 1px solid var(--line);
}
.hero-stats dt { font-size: 0.74rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
.hero-stats dd { margin: 0.2rem 0 0; font-family: var(--display); font-weight: 800; font-size: 1.5rem; }
/* ===== Turntable / hero deck ===== */
.hero-deck { display: grid; place-items: center; }
.turntable {
position: relative;
width: min(420px, 86vw);
aspect-ratio: 1;
border-radius: var(--r-lg);
padding: 26px;
background:
radial-gradient(120% 120% at 30% 20%, rgba(217, 154, 78, 0.12), transparent 55%),
linear-gradient(150deg, var(--surface-2), var(--surface));
border: 1px solid var(--line-2);
box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.platter {
position: relative;
width: 100%; height: 100%;
border-radius: var(--r-full);
background:
radial-gradient(circle at 50% 50%, #1a120b 0 70%, #0e0905 100%);
display: grid;
place-items: center;
cursor: pointer;
border: 6px solid rgba(0, 0, 0, 0.4);
}
.platter:focus-visible { outline: 3px solid var(--accent-2); outline-offset: 4px; }
.record {
position: relative;
width: 94%; height: 94%;
border-radius: var(--r-full);
background:
repeating-radial-gradient(circle at 50% 50%,
#0b0805 0 2px, #181009 2px 3px),
radial-gradient(circle at 38% 32%, rgba(217, 154, 78, 0.12), transparent 45%),
#0a0603;
display: grid; place-items: center;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.9);
}
.record.is-spinning { animation: spin 3.4s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.record-ring { position: absolute; border-radius: var(--r-full); border: 1px solid rgba(217, 154, 78, 0.07); }
.r1 { inset: 9%; }
.r2 { inset: 18%; }
.r3 { inset: 27%; }
.record-label {
position: relative;
width: 42%; height: 42%;
border-radius: var(--r-full);
background: radial-gradient(circle at 40% 35%, #b9442f, var(--accent) 70%);
color: var(--paper);
display: grid;
align-content: center;
justify-items: center;
gap: 0.18rem;
text-align: center;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25), 0 0 0 6px #0a0603;
}
.rl-top { font-family: var(--display); font-weight: 800; font-size: 0.62rem; letter-spacing: 0.12em; text-transform: uppercase; }
.rl-mid { font-family: var(--display); font-weight: 800; font-size: 0.74rem; line-height: 1; }
.rl-bot { font-size: 0.5rem; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.85; }
.record-hole {
position: absolute;
width: 8px; height: 8px;
border-radius: var(--r-full);
background: #0a0603;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
}
.record-sheen {
position: absolute; inset: 0;
border-radius: var(--r-full);
background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.10) 48%, transparent 56%);
pointer-events: none;
}
/* Tonearm */
.tonearm {
position: absolute;
top: 6%; right: 6%;
width: 44%; height: 44%;
transform-origin: top right;
transform: rotate(-26deg);
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
}
.tonearm.is-down { transform: rotate(8deg); }
.tonearm-base {
position: absolute; top: -6px; right: -6px;
width: 26px; height: 26px;
border-radius: var(--r-full);
background: linear-gradient(180deg, #c9b48f, #8a744f);
box-shadow: var(--shadow-sm);
}
.tonearm-rod {
position: absolute; top: 6px; right: 6px;
width: 78%; height: 5px;
border-radius: var(--r-full);
background: linear-gradient(90deg, #ddc9a3, #9a8358);
transform-origin: right;
transform: rotate(38deg);
}
.tonearm-head {
position: absolute; bottom: 6%; left: 14%;
width: 16px; height: 11px;
border-radius: 3px;
background: #2a1c10;
border: 1px solid var(--accent-2);
}
.now-spinning {
position: absolute;
left: 50%; bottom: 8px;
transform: translateX(-50%);
display: inline-flex; align-items: center; gap: 0.45rem;
background: rgba(10, 6, 3, 0.7);
border: 1px solid var(--line-2);
border-radius: var(--r-full);
padding: 0.32rem 0.8rem;
font-size: 0.72rem;
font-weight: 600;
color: var(--muted);
white-space: nowrap;
backdrop-filter: blur(6px);
}
.ns-dot { width: 7px; height: 7px; border-radius: var(--r-full); background: var(--muted); }
.now-spinning.is-live .ns-dot { background: var(--accent-2); box-shadow: 0 0 8px var(--accent-2); animation: pulse 1.2s ease infinite; }
.now-spinning.is-live { color: var(--text); }
@keyframes pulse { 50% { opacity: 0.4; } }
/* Crackle equalizer */
.crackle {
position: absolute;
left: 14px; bottom: 14px;
display: flex; align-items: flex-end; gap: 3px;
height: 26px;
opacity: 0;
transition: opacity 0.3s ease;
}
.crackle.is-live { opacity: 1; }
.crackle span {
width: 3px; height: 6px;
background: var(--accent-2);
border-radius: 2px;
}
.crackle.is-live span { animation: eq 0.9s ease-in-out infinite; }
.crackle span:nth-child(2n) { animation-delay: 0.15s; }
.crackle span:nth-child(3n) { animation-delay: 0.3s; }
.crackle span:nth-child(4n) { animation-delay: 0.45s; }
@keyframes eq { 0%, 100% { height: 5px; } 50% { height: 24px; } }
/* ===== Sections ===== */
.section {
max-width: 1200px;
margin: 0 auto;
padding: clamp(2.5rem, 6vw, 5rem) clamp(1rem, 4vw, 3rem);
}
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 2rem; }
.section-head.center { flex-direction: column; align-items: center; text-align: center; }
.section-title { font-family: var(--display); font-weight: 800; font-size: clamp(1.7rem, 4vw, 2.6rem); margin: 0; letter-spacing: -0.01em; }
/* Genre tabs */
.genre-tabs { display: inline-flex; gap: 0.3rem; padding: 0.3rem; background: rgba(0, 0, 0, 0.25); border: 1px solid var(--line); border-radius: var(--r-full); }
.genre-tab {
font-family: var(--body); font-weight: 700; font-size: 0.82rem;
color: var(--muted);
background: transparent;
border: none;
padding: 0.5rem 1rem;
border-radius: var(--r-full);
cursor: pointer;
transition: color 0.2s ease, background 0.2s ease;
}
.genre-tab:hover { color: var(--text); }
.genre-tab.is-active { background: var(--accent); color: var(--paper); box-shadow: var(--shadow-sm); }
.genre-tab:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
/* ===== Press grid ===== */
.press-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
gap: 1.4rem;
}
.press-card {
background: linear-gradient(180deg, var(--surface), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1rem;
box-shadow: var(--shadow-sm);
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease, opacity 0.3s ease;
}
.press-card:hover { transform: translateY(-4px); border-color: var(--line-2); box-shadow: var(--shadow); }
.press-card.is-hidden { display: none; }
.sleeve {
position: relative;
aspect-ratio: 1;
border-radius: var(--r-md);
overflow: hidden;
display: grid;
place-items: center;
isolation: isolate;
}
.sleeve::after {
content: "";
position: absolute; inset: 0;
background: linear-gradient(160deg, rgba(255, 255, 255, 0.1), transparent 40%);
z-index: 3;
}
.sleeve-art {
position: absolute; inset: 0;
z-index: 1;
}
.sleeve-title {
position: relative;
z-index: 4;
font-family: var(--display);
font-weight: 800;
font-size: 1.3rem;
text-align: center;
color: var(--paper);
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
padding: 0 0.8rem;
line-height: 1.05;
}
/* sleeve disc peeking out; peeks on hover, spins only while playing */
.sleeve-disc {
position: absolute;
z-index: 2;
top: 50%; right: -34%;
width: 78%; aspect-ratio: 1;
transform: translateY(-50%);
border-radius: var(--r-full);
background:
repeating-radial-gradient(circle at 50% 50%, #0b0805 0 2px, #1c130b 2px 3px),
#0a0603;
transition: right 0.4s cubic-bezier(0.22, 1, 0.36, 1);
box-shadow: -10px 0 22px rgba(0, 0, 0, 0.5);
}
.sleeve-disc::after {
content: "";
position: absolute; inset: 38%;
border-radius: var(--r-full);
background: var(--disc, var(--accent));
box-shadow: 0 0 0 4px #0a0603;
}
.press-card:hover .sleeve-disc { right: -20%; }
.press-meta { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.6rem; margin-top: 0.9rem; }
.press-artist { font-family: var(--display); font-weight: 700; font-size: 1.05rem; margin: 0; }
.press-album { color: var(--muted); font-size: 0.86rem; margin: 0.15rem 0 0; }
.press-genre {
font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--accent-2);
border: 1px solid var(--line-2);
border-radius: var(--r-full);
padding: 0.25rem 0.6rem;
white-space: nowrap;
}
.press-foot { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px solid var(--line); }
.press-price { font-family: var(--display); font-weight: 800; font-size: 1.2rem; }
.press-price small { font-size: 0.72rem; color: var(--muted); font-weight: 600; font-family: var(--body); }
.press-actions { display: flex; align-items: center; gap: 0.5rem; }
.icon-btn {
width: 38px; height: 38px;
display: grid; place-items: center;
border-radius: var(--r-full);
border: 1px solid var(--line-2);
background: rgba(243, 231, 211, 0.03);
color: var(--text);
cursor: pointer;
transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease, color 0.18s ease;
}
.icon-btn:hover { transform: translateY(-2px); border-color: var(--accent-2); }
.icon-btn:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.icon-btn .play-ico { color: currentColor; }
.icon-btn.cart { font-size: 1rem; line-height: 1; }
.heart {
position: relative;
width: 16px; height: 16px;
}
.heart::before, .heart::after {
content: ""; position: absolute;
width: 8px; height: 13px;
left: 8px; top: 0;
background: currentColor;
border-radius: 8px 8px 0 0;
transform: rotate(-45deg);
transform-origin: 0 100%;
opacity: 0.85;
}
.heart::after { left: 0; transform: rotate(45deg); transform-origin: 100% 100%; }
.like-btn[aria-pressed="true"] { color: var(--accent-3, var(--accent)); border-color: var(--accent); background: rgba(140, 47, 34, 0.2); }
.like-btn[aria-pressed="true"] .heart::before,
.like-btn[aria-pressed="true"] .heart::after { opacity: 1; }
.like-btn.bump { animation: bump 0.4s ease; }
@keyframes bump { 30% { transform: scale(1.3); } 60% { transform: scale(0.92); } }
/* playing card spinner state */
.press-card.is-playing .sleeve-disc { right: -20%; animation: spin 2.4s linear infinite; }
.press-card.is-playing { border-color: var(--accent-2); }
/* ===== Why analog ===== */
.why-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.4rem; }
.why-card {
background: linear-gradient(180deg, var(--surface), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.8rem 1.5rem;
text-align: center;
transition: transform 0.25s ease, border-color 0.25s ease;
}
.why-card:hover { transform: translateY(-4px); border-color: var(--line-2); }
.why-ico {
width: 64px; height: 64px;
margin: 0 auto 1.1rem;
display: grid; place-items: center;
border-radius: var(--r-full);
background: radial-gradient(circle at 35% 30%, rgba(217, 154, 78, 0.2), transparent 60%), var(--surface-2);
border: 1px solid var(--line-2);
}
.why-card h3 { font-family: var(--display); font-weight: 800; font-size: 1.35rem; margin: 0 0 0.5rem; }
.why-card p { color: var(--muted); font-size: 0.95rem; margin: 0; }
.eq { display: flex; align-items: flex-end; gap: 3px; height: 26px; }
.eq i { width: 4px; height: 8px; background: var(--accent-2); border-radius: 2px; animation: eq 1s ease-in-out infinite; }
.eq i:nth-child(2) { animation-delay: 0.2s; }
.eq i:nth-child(3) { animation-delay: 0.4s; }
.eq i:nth-child(4) { animation-delay: 0.1s; }
.ritual-disc {
width: 30px; height: 30px;
border-radius: var(--r-full);
background: repeating-radial-gradient(circle at 50% 50%, #0b0805 0 2px, #1c130b 2px 3px), #0a0603;
position: relative;
animation: spin 4s linear infinite;
}
.ritual-disc::after { content: ""; position: absolute; inset: 38%; border-radius: var(--r-full); background: var(--accent); box-shadow: 0 0 0 3px #0a0603; }
.art-frame {
width: 30px; height: 30px;
border-radius: 4px;
border: 2px solid var(--accent-2);
background: linear-gradient(135deg, var(--accent), var(--accent-2));
position: relative;
}
.art-frame::after { content: ""; position: absolute; inset: 4px; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 2px; }
/* ===== Gear band ===== */
.gear { }
.gear-inner {
display: grid;
grid-template-columns: 1fr 1fr;
gap: clamp(2rem, 5vw, 4rem);
align-items: center;
background: linear-gradient(150deg, var(--surface-2), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: clamp(1.8rem, 4vw, 3rem);
box-shadow: var(--shadow);
}
.gear-lead { color: var(--muted); font-size: 1.02rem; max-width: 28rem; }
.gear-list { list-style: none; padding: 0; margin: 1.4rem 0 1.8rem; display: grid; gap: 0.5rem; }
.gear-list li { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.7rem 0; border-bottom: 1px solid var(--line); }
.gear-list span { font-weight: 600; }
.gear-list em { color: var(--accent-2); font-style: normal; font-weight: 700; font-size: 0.9rem; }
.gear-deck { margin: 0; display: grid; place-items: center; position: relative; }
.deck-platter {
width: min(300px, 70vw); aspect-ratio: 1;
border-radius: var(--r-full);
background: radial-gradient(circle at 50% 50%, #1a120b 0 72%, #0e0905 100%);
border: 12px solid var(--surface);
box-shadow: var(--shadow), inset 0 0 40px rgba(0, 0, 0, 0.7);
display: grid; place-items: center;
}
.deck-record {
width: 90%; aspect-ratio: 1;
border-radius: var(--r-full);
background: repeating-radial-gradient(circle at 50% 50%, #0b0805 0 2px, #1c130b 2px 3px), #0a0603;
display: grid; place-items: center;
animation: spin 5s linear infinite;
}
.deck-label { width: 36%; aspect-ratio: 1; border-radius: var(--r-full); background: radial-gradient(circle at 40% 35%, var(--accent-2), var(--accent)); box-shadow: 0 0 0 5px #0a0603; }
.deck-arm {
position: absolute; top: 12%; right: 10%;
width: 38%; height: 5px;
background: linear-gradient(90deg, #ddc9a3, #9a8358);
border-radius: var(--r-full);
transform-origin: right; transform: rotate(28deg);
box-shadow: var(--shadow-sm);
}
.deck-cap { position: absolute; bottom: -2px; font-size: 0.78rem; color: var(--muted); letter-spacing: 0.05em; }
/* ===== Newsletter cut-out ===== */
.cutout {
position: relative;
background:
repeating-linear-gradient(135deg, rgba(0,0,0,0.02) 0 12px, transparent 12px 24px),
var(--paper);
color: var(--ink);
border-radius: var(--r-lg);
padding: clamp(2rem, 5vw, 3.4rem);
box-shadow: var(--shadow);
overflow: hidden;
}
.cutout-perf {
position: absolute; left: 0; right: 0; top: 14px; height: 0;
border-top: 2px dashed rgba(42, 28, 16, 0.25);
}
.cutout-body { max-width: 36rem; position: relative; z-index: 2; }
.cutout .eyebrow { color: var(--accent); }
.cutout-title { font-family: var(--display); font-weight: 900; font-size: clamp(1.7rem, 4vw, 2.6rem); margin: 0 0 0.7rem; letter-spacing: -0.01em; }
.cutout-sub { color: rgba(42, 28, 16, 0.75); font-size: 1.02rem; margin: 0 0 1.5rem; }
.cutout-form { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.cutout-form input {
flex: 1 1 16rem;
font-family: var(--body); font-size: 0.95rem;
padding: 0.85rem 1.1rem;
border-radius: var(--r-full);
border: 1px solid rgba(42, 28, 16, 0.25);
background: rgba(255, 255, 255, 0.5);
color: var(--ink);
}
.cutout-form input::placeholder { color: rgba(42, 28, 16, 0.5); }
.cutout-form input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cutout-form input.invalid { border-color: var(--accent); background: rgba(140, 47, 34, 0.08); }
.cutout-fine { font-size: 0.8rem; color: rgba(42, 28, 16, 0.6); margin: 0.9rem 0 0; }
.cutout-stamp {
position: absolute; top: 50%; right: clamp(1.5rem, 5vw, 3rem);
transform: translateY(-50%) rotate(-8deg);
width: 100px; height: 100px;
display: grid; place-content: center; text-align: center;
border-radius: var(--r-full);
border: 3px double var(--accent);
color: var(--accent);
font-family: var(--display); font-weight: 900;
}
.cutout-stamp span { font-size: 1.5rem; line-height: 1; }
.cutout-stamp small { font-size: 0.55rem; letter-spacing: 0.15em; text-transform: uppercase; font-family: var(--body); }
/* ===== Footer ===== */
.site-foot {
max-width: 1200px; margin: 0 auto;
display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
padding: 2rem clamp(1rem, 4vw, 3rem) 3rem;
border-top: 1px solid var(--line);
margin-top: 2rem;
}
.foot-brand { display: inline-flex; align-items: center; gap: 0.5rem; font-family: var(--display); font-weight: 700; }
.foot-fine { color: var(--muted); font-size: 0.82rem; margin: 0; }
/* ===== Toast ===== */
.toast {
position: fixed;
left: 50%; bottom: 24px;
transform: translate(-50%, 130%);
background: var(--ink);
color: var(--paper);
border: 1px solid var(--accent-2);
padding: 0.8rem 1.3rem;
border-radius: var(--r-full);
font-weight: 600; font-size: 0.9rem;
box-shadow: var(--shadow);
z-index: 10000;
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
max-width: 90vw;
}
.toast.show { transform: translate(-50%, 0); }
/* ===== Responsive ===== */
@media (max-width: 880px) {
.hero { grid-template-columns: 1fr; }
.hero-deck { order: -1; }
.gear-inner { grid-template-columns: 1fr; }
.gear-deck { order: -1; }
.why-grid { grid-template-columns: 1fr; }
.nav { display: none; }
}
@media (max-width: 520px) {
.site-head { gap: 0.8rem; }
.head-cta { display: none; }
.hero { padding-top: 2rem; }
.hero-stats { gap: 1.1rem; }
.hero-stats dd { font-size: 1.25rem; }
.section-head { align-items: flex-start; }
.genre-tabs { width: 100%; justify-content: space-between; overflow-x: auto; }
.press-grid { grid-template-columns: 1fr; }
.cutout-stamp { display: none; }
.cutout-form .btn { width: 100%; justify-content: center; }
.turntable { padding: 18px; }
.deck-platter { border-width: 8px; }
}
@media (prefers-reduced-motion: reduce) {
.record.is-spinning, .sleeve-disc, .ritual-disc, .deck-record,
.press-card:hover .sleeve-disc, .crackle.is-live span, .eq i { animation: none !important; }
html { scroll-behavior: auto; }
}/* Revolver Press — vinyl / analog retro landing
Vanilla JS only. No audio files: playback is simulated with timers + CSS. */
(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
/* ---------- catalog data ---------- */
var PRESSINGS = [
{
artist: "Neon Tides", album: "Midnight Reservoir", genre: "soul",
price: 32, disc: "#d99a4e",
art: "linear-gradient(160deg,#8c2f22,#3a1f14 70%),radial-gradient(circle at 70% 25%,rgba(217,154,78,.6),transparent 50%)"
},
{
artist: "Velvet Static", album: "Paper Lanterns", genre: "jazz",
price: 28, disc: "#8c2f22",
art: "linear-gradient(200deg,#2e2118,#46341f),radial-gradient(circle at 30% 70%,rgba(217,154,78,.7),transparent 55%)"
},
{
artist: "The Amber Hours", album: "Slow Combustion", genre: "folk",
price: 26, disc: "#c0a888",
art: "linear-gradient(140deg,#5a3a1c,#241a12),repeating-linear-gradient(90deg,rgba(217,154,78,.18) 0 8px,transparent 8px 18px)"
},
{
artist: "Coral & Smoke", album: "Tide Glass", genre: "soul",
price: 34, disc: "#d99a4e",
art: "radial-gradient(circle at 50% 30%,#d99a4e,#8c2f22 60%,#241a12)"
},
{
artist: "Birch Avenue", album: "Wintering", genre: "folk",
price: 24, disc: "#8c2f22",
art: "linear-gradient(180deg,#46341f,#241a12),radial-gradient(circle at 80% 20%,rgba(243,231,211,.25),transparent 45%)"
},
{
artist: "Loretta Vane", album: "Brass Cathedral", genre: "jazz",
price: 30, disc: "#d99a4e",
art: "conic-gradient(from 200deg at 50% 50%,#8c2f22,#d99a4e,#3a2a1d,#8c2f22)"
},
{
artist: "Sableffield", album: "Dust & Honey", genre: "folk",
price: 27, disc: "#c0a888",
art: "linear-gradient(120deg,#7a4a22,#2e2118),radial-gradient(circle at 25% 80%,rgba(217,154,78,.5),transparent 50%)"
},
{
artist: "Marble Choir", album: "Saltwater Hymn", genre: "soul",
price: 35, disc: "#8c2f22",
art: "linear-gradient(160deg,#a23a2b,#8c2f22 40%,#241a12)"
}
];
var GENRE_LABEL = { soul: "Soul", jazz: "Jazz", folk: "Folk" };
/* ---------- render press cards ---------- */
var grid = document.getElementById("pressGrid");
var cardRefs = [];
function fmtCount() {
return (Math.floor(Math.random() * 900) + 90) + "k plays";
}
PRESSINGS.forEach(function (p, i) {
var card = document.createElement("article");
card.className = "press-card";
card.dataset.genre = p.genre;
card.dataset.index = String(i);
card.style.setProperty("--disc", p.disc);
card.innerHTML =
'<div class="sleeve">' +
'<div class="sleeve-art" style="background-image:' + p.art + '"></div>' +
'<div class="sleeve-disc"></div>' +
'<span class="sleeve-title">' + p.album + "</span>" +
"</div>" +
'<div class="press-meta">' +
"<div>" +
'<p class="press-artist">' + p.artist + "</p>" +
'<p class="press-album">' + p.album + " · LP</p>" +
"</div>" +
'<span class="press-genre">' + GENRE_LABEL[p.genre] + "</span>" +
"</div>" +
'<div class="press-foot">' +
'<span class="press-price">$' + p.price + ' <small>180g</small></span>' +
'<div class="press-actions">' +
'<button class="icon-btn like-btn" type="button" aria-pressed="false" aria-label="Like ' + p.album + '"><span class="heart" aria-hidden="true"></span></button>' +
'<button class="icon-btn play-btn" type="button" aria-pressed="false" aria-label="Preview ' + p.album + '"><span class="play-ico" aria-hidden="true"></span></button>' +
'<button class="icon-btn cart" type="button" aria-label="Add ' + p.album + ' to cart">+</button>' +
"</div>" +
"</div>";
grid.appendChild(card);
cardRefs.push(card);
});
/* ---------- like toggles ---------- */
grid.addEventListener("click", function (e) {
var like = e.target.closest(".like-btn");
if (like) {
var on = like.getAttribute("aria-pressed") === "true";
like.setAttribute("aria-pressed", String(!on));
like.classList.remove("bump");
void like.offsetWidth; // reflow to restart animation
like.classList.add("bump");
var album = like.closest(".press-card").querySelector(".press-artist").textContent;
toast(on ? "Removed " + album + " from favourites" : "♥ Saved " + album + " to your crate");
return;
}
var cart = e.target.closest(".cart");
if (cart) {
var c = cart.closest(".press-card");
var name = c.querySelector(".sleeve-title").textContent;
bumpCart();
toast("Added “" + name + "” to your bag · " + cartCount + " item" + (cartCount > 1 ? "s" : ""));
return;
}
var play = e.target.closest(".play-btn");
if (play) {
togglePreview(play);
return;
}
});
/* cart counter */
var cartCount = 0;
function bumpCart() { cartCount += 1; }
/* ---------- card preview playback (only one at a time) ---------- */
var activePreview = null;
function togglePreview(btn) {
var card = btn.closest(".press-card");
var isOn = btn.getAttribute("aria-pressed") === "true";
if (activePreview && activePreview !== btn) {
activePreview.setAttribute("aria-pressed", "false");
activePreview.closest(".press-card").classList.remove("is-playing");
}
if (isOn) {
btn.setAttribute("aria-pressed", "false");
card.classList.remove("is-playing");
activePreview = null;
return;
}
btn.setAttribute("aria-pressed", "true");
card.classList.add("is-playing");
activePreview = btn;
var album = card.querySelector(".sleeve-title").textContent;
toast("Now previewing “" + album + "” — Side A");
}
/* ---------- genre tabs ---------- */
var tabs = Array.prototype.slice.call(document.querySelectorAll(".genre-tab"));
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
tabs.forEach(function (t) {
t.classList.toggle("is-active", t === tab);
t.setAttribute("aria-selected", String(t === tab));
});
var g = tab.dataset.genre;
var shown = 0;
cardRefs.forEach(function (c) {
var match = g === "all" || c.dataset.genre === g;
c.classList.toggle("is-hidden", !match);
if (match) shown += 1;
});
toast(g === "all" ? "Showing all " + shown + " pressings" : "Filtered to " + shown + " " + tab.textContent + " title" + (shown !== 1 ? "s" : ""));
});
});
/* ---------- hero turntable (record spin + tonearm + crackle) ---------- */
var record = document.getElementById("record");
var platter = document.getElementById("platter");
var tonearm = document.getElementById("tonearm");
var crackle = document.getElementById("crackle");
var nowSpinning = document.getElementById("nowSpinning");
var nsText = nowSpinning.querySelector(".ns-text");
var heroPlay = document.getElementById("heroPlay");
var heroPlayLabel = heroPlay.querySelector(".hero-play-label");
var spinning = false;
var elapsed = 0; // simulated seconds played
var spinTimer = null;
var TRACK_LEN = 222; // 3:42
function fmtTime(s) {
var m = Math.floor(s / 60);
var sec = Math.floor(s % 60);
return m + ":" + (sec < 10 ? "0" : "") + sec;
}
function setSpin(on) {
spinning = on;
record.classList.toggle("is-spinning", on);
tonearm.classList.toggle("is-down", on);
crackle.classList.toggle("is-live", on);
nowSpinning.classList.toggle("is-live", on);
platter.setAttribute("aria-pressed", String(on));
heroPlay.setAttribute("aria-pressed", String(on));
heroPlayLabel.textContent = on ? "Lift the needle" : "Drop the needle";
if (on) {
toast("Needle down — Midnight Reservoir, Side A");
tick();
spinTimer = setInterval(tick, 1000);
} else {
clearInterval(spinTimer);
spinTimer = null;
nsText.textContent = "Stopped — click the record";
}
}
function tick() {
elapsed += 1;
if (elapsed >= TRACK_LEN) {
elapsed = 0;
toast("Side A finished — flip the record");
setSpin(false);
return;
}
nsText.textContent = "Now spinning · " + fmtTime(elapsed) + " / " + fmtTime(TRACK_LEN);
}
function toggleSpin() { setSpin(!spinning); }
platter.addEventListener("click", toggleSpin);
platter.addEventListener("keydown", function (e) {
if (e.key === " " || e.key === "Enter") { e.preventDefault(); toggleSpin(); }
});
heroPlay.addEventListener("click", toggleSpin);
/* ---------- newsletter cut-out ---------- */
var form = document.getElementById("letterForm");
var email = document.getElementById("letterEmail");
form.addEventListener("submit", function (e) {
e.preventDefault();
var val = email.value.trim();
var ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
if (!ok) {
email.classList.add("invalid");
email.focus();
toast("Hmm — that email doesn’t look right");
return;
}
email.classList.remove("invalid");
email.value = "";
toast("You’re on the list — first dibs incoming ✉");
});
email.addEventListener("input", function () { email.classList.remove("invalid"); });
/* gentle intro nudge */
setTimeout(function () { toast("Tip: click the record to drop the needle"); }, 1100);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Revolver Press — Analog Reissues on Vinyl</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=Playfair+Display:ital,wght@0,500;0,700;0,800;0,900;1,500&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="grain" aria-hidden="true"></div>
<!-- ===== Header ===== -->
<header class="site-head">
<a class="brand" href="#top">
<span class="brand-mark" aria-hidden="true">
<span class="brand-disc"></span>
</span>
<span class="brand-name">Revolver Press</span>
</a>
<nav class="nav" aria-label="Primary">
<a href="#pressings">Pressings</a>
<a href="#why">Why analog</a>
<a href="#gear">Turntables</a>
<a href="#letter">The cut</a>
</nav>
<a class="btn btn-ghost head-cta" href="#pressings">Shop the press</a>
</header>
<main id="top">
<!-- ===== Hero ===== -->
<section class="hero">
<div class="hero-copy">
<p class="eyebrow">Est. 1971 · Analog reissue label</p>
<h1 class="hero-title">Records that<br /><em>breathe.</em></h1>
<p class="hero-sub">
Heavyweight 180-gram pressings cut from the original analog masters.
Warm, tactile, gloriously imperfect — the way the artists heard it.
</p>
<div class="hero-actions">
<a class="btn btn-solid" href="#pressings">Shop the press</a>
<button class="btn btn-ghost" id="heroPlay" type="button" aria-pressed="false">
<span class="play-ico" aria-hidden="true"></span>
<span class="hero-play-label">Drop the needle</span>
</button>
</div>
<dl class="hero-stats">
<div><dt>Pressed by hand</dt><dd>12,400+</dd></div>
<div><dt>Catalog titles</dt><dd>318</dd></div>
<div><dt>Avg. rating</dt><dd>4.9 / 5</dd></div>
</dl>
</div>
<div class="hero-deck">
<div class="turntable">
<div class="platter" id="platter" role="button" tabindex="0"
aria-pressed="false" aria-label="Play or pause the demo record">
<div class="record" id="record">
<div class="record-ring r1"></div>
<div class="record-ring r2"></div>
<div class="record-ring r3"></div>
<div class="record-label">
<span class="rl-top">Revolver</span>
<span class="rl-mid">Midnight<br />Reservoir</span>
<span class="rl-bot">Side A · 33⅓</span>
<span class="record-hole" aria-hidden="true"></span>
</div>
<div class="record-sheen" aria-hidden="true"></div>
</div>
</div>
<div class="tonearm" id="tonearm" aria-hidden="true">
<span class="tonearm-base"></span>
<span class="tonearm-rod"></span>
<span class="tonearm-head"></span>
</div>
<div class="now-spinning" id="nowSpinning">
<span class="ns-dot" aria-hidden="true"></span>
<span class="ns-text">Stopped — click the record</span>
</div>
<div class="crackle" id="crackle" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span>
</div>
</div>
</div>
</section>
<!-- ===== Featured pressings ===== -->
<section class="section" id="pressings">
<div class="section-head">
<div>
<p class="eyebrow">Featured pressings</p>
<h2 class="section-title">This season’s cuts</h2>
</div>
<div class="genre-tabs" role="tablist" aria-label="Filter pressings by genre">
<button class="genre-tab is-active" role="tab" aria-selected="true" data-genre="all">All</button>
<button class="genre-tab" role="tab" aria-selected="false" data-genre="soul">Soul</button>
<button class="genre-tab" role="tab" aria-selected="false" data-genre="jazz">Jazz</button>
<button class="genre-tab" role="tab" aria-selected="false" data-genre="folk">Folk</button>
</div>
</div>
<div class="press-grid" id="pressGrid"><!-- cards injected by JS --></div>
</section>
<!-- ===== Why analog ===== -->
<section class="section why" id="why">
<div class="section-head center">
<p class="eyebrow">Why analog</p>
<h2 class="section-title">Three reasons we never went digital</h2>
</div>
<div class="why-grid">
<article class="why-card">
<span class="why-ico" aria-hidden="true">
<span class="eq mini"><i></i><i></i><i></i><i></i></span>
</span>
<h3>Warmth</h3>
<p>Analog tape rounds the highs and fattens the lows. You don’t just hear the
record — you feel the room it was cut in.</p>
</article>
<article class="why-card">
<span class="why-ico" aria-hidden="true">
<span class="ritual-disc"></span>
</span>
<h3>Ritual</h3>
<p>Sleeve in hand, needle down, side over. A deliberate forty minutes that asks you to
actually listen instead of skip.</p>
</article>
<article class="why-card">
<span class="why-ico" aria-hidden="true">
<span class="art-frame"></span>
</span>
<h3>Artwork</h3>
<p>Twelve inches of cover art, liner notes and inner sleeves. The album as an object you
can hold, frame and pass on.</p>
</article>
</div>
</section>
<!-- ===== Turntable / equipment band ===== -->
<section class="section gear" id="gear">
<div class="gear-inner">
<div class="gear-copy">
<p class="eyebrow">The hardware</p>
<h2 class="section-title">Pair it with the right deck</h2>
<p class="gear-lead">
A great pressing deserves a great turntable. We hand-pick belt-drive decks with
tonearms tuned for our heavyweight cuts — no compromise, no presets.
</p>
<ul class="gear-list">
<li><span>Belt-drive isolation</span><em>−68 dB rumble</em></li>
<li><span>Hand-balanced tonearm</span><em>1.8 g tracking</em></li>
<li><span>Walnut & brass plinth</span><em>made to outlive you</em></li>
</ul>
<a class="btn btn-solid" href="#letter">Browse the decks</a>
</div>
<figure class="gear-deck" aria-hidden="true">
<div class="deck-platter">
<div class="deck-record" id="deckRecord">
<span class="deck-label"></span>
</div>
</div>
<div class="deck-arm"></div>
<figcaption class="deck-cap">Model 71 · Walnut & Brass</figcaption>
</figure>
</div>
</section>
<!-- ===== Newsletter cut-out ===== -->
<section class="section" id="letter">
<div class="cutout">
<div class="cutout-perf" aria-hidden="true"></div>
<div class="cutout-body">
<p class="eyebrow">The cut · monthly dispatch</p>
<h2 class="cutout-title">Get first dibs on every pressing</h2>
<p class="cutout-sub">
New reissues sell out in hours. Subscribers get the pre-sale link a full day early,
plus a free 7-inch with every third order.
</p>
<form class="cutout-form" id="letterForm" novalidate>
<label class="sr-only" for="letterEmail">Email address</label>
<input id="letterEmail" name="email" type="email" inputmode="email"
placeholder="[email protected]" autocomplete="email" required />
<button class="btn btn-solid" type="submit">Mail me the cut</button>
</form>
<p class="cutout-fine">No spam, ever. One dispatch a month. Unsubscribe in a click.</p>
</div>
<div class="cutout-stamp" aria-hidden="true">
<span>180g</span><small>heavyweight</small>
</div>
</div>
</section>
</main>
<footer class="site-foot">
<div class="foot-brand">
<span class="brand-mark sm" aria-hidden="true"><span class="brand-disc"></span></span>
Revolver Press
</div>
<p class="foot-fine">© 1971–2026 Revolver Press · Cut at 45rpm in Margate · Fictional demo store</p>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Vinyl / Analog Retro Landing
A nostalgic, premium landing page for Revolver Press, a fictional analog reissue label and record shop. The palette leans into warm sepia, cream and oxblood with a vintage serif (Playfair Display) for headings and Inter for body and UI. A fixed film-grain overlay and tactile paper textures give the whole page an aged, hand-pressed feel.
The hero pairs a vintage serif title and a “Shop the press” CTA with a fully CSS-drawn turntable: a grooved record, a centered oxblood label, a swinging tonearm and a crackle equalizer. Click the record (or the “Drop the needle” button) to start it spinning — the tonearm drops, the equalizer animates, and a now-spinning readout counts the simulated playback time. Below, a featured-pressings grid shows record cards whose disc peeks out from behind the sleeve and spins on hover; each card has like (heart), preview and add-to-cart controls, and a genre tab bar filters the grid live. The page rounds out with a “why analog” feature trio (warmth, ritual, artwork), a turntable equipment band with a second spinning deck, and a perforated paper cut-out newsletter with email validation.
All interactions are vanilla JS with no external libraries and no audio files: playback, previews and counters are simulated with timers and CSS transforms, and a small toast() helper surfaces feedback. The layout is responsive down to ~360px and respects prefers-reduced-motion.
Illustrative UI only — fictional artists, albums, tracks, and data. No real audio playback.