// V1 — PASTEL MEADOW
// Vaporwave-meets-meadow: pastel sky gradient, 3D animated grass, glossy chrome serif title,
// floating glass player. Single viewport. Audio-reactive.
const V1 = () => {
const radio = useEphemeralRadio();
const { state, track, listeners, toggle, levels, live } = radio;
const playing = state === "playing";
const [panel, setPanel] = React.useState(null); // null | "about" | "contact" | "streams"
const isLive = !!live?.isLive;
const streamer = live?.streamer || "live operator";
// Floating particles (pollen / dust motes)
const motes = React.useMemo(() => Array.from({ length: 24 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: 2 + Math.random() * 4,
dur: 14 + Math.random() * 18,
delay: -Math.random() * 30,
})), []);
// Grass blades — distributed across the bottom
const blades = React.useMemo(() => Array.from({ length: 70 }, (_, i) => {
const x = (i / 70) * 100 + (Math.random() - 0.5) * 1.4;
return {
id: i,
x,
h: 60 + Math.random() * 90,
sway: 1.4 + Math.random() * 1.6,
delay: -Math.random() * 4,
hue: 95 + Math.random() * 30,
light: 55 + Math.random() * 18,
z: Math.random(),
};
}).sort((a,b) => a.z - b.z), []);
// Reactive level: 0..1, biased toward bass
const level = React.useMemo(() => {
if (!playing) return 0;
let s = 0; const n = Math.min(16, levels.length);
for (let i = 0; i < n; i++) s += levels[i];
return Math.min(1, (s / n) / 200);
}, [levels, playing]);
return (
{/* Sky */}
{/* Distant clouds */}
{/* Pollen motes */}
{motes.map(m => (
))}
{/* Distant hills */}
{/* Header */}
{/* Hero wordmark */}
▸ a transmission, briefly
ephemeral
fm
·
MMXXVI
nothing is permanent. the song you are hearing
will never play this way again. touch grass,
stream sound, then let it pass.
{/* Player card */}
{/* Live DJ banner — only shown when a streamer has taken over from AutoDJ */}
{isLive && (
LIVE DJ
{streamer}
on air · no archive · no rewinds
)}
{/* Footer ticker */}
{/* Grass — drawn last, top of stack */}
{/* Lens flare */}
{/* Modal panels */}
{panel &&
setPanel(null)} />}
);
};
const StreamRow = ({ label, sub, url }) => {
const [copied, setCopied] = React.useState(false);
const copy = async () => {
try {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(()=>setCopied(false), 1600);
} catch {
const ta = document.createElement("textarea");
ta.value = url; document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); setCopied(true); setTimeout(()=>setCopied(false),1600); } catch {}
ta.remove();
}
};
return (
{label}
{sub}
{url}
{copied ? "copied ◉" : "copy ▸"}
);
};
const Panel = ({ kind, onClose }) => {
const isAbout = kind === "about";
const isStreams = kind === "streams";
const eyebrowTxt = isAbout ? "// about.txt" : isStreams ? "// streams.txt" : "// contact.txt";
return (
e.stopPropagation()}>
×
▸ {eyebrowTxt}
{isStreams ? (
<>
tune in elsewhere
paste either url into any third-party player — VLC, foobar2000,
Transistor, RadioDroid, Broadcasts, your car stereo if it'll have it.
the signal is the same. only the wrapper changes.
flac is the way we intend it. mp3 is provided as a courtesy
for hardware and apps that won't speak anything else.
◉ live stream · no archive · no rewinds
◉ same signal · different wrappers
>
) : isAbout ? (
<>
lossless transmission, fleeting moment
Ephemeral FM broadcasts in lossless FLAC
wrapped in Ogg — 24-bit, 44.1 kHz, bit-perfect from the stream out.
no MP3 fog. no codec haze. no re-encoded ghosts of a song you almost heard.
source files are as hi-res as we can get them, and we keep upgrading them where we can.
the paradox is the point. we use the most permanent format we can find
to deliver the most impermanent thing we know: a moment. the file is uncompressed;
the moment is non-renewable. the bits are exact; your experience is unique to when you listen.
no rewinds. no on-demand. no recommendation engine pretending to know you.
the playlist is curated, then forgotten. the schedule is when we feel like it.
the recordings are not retained. what you missed, you missed.
what you caught — that song, the way the grass looked while you heard it —
belongs to you, briefly, and to no one else.
a handful of trusted operators hold uplink credentials.
when one of them keys in, the station yields — the playlist halts,
the broadcast becomes them, live, into the void.
no schedule. no announcement. no archive.
a live DJ transmission is exactly as long as it lasts,
and it lasts exactly once. tune in. find out. or don't.
◉ flac-in-ogg · 24-bit · 44.1 khz · always lossless
◉ touch grass between transmissions
>
) : (
<>
send a signal
demos, dedications, complaints about the weather:
dj@ephemeral.club
we read everything. we reply when the spirit moves us.
attach an mp3, a flac, a field recording, a photo of where
you are listening from. the more ephemeral the better.
◐ replies are not guaranteed
◐ replies are sincere when sent
◐ no PR pitches please
>
)}
);
};
// ----- Sleek volume popout: speaker button + reveal-on-click slider -----
const VolumePopout = ({ volume, setVolume }) => {
const [open, setOpen] = React.useState(false);
const lastRef = React.useRef(volume > 0 ? volume : 0.8);
const wrapRef = React.useRef(null);
React.useEffect(() => { if (volume > 0) lastRef.current = volume; }, [volume]);
React.useEffect(() => {
if (!open) return;
const onDoc = (e) => { if (!wrapRef.current?.contains(e.target)) setOpen(false); };
document.addEventListener("pointerdown", onDoc);
return () => document.removeEventListener("pointerdown", onDoc);
}, [open]);
const tier = volume === 0 ? 0 : volume < 0.34 ? 1 : volume < 0.67 ? 2 : 3;
const toggleMute = (e) => { e.stopPropagation(); if (volume > 0) { lastRef.current = volume; setVolume(0); } else { setVolume(lastRef.current || 0.8); } };
return (
);
};
// Resolve a human store name from a purchase URL so the button can adapt
// its wording. Returns "bandcamp", "itunes", or null (generic "buy").
function buyStore(url) {
if (!url) return null;
const u = url.toLowerCase();
if (u.includes("bandcamp.com")) return "bandcamp";
if (u.includes("qobuz.com") || u.includes("open.qobuz.com")) return "qobuz";
if (u.includes("itunes.apple.com") || u.includes("music.apple.com") || u.includes("apple.co")) return "itunes";
return null;
}
// Buy tab — a slim ribbon that slides up out of the player card's top edge
// ONLY when the current track carries a store URL. Stays mounted so it can
// animate in/out; wording adapts to the destination but stays understated.
const BuyTab = ({ url }) => {
const store = buyStore(url);
const label = store || "purchase";
const nice = store ? store[0].toUpperCase() + store.slice(1) : "this track";
return (
{label}
▸
);
};
// ----- Player card (used by V1 only here, but designed to be reused) -----
const PlayerCard = ({ radio, level, playing }) => {
const { track, toggle, state, listeners, volume, setVolume, live } = radio;
const elapsed = useElapsed(track, playing);
const isLive = !!live?.isLive;
return (
{/* Buy tab — slides out of the card's top edge only when the track is for sale */}
{track?.buyUrl &&
}
{/* Volume popout — anchored to player card top-right */}
{/* Album art */}
{!track?.art &&
}
{state === "loading"
?
: (playing
?
:
)}
{/* Meta */}
{isLive
? <> LIVE DJ ▸ {live?.streamer || "live operator"} >
: <>NOW DRIFTING ▸ FLAC · 24-BIT · 44.1 KHZ >
}
{track?.artist || "—"}
{track?.album &&
from {track.album}
}
{/* Visualizer */}
{/* Progress */}
{fmtTime(elapsed)}
{listeners != null ? `${listeners} listening · live` : "live"}
{track?.duration ? fmtTime(track.duration) : "∞"}
);
};
// Marquee — slides text back and forth ONLY when it overflows its container.
const Marquee = ({ text, className, style }) => {
const wrap = React.useRef(null);
const inner = React.useRef(null);
const [shift, setShift] = React.useState(0);
React.useLayoutEffect(() => {
const w = wrap.current, t = inner.current;
if (!w || !t) return;
const measure = () => {
const overflow = t.scrollWidth - w.clientWidth;
if (overflow > 4) setShift(-overflow);
else setShift(0);
};
measure();
const ro = new ResizeObserver(measure);
ro.observe(w); ro.observe(t);
return () => ro.disconnect();
}, [text]);
const isOverflow = shift < 0;
return (
{text}
);
};
const ArtPlaceholder = () => (
);
const Visualizer = ({ levels, playing }) => {
const bars = 28;
const arr = React.useMemo(() => {
const out = new Array(bars).fill(0);
if (!playing || !levels?.length) return out;
const step = Math.floor(levels.length / bars) || 1;
for (let i = 0; i < bars; i++) {
let s = 0; let c = 0;
for (let k = i * step; k < (i + 1) * step && k < levels.length; k++) { s += levels[k]; c++; }
out[i] = c ? s / c : 0;
}
return out;
}, [levels, playing]);
return (
);
};
function useElapsed(track, playing) {
const [t, setT] = React.useState(0);
React.useEffect(() => {
if (!track) return;
const base = track.elapsed || 0;
const start = Date.now();
setT(base);
if (!playing) return;
const id = setInterval(() => {
setT(base + (Date.now() - start) / 1000);
}, 500);
return () => clearInterval(id);
}, [track, playing]);
return t;
}
function fmtTime(s) {
if (!isFinite(s)) return "0:00";
s = Math.max(0, Math.floor(s));
const m = Math.floor(s / 60); const r = s % 60;
return `${m}:${r.toString().padStart(2, "0")}`;
}
const v1CSS = `
@keyframes mote { 0%{transform:translateY(0) translateX(0); opacity:0} 10%{opacity:.9} 90%{opacity:.9} 100%{transform:translateY(-220px) translateX(40px); opacity:0} }
.mote { position:absolute; border-radius:50%; background:radial-gradient(circle, #fff 0%, rgba(255,255,255,.6) 40%, transparent 70%); box-shadow:0 0 8px rgba(255,255,255,.6); animation:mote linear infinite; pointer-events:none; }
@keyframes blink { 0%,49%{opacity:1} 50%,100%{opacity:0} }
.blink { animation: blink 1.2s steps(1) infinite; }
@keyframes sway { 0%,100%{transform:rotate(-3deg)} 50%{transform:rotate(4deg)} }
.blade { position:absolute; bottom:0; width:3px; transform-origin:bottom center; clip-path:polygon(50% 0%, 100% 100%, 0% 100%); animation: sway ease-in-out infinite; filter:drop-shadow(0 -1px 0 rgba(255,255,255,.2)); transition:height .25s ease; }
@keyframes drift { 0%{transform:translateX(-100px)} 100%{transform:translateX(calc(100vw + 100px))} }
.cloudish { animation: drift linear infinite; }
@keyframes ticker { 0%{transform:translateX(0)} 100%{transform:translateX(-50%)} }
.ticker { display:flex; gap:48px; white-space:nowrap; animation: ticker 60s linear infinite; }
.ticker > span { font-family:'JetBrains Mono', monospace; font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:#3a3556; }
.spinner { width:22px; height:22px; border:2.5px solid rgba(255,255,255,.3); border-top-color:white; border-radius:50%; animation: spin .9s linear infinite; }
@keyframes spin { to { transform:rotate(360deg) } }
@keyframes fadein { from { opacity:0 } to { opacity:1 } }
/* ---------- live DJ ---------- */
@keyframes livePulse {
0%, 100% { transform:scale(1); box-shadow:0 0 12px 2px rgba(255,255,255,.85), 0 0 0 3px rgba(255,255,255,.2) }
50% { transform:scale(1.25); box-shadow:0 0 18px 4px rgba(255,255,255,.95), 0 0 0 6px rgba(255,255,255,.0) }
}
.v1-liveDotInline {
display:inline-block; width:8px; height:8px; border-radius:50%;
background:#d6275a; margin-right:8px; vertical-align:1px;
box-shadow:0 0 8px 1px rgba(214,39,90,.7);
animation: livePulse 1.2s ease-in-out infinite;
}
.v1-player.is-live { box-shadow: 0 30px 80px -20px rgba(214,39,90,.45), 0 8px 24px -8px rgba(214,39,90,.35), inset 0 1px 0 rgba(255,255,255,.9), 0 0 0 1.5px rgba(214,39,90,.35); }
/* ---------- sleek volume popout (desktop only) ---------- */
.v1-vol {
position:absolute; bottom:14px; left:14px; z-index:5;
display:flex; flex-direction:row-reverse; align-items:center; gap:0;
height:24px;
}
.v1-volBtn {
width:24px; height:24px; border-radius:50%; flex-shrink:0;
border:none; background:transparent;
color:rgba(58,53,86,.5); cursor:pointer;
display:inline-flex; align-items:center; justify-content:center;
transition: color .18s ease, background .18s ease;
padding:0;
}
.v1-volBtn:hover { color:#7a4a8c; background:rgba(58,53,86,.06); }
.v1-vol.is-open .v1-volBtn { color:#7a4a8c; background:rgba(58,53,86,.08); }
.v1-volRange {
-webkit-appearance:none; appearance:none;
width:0; height:2px; border-radius:99px; outline:none; border:none;
background:linear-gradient(90deg, #7a4a8c 0%, #b88dc8 var(--vol), rgba(58,53,86,.18) var(--vol), rgba(58,53,86,.18) 100%);
opacity:0; pointer-events:none;
transition: width .3s cubic-bezier(.2,.85,.25,1), opacity .2s ease, margin-right .3s cubic-bezier(.2,.85,.25,1);
margin-right:0;
}
.v1-vol.is-open .v1-volRange { width:136px; opacity:1; pointer-events:auto; margin-left:8px; margin-right:0; }
.v1-volRange::-webkit-slider-thumb {
-webkit-appearance:none; appearance:none;
width:10px; height:10px; border-radius:50%;
background:#7a4a8c; border:none;
box-shadow:0 1px 3px rgba(80,40,120,.3);
cursor:grab;
}
.v1-volRange::-webkit-slider-thumb:active { cursor:grabbing; }
.v1-volRange::-moz-range-thumb {
width:10px; height:10px; border-radius:50%;
background:#7a4a8c; border:none;
box-shadow:0 1px 3px rgba(80,40,120,.3);
cursor:grab;
}
/* ---------- mobile ---------- */
@media (max-width: 760px) {
.v1-root { height:auto !important; min-height:100dvh; overflow-x:hidden !important; overflow-y:visible !important; padding-bottom:0 !important; width:100% !important; max-width:100vw !important; background:linear-gradient(180deg, #f9e9d9 0%, #f4d0c8 22%, #e8b8c8 45%, #d8c0e0 68%, #b8d4c0 88%, #88b894 100%) !important; }
.v1-header { flex-direction:column; gap:14px; padding:18px 18px !important; align-items:flex-start !important; width:100%; box-sizing:border-box; }
.v1-brandRow { flex-wrap:wrap; gap:10px !important; max-width:100%; }
.v1-main { display:flex !important; flex-direction:column; gap:28px; padding:20px 18px 40px !important; height:auto !important; width:100% !important; max-width:100vw !important; box-sizing:border-box; align-items:stretch !important; }
.v1-heroBlock { width:100%; max-width:100%; min-width:0; }
.v1-wordmark { line-height:.92 !important; max-width:100%; }
.v1-wmLine1 { font-size:18vw !important; line-height:.9 !important; display:block; max-width:100%; }
.v1-wmLine2 { gap:10px !important; flex-wrap:wrap; max-width:100%; }
.v1-wmEm { font-size:15vw !important; line-height:1 !important; }
.v1-wmDot { font-size:9vw !important; }
.v1-wmYear { font-size:10px !important; margin-left:0 !important; }
.v1-manifesto { font-size:15px !important; max-width:100% !important; margin-top:22px !important; }
.v1-eyebrow { font-size:10px !important; margin-bottom:12px !important; }
.v1-player { width:100% !important; max-width:100% !important; box-sizing:border-box; }
.v1-playerInner { grid-template-columns:1fr !important; padding:18px !important; gap:18px !important; box-sizing:border-box; width:100%; }
.v1-artBtn { width:100% !important; height:auto !important; aspect-ratio:1/1; max-width:280px; margin:0 auto !important; display:block; }
.v1-art { aspect-ratio:1/1; height:auto !important; width:100% !important; }
.v1-meta { min-width:0; max-width:100%; overflow:hidden; }
.v1-title { font-size:24px !important; }
.v1-vol { display:none !important; }
.v1-liveBanner { position:relative !important; top:auto !important; left:auto !important; transform:none !important; margin:0 auto 12px; max-width:fit-content; flex-wrap:wrap; justify-content:center; padding:8px 14px !important; gap:10px !important; }
.v1-liveStreamer { font-size:16px !important; }
.v1-liveTail { display:none; }
.v1-footer { position:relative !important; margin-top:32px; padding-top:18px; background:linear-gradient(180deg, transparent 0%, rgba(170,206,168,.35) 60%, rgba(140,190,150,.65) 100%); }
.v1-tickerWrap { background:rgba(255,255,255,.42) !important; }
.v1-grassLayer { display:block !important; position:relative !important; height:96px !important; width:100% !important; background:linear-gradient(180deg, rgba(140,190,150,.65) 0%, rgba(110,170,124,.92) 55%, rgba(82,142,100,1) 100%); margin:0 !important; bottom:auto !important; overflow:hidden; }
.v1-grassLayer .blade { opacity:.55 !important; }
.v1-hills { display:none !important; }
.v1-sun { width:70px !important; height:70px !important; top:6% !important; right:8% !important; }
.v1-sunGlow { width:70px !important; height:70px !important; top:6% !important; right:8% !important; }
.v1-flare { display:none; }
.v1-panelCard { width:92% !important; max-height:88dvh !important; }
.v1-panelTitle { font-size:30px !important; }
.v1-bigEmail { font-size:22px !important; word-break:break-all; }
}
/* ---------- buy tab (pops out of the player card top edge) ---------- */
@keyframes v1PopTab { from { transform:translateY(0); opacity:0; } to { transform:translateY(-100%); opacity:1; } }
.v1-buyTab {
position:absolute; top:0; right:30px; z-index:1;
display:inline-flex; align-items:center; gap:7px;
padding:7px 14px 9px;
border-radius:13px 13px 0 0;
font-family:'JetBrains Mono', monospace; font-size:9.5px; letter-spacing:.22em;
text-transform:uppercase; text-decoration:none; color:#d6275a;
background:linear-gradient(180deg, rgba(255,255,255,.94), rgba(255,236,244,.82));
border:1px solid rgba(214,39,90,.22); border-bottom:none;
box-shadow:0 -7px 18px -12px rgba(214,39,90,.55), inset 0 1px 0 rgba(255,255,255,.92);
transform:translateY(-100%);
animation:v1PopTab .55s cubic-bezier(.2,.9,.25,1) both;
transition:transform .25s ease, box-shadow .25s ease, color .25s ease, border-color .25s ease;
}
.v1-buyTab:hover { transform:translateY(calc(-100% - 3px)); color:#b81f49; border-color:rgba(214,39,90,.4); box-shadow:0 -10px 22px -12px rgba(214,39,90,.6), inset 0 1px 0 rgba(255,255,255,.95); }
@media (prefers-reduced-motion: reduce) { .v1-buyTab { animation:none; } }
/* ---------- marquee for long titles ---------- */
.v1-marquee { display:block; overflow:hidden; white-space:nowrap; max-width:100%; }
.v1-marquee.is-overflow { mask-image:linear-gradient(90deg, transparent 0, #000 8%, #000 92%, transparent 100%); -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 8%, #000 92%, transparent 100%); }
.v1-marquee-track { display:inline-block; padding-left:0; }
.v1-marquee.is-overflow .v1-marquee-track { animation: v1MarqueeSlide 14s ease-in-out infinite alternate; }
@keyframes v1MarqueeSlide {
0% { transform:translateX(0); }
20% { transform:translateX(0); }
80% { transform:translateX(var(--marquee-shift, 0)); }
100% { transform:translateX(var(--marquee-shift, 0)); }
}
`;
const v1Styles = {
root: {
position:"relative", width:"100%", height:"100%", overflow:"hidden",
fontFamily:"'Inter', system-ui, sans-serif", color:"#2a2548",
background:"#fce6d4",
},
sky: {
position:"absolute", inset:0,
background: `
radial-gradient(ellipse 90% 60% at 78% 18%, rgba(255,236,210,1) 0%, rgba(255,236,210,0) 60%),
radial-gradient(ellipse 70% 50% at 22% 30%, rgba(212,196,255,.8) 0%, rgba(212,196,255,0) 65%),
linear-gradient(180deg,
#f8c8e0 0%,
#f4d0c4 18%,
#f7dac8 32%,
#e6d2f0 50%,
#c8d8f0 68%,
#b8e0d8 82%,
#c4e8c4 100%)`,
},
sun: {
position:"absolute", top:"14%", right:"18%",
width:120, height:120, borderRadius:"50%",
background:"radial-gradient(circle at 35% 30%, #fffaf0 0%, #ffe8c4 35%, #ffc89c 70%, #ff9c8a 100%)",
boxShadow:"0 0 80px 20px rgba(255,200,156,.45)",
},
sunGlow: {
position:"absolute", top:"14%", right:"18%",
width:120, height:120, borderRadius:"50%",
boxShadow:"0 0 200px 80px rgba(255,210,180,.4)",
pointerEvents:"none",
},
cloud: {
position:"absolute", width:200, height:60,
background:"radial-gradient(ellipse, rgba(255,255,255,.85) 0%, rgba(255,255,255,0) 65%)",
filter:"blur(8px)",
animation: "drift linear infinite",
willChange:"transform",
},
hills: { position:"absolute", bottom:120, left:0, width:"100%", height:240, zIndex:2 },
header: {
position:"relative", zIndex:5,
padding:"24px 40px",
display:"flex", justifyContent:"space-between", alignItems:"center",
},
brandRow: { display:"flex", gap:14, alignItems:"center" },
brandDot: { color:"#d65a8a", fontSize:14, filter:"drop-shadow(0 0 6px rgba(214,90,138,.6))" },
brandMono: {
fontFamily:"'JetBrains Mono', monospace", fontSize:11, letterSpacing:".22em",
textTransform:"uppercase", color:"#3a3556",
},
nav: { display:"flex", gap:24 },
navLink: {
fontFamily:"'JetBrains Mono', monospace", fontSize:11, letterSpacing:".22em",
textTransform:"uppercase", color:"#3a3556", textDecoration:"none",
padding:"6px 12px",
borderRadius:99, border:"1px solid rgba(58,53,86,.2)",
background:"rgba(255,255,255,.4)", backdropFilter:"blur(8px)",
},
main: {
position:"relative", zIndex:5,
display:"grid", gridTemplateColumns:"1.2fr 1fr",
alignItems:"center", gap:60,
padding:"40px 60px 200px",
height:"calc(100% - 200px)",
},
heroBlock: { },
eyebrow: {
fontFamily:"'JetBrains Mono', monospace", fontSize:11,
letterSpacing:".3em", textTransform:"uppercase",
color:"#7a4a8c", marginBottom:18,
},
wordmark: {
margin:0, lineHeight:.86,
fontFamily:"'Cormorant Garamond', 'EB Garamond', Georgia, serif",
fontWeight:300, color:"#2a2548",
letterSpacing:"-.04em",
},
wmLine1: {
display:"block", fontSize:"clamp(72px, 10vw, 148px)",
fontStyle:"italic",
background:"linear-gradient(180deg, #2a2548 0%, #5a4a8a 50%, #d68aa8 100%)",
WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent",
textShadow:"0 1px 0 rgba(255,255,255,.4)",
},
wmLine2: { display:"flex", alignItems:"baseline", gap:18, marginTop:6 },
wmEm: {
fontSize:"clamp(60px, 8vw, 120px)", fontWeight:600, fontStyle:"italic",
color:"#d65a8a",
textShadow:"0 0 30px rgba(214,90,138,.4)",
},
wmDot: { fontSize:"clamp(40px, 6vw, 80px)", color:"#7a4a8c", opacity:.5 },
wmYear: {
fontFamily:"'JetBrains Mono', monospace", fontSize:14, letterSpacing:".3em",
color:"#7a4a8c", alignSelf:"center", marginLeft:6,
},
manifesto: {
marginTop:32, fontSize:17, lineHeight:1.65, maxWidth:480,
color:"#3a3556", fontFamily:"'Cormorant Garamond', Georgia, serif",
fontStyle:"italic", fontWeight:400,
},
manifestoEm: { color:"#d65a8a", fontWeight:600, fontStyle:"normal", fontFamily:"'Inter', sans-serif", fontSize:14, letterSpacing:".05em", textTransform:"uppercase" },
player: {
position:"relative", padding:2, borderRadius:24,
background:"linear-gradient(145deg, rgba(255,255,255,.9), rgba(255,255,255,.2))",
boxShadow:"0 30px 80px -20px rgba(80,40,120,.35), 0 8px 24px -8px rgba(80,40,120,.3), inset 0 1px 0 rgba(255,255,255,.9)",
backdropFilter:"blur(20px)",
},
playerInner: {
background:"linear-gradient(160deg, rgba(255,255,255,.65), rgba(248,232,250,.55))",
border:"1px solid rgba(255,255,255,.5)",
borderRadius:22, padding:22, position:"relative",
display:"grid", gridTemplateColumns:"160px 1fr", gap:22,
},
artBtn: {
border:"none", padding:0, background:"transparent", cursor:"pointer",
width:160, height:160, borderRadius:14, position:"relative",
},
art: {
width:"100%", height:"100%", borderRadius:14, overflow:"hidden",
backgroundSize:"cover", backgroundPosition:"center",
boxShadow:"0 14px 30px -8px rgba(80,40,120,.4), inset 0 0 0 1px rgba(255,255,255,.4)",
position:"relative", transition:"transform .3s ease",
},
artGloss: {
position:"absolute", inset:0, borderRadius:14, pointerEvents:"none",
background:"linear-gradient(160deg, rgba(255,255,255,.5) 0%, rgba(255,255,255,0) 35%, rgba(255,255,255,0) 75%, rgba(255,255,255,.15) 100%)",
},
playOverlay: {
position:"absolute", inset:0, display:"flex", alignItems:"center", justifyContent:"center",
background:"radial-gradient(circle at center, rgba(0,0,0,.35), rgba(0,0,0,.55))",
borderRadius:14, transition:"opacity .25s ease",
},
meta: { display:"flex", flexDirection:"column", justifyContent:"center", minWidth:0, position:"relative" },
metaLabel: {
fontFamily:"'JetBrains Mono', monospace", fontSize:10, letterSpacing:".3em",
color:"#d65a8a", textTransform:"uppercase", marginBottom:6,
},
title: {
fontFamily:"'Cormorant Garamond', Georgia, serif",
fontSize:28, lineHeight:1.1, fontStyle:"italic",
color:"#2a2548", marginBottom:4,
width:"100%",
},
artist: { fontSize:14, color:"#3a3556", letterSpacing:".02em", marginBottom:2 },
album: { fontSize:11, color:"#7a4a8c", marginBottom:10, opacity:.85, fontFamily:"'Cormorant Garamond', serif" },
viz: { display:"flex", gap:2, alignItems:"flex-end", height:42, marginTop:8, marginBottom:10 },
vizBar: {
flex:1, minHeight:3,
background:"linear-gradient(to top, #d65a8a, #c690d8 50%, #8aaadd 100%)",
borderRadius:1, transition:"height .08s linear",
},
progress: {
height:4, borderRadius:99,
background:"rgba(58,53,86,.15)", overflow:"hidden",
},
progressFill: {
height:"100%",
background:"linear-gradient(90deg, #d65a8a, #c690d8 50%, #8aaadd)",
transition:"width .5s linear",
},
progRow: {
display:"flex", justifyContent:"space-between", marginTop:6,
fontFamily:"'JetBrains Mono', monospace", fontSize:10, letterSpacing:".15em",
color:"#3a3556", textTransform:"uppercase",
},
footer: {
position:"absolute", bottom:0, left:0, right:0, zIndex:6,
paddingBottom:6,
},
tickerWrap: {
overflow:"hidden", padding:"6px 0",
background:"rgba(255,255,255,.35)", backdropFilter:"blur(10px)",
borderTop:"1px solid rgba(58,53,86,.15)",
borderBottom:"1px solid rgba(58,53,86,.15)",
},
socials: {
display:"flex", gap:18, justifyContent:"center", padding:"8px 0",
},
social: {
fontFamily:"'JetBrains Mono', monospace", fontSize:10, letterSpacing:".25em",
color:"#3a3556", textDecoration:"none", textTransform:"uppercase",
},
grassLayer: {
position:"absolute", bottom:0, left:0, right:0, height:140,
pointerEvents:"none", zIndex:7,
},
flare: {
position:"absolute", top:"14%", right:"18%",
width:300, height:300, borderRadius:"50%",
background:"radial-gradient(circle, rgba(255,240,200,.4) 0%, rgba(255,200,170,.15) 35%, transparent 65%)",
transform:"translate(50%, -50%)", pointerEvents:"none", zIndex:3,
mixBlendMode:"screen",
},
panelBackdrop: {
position:"absolute", inset:0, zIndex:20,
background:"radial-gradient(ellipse at center, rgba(80,40,120,.25), rgba(40,20,60,.55))",
backdropFilter:"blur(8px)",
display:"flex", alignItems:"center", justifyContent:"center",
animation:"fadein .25s ease",
},
panelCard: {
position:"relative", width:"min(620px, 86%)", maxHeight:"82%",
overflow:"auto",
padding:2, borderRadius:24,
background:"linear-gradient(145deg, rgba(255,255,255,.95), rgba(255,255,255,.4))",
boxShadow:"0 30px 80px -20px rgba(40,20,60,.55), 0 12px 24px -8px rgba(40,20,60,.4), inset 0 1px 0 rgba(255,255,255,.95)",
},
panelClose: {
position:"absolute", top:14, right:18, zIndex:2,
width:32, height:32, borderRadius:"50%",
border:"1px solid rgba(58,53,86,.25)",
background:"linear-gradient(180deg, rgba(255,255,255,.9), rgba(255,255,255,.5))",
color:"#3a3556", fontSize:20, lineHeight:"28px", cursor:"pointer",
fontFamily:"'Inter', sans-serif",
},
panelEyebrow: {
fontFamily:"'JetBrains Mono', monospace", fontSize:11,
letterSpacing:".3em", textTransform:"uppercase",
color:"#7a4a8c", padding:"22px 30px 4px",
},
panelTitle: {
margin:"0 30px 18px",
fontFamily:"'Cormorant Garamond', Georgia, serif",
fontSize:42, lineHeight:1.05, fontWeight:300, color:"#2a2548",
letterSpacing:"-.02em",
},
panelBody: {
padding:"0 30px 26px",
fontFamily:"'Cormorant Garamond', Georgia, serif",
fontSize:17, lineHeight:1.55, color:"#2a2548",
},
bigEmail: {
display:"block", fontFamily:"'Cormorant Garamond', serif",
fontSize:38, fontStyle:"italic", color:"#d65a8a",
textDecoration:"none", margin:"14px 0",
letterSpacing:"-.01em",
textShadow:"0 0 30px rgba(214,90,138,.25)",
},
signoff: {
fontFamily:"'JetBrains Mono', monospace", fontSize:11,
letterSpacing:".18em", color:"#7a4a8c", marginTop:24,
textTransform:"uppercase", lineHeight:1.9,
},
panelMeta: {
display:"flex", flexDirection:"column", gap:4, marginTop:18,
fontFamily:"'JetBrains Mono', monospace", fontSize:10,
letterSpacing:".22em", color:"#7a4a8c", textTransform:"uppercase",
},
// ----- streams panel rows -----
streamList: {
display:"flex", flexDirection:"column", gap:12,
marginTop:20,
},
streamRow: {
padding:"14px 16px", borderRadius:14,
background:"linear-gradient(160deg, rgba(255,255,255,.7), rgba(248,232,250,.5))",
border:"1px solid rgba(255,255,255,.6)",
boxShadow:"0 6px 18px -10px rgba(80,40,120,.35), inset 0 1px 0 rgba(255,255,255,.8)",
},
streamHead: {
display:"flex", alignItems:"baseline", justifyContent:"space-between",
gap:12, marginBottom:8,
},
streamLabel: {
fontFamily:"'Cormorant Garamond', Georgia, serif",
fontStyle:"italic", fontSize:24, color:"#d65a8a",
letterSpacing:"-.01em", lineHeight:1,
},
streamSub: {
fontFamily:"'JetBrains Mono', monospace", fontSize:9,
letterSpacing:".22em", color:"#7a4a8c", textTransform:"uppercase",
textAlign:"right",
},
streamUrlRow: {
display:"flex", alignItems:"center", gap:10, minWidth:0,
},
streamUrl: {
flex:1, minWidth:0,
fontFamily:"'JetBrains Mono', monospace", fontSize:11,
letterSpacing:".04em", color:"#3a3556",
textDecoration:"none",
padding:"8px 10px", borderRadius:8,
background:"rgba(58,53,86,.06)",
border:"1px dashed rgba(58,53,86,.18)",
overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap",
},
streamCopy: {
flexShrink:0,
fontFamily:"'JetBrains Mono', monospace", fontSize:10,
letterSpacing:".22em", textTransform:"uppercase",
color:"#3a3556", cursor:"pointer",
padding:"8px 12px", borderRadius:99,
border:"1px solid rgba(58,53,86,.25)",
background:"linear-gradient(180deg, rgba(255,255,255,.9), rgba(255,255,255,.5))",
transition:"all .18s ease",
},
// ----- Live DJ banner (rendered above footer when a streamer is on) -----
liveBanner: {
position:"absolute", top:78, left:"50%", transform:"translateX(-50%)",
zIndex:8,
display:"flex", alignItems:"center", gap:14,
padding:"8px 18px", borderRadius:99,
background:"linear-gradient(135deg, rgba(214,39,90,.95), rgba(255,90,138,.85))",
boxShadow:"0 10px 30px -10px rgba(214,39,90,.6), 0 4px 10px -4px rgba(214,39,90,.5), inset 0 1px 0 rgba(255,255,255,.45)",
color:"#fff8fa",
fontFamily:"'JetBrains Mono', monospace", fontSize:11,
letterSpacing:".22em", textTransform:"uppercase",
border:"1px solid rgba(255,255,255,.35)",
backdropFilter:"blur(8px)",
},
liveDot: {
width:9, height:9, borderRadius:"50%", background:"#fff",
boxShadow:"0 0 12px 2px rgba(255,255,255,.85), 0 0 0 3px rgba(255,255,255,.2)",
animation:"livePulse 1.2s ease-in-out infinite",
flexShrink:0,
},
liveLabel: { fontWeight:700 },
liveStreamer: { color:"#fff", textTransform:"none", fontFamily:"'Cormorant Garamond', Georgia, serif", fontStyle:"italic", fontSize:18, letterSpacing:0, padding:"0 4px" },
liveTail: { opacity:.8, fontSize:10 },
// ----- Volume slider -----
volumeRow: {
display:"flex", alignItems:"center", gap:10, marginTop:14,
paddingTop:12, borderTop:"1px dashed rgba(58,53,86,.18)",
},
volumeIcon: { display:"flex", alignItems:"center", justifyContent:"center", width:18, height:18, flexShrink:0 },
volumePct: {
fontFamily:"'JetBrains Mono', monospace", fontSize:10,
letterSpacing:".15em", color:"#7a4a8c", minWidth:24, textAlign:"right",
},
};
window.V1 = V1;