Streaming — Hero Billboard
A cinematic, dark-first hero billboard for streaming platforms with a full-bleed backdrop, gradient scrim, title logo, match score and quality badges, synopsis, and play plus more-info buttons. An auto-rotating featured carousel cycles through fictional titles with progress-filled indicator dots, mute and replay controls, keyboard arrows, hover-to-pause, and a synced thumbnail row below the billboard.
MCP
Code
:root {
--bg: #0b0b0f;
--surface: #15151c;
--surface-2: #1e1e27;
--ink: #f4f4f7;
--ink-2: #b6b7c3;
--muted: #83859a;
--brand: #e50914;
--accent: #ffffff;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.16);
--r-sm: 8px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
--ease: cubic-bezier(0.22, 1, 0.36, 1);
}
* { box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
/* ---------- Top nav ---------- */
.topnav {
position: fixed;
inset: 0 0 auto 0;
z-index: 30;
display: flex;
align-items: center;
gap: 28px;
padding: 18px clamp(16px, 4vw, 48px);
background: linear-gradient(180deg, rgba(0, 0, 0, 0.8), transparent);
transition: background 0.4s var(--ease);
}
.topnav.scrolled { background: rgba(11, 11, 15, 0.92); backdrop-filter: blur(8px); }
.brand {
font-weight: 800;
letter-spacing: 0.18em;
color: var(--brand);
font-size: 22px;
}
.topnav__links { display: flex; gap: 22px; margin-left: 12px; }
.topnav__links a {
color: var(--ink-2);
font-size: 14px;
font-weight: 500;
transition: color 0.2s var(--ease);
}
.topnav__links a:hover { color: var(--ink); }
.topnav__links a.active { color: var(--ink); font-weight: 600; }
.topnav__right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
.icon-btn {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border: 0;
border-radius: 50%;
background: transparent;
color: var(--ink);
cursor: pointer;
transition: background 0.2s var(--ease);
}
.icon-btn:hover { background: var(--line); }
.avatar {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: var(--r-sm);
background: linear-gradient(135deg, var(--brand), #ff5b63);
font-size: 12px;
font-weight: 700;
}
/* ---------- Billboard ---------- */
.billboard {
position: relative;
min-height: 92vh;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.billboard__stage { position: absolute; inset: 0; overflow: hidden; }
.slide {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
opacity: 0;
transform: scale(1.06);
transition: opacity 1s var(--ease), transform 6s linear;
}
.slide.is-active { opacity: 1; transform: scale(1.12); }
.billboard__stage::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(90deg, rgba(11, 11, 15, 0.95) 0%, rgba(11, 11, 15, 0.55) 35%, transparent 65%),
linear-gradient(0deg, var(--bg) 4%, transparent 45%);
pointer-events: none;
}
.billboard__overlay {
position: relative;
z-index: 2;
display: flex;
align-items: flex-end;
gap: 16px;
padding: 0 clamp(16px, 4vw, 48px) 26px;
}
.billboard__content { max-width: 560px; }
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.badge--rank {
color: var(--ink);
background: var(--brand);
padding: 4px 9px;
border-radius: 4px;
}
.badge--rank::before { content: "TOP"; font-size: 9px; opacity: 0.85; }
.billboard__title {
margin: 14px 0 12px;
font-size: clamp(2.4rem, 6vw, 4.4rem);
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.02;
text-shadow: 0 4px 30px rgba(0, 0, 0, 0.6);
}
.billboard__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-bottom: 14px;
font-size: 14px;
color: var(--ink-2);
}
.billboard__meta .match { color: #6ee787; font-weight: 700; }
.billboard__meta .tag {
border: 1px solid var(--line-2);
border-radius: 4px;
padding: 1px 7px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.03em;
color: var(--ink);
}
.billboard__meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--muted); }
.billboard__synopsis {
margin: 0 0 22px;
font-size: clamp(0.95rem, 1.4vw, 1.05rem);
color: var(--ink-2);
max-width: 520px;
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.5);
}
.billboard__actions { display: flex; gap: 12px; flex-wrap: wrap; }
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
border: 0;
border-radius: var(--r-sm);
padding: 11px 24px;
font-family: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: transform 0.15s var(--ease), background 0.2s var(--ease), opacity 0.2s;
}
.btn:active { transform: scale(0.96); }
.btn--play { background: var(--accent); color: #0a0a0a; }
.btn--play:hover { background: rgba(255, 255, 255, 0.82); }
.btn--info { background: rgba(110, 110, 130, 0.5); color: var(--ink); backdrop-filter: blur(4px); }
.btn--info:hover { background: rgba(110, 110, 130, 0.36); }
.billboard__side {
display: flex;
align-items: center;
gap: 12px;
margin-left: auto;
padding-bottom: 4px;
}
.ctrl-btn {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border: 1px solid var(--line-2);
border-radius: 50%;
background: rgba(20, 20, 28, 0.4);
color: var(--ink);
cursor: pointer;
backdrop-filter: blur(4px);
transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.ctrl-btn:hover { border-color: var(--accent); background: rgba(30, 30, 40, 0.6); }
#muteBtn .ic-sound { display: none; }
#muteBtn[aria-pressed="false"] .ic-muted { display: none; }
#muteBtn[aria-pressed="false"] .ic-sound { display: block; }
.rating-pill {
border-left: 3px solid var(--ink-2);
background: rgba(20, 20, 28, 0.5);
padding: 4px 12px 4px 9px;
font-size: 13px;
font-weight: 600;
color: var(--ink);
backdrop-filter: blur(4px);
}
.billboard__dots {
position: relative;
z-index: 2;
display: flex;
gap: 8px;
justify-content: center;
padding: 0 0 22px;
}
.dot-btn {
width: 28px;
height: 4px;
border: 0;
border-radius: 99px;
background: var(--line-2);
cursor: pointer;
padding: 0;
overflow: hidden;
position: relative;
transition: background 0.2s var(--ease);
}
.dot-btn:hover { background: var(--line-2); }
.dot-btn.is-active { background: rgba(255, 255, 255, 0.3); }
.dot-btn .fill {
position: absolute;
inset: 0;
width: 0;
background: var(--accent);
}
.dot-btn.is-active .fill { width: 100%; transition: width var(--rotate, 7s) linear; }
/* ---------- Thumb row ---------- */
.row { padding: 8px clamp(16px, 4vw, 48px) 56px; }
.row__title { font-size: 18px; font-weight: 700; margin: 0 0 14px; }
.row__track {
display: flex;
gap: 12px;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding-bottom: 8px;
scrollbar-width: thin;
scrollbar-color: var(--surface-2) transparent;
}
.row__track::-webkit-scrollbar { height: 6px; }
.row__track::-webkit-scrollbar-thumb { background: var(--surface-2); border-radius: 99px; }
.thumb {
position: relative;
flex: 0 0 auto;
width: 192px;
aspect-ratio: 16 / 9;
border-radius: var(--r-md);
overflow: hidden;
cursor: pointer;
scroll-snap-align: start;
background-size: cover;
background-position: center;
border: 2px solid transparent;
transition: transform 0.25s var(--ease), border-color 0.2s var(--ease);
}
.thumb:hover { transform: scale(1.06); box-shadow: var(--shadow); }
.thumb:focus-visible { outline: none; border-color: var(--accent); }
.thumb.is-active { border-color: var(--brand); }
.thumb::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent 55%);
}
.thumb__label {
position: absolute;
z-index: 1;
left: 10px;
right: 10px;
bottom: 9px;
font-size: 13px;
font-weight: 600;
}
.thumb__hd {
position: absolute;
z-index: 1;
top: 8px;
right: 8px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.05em;
background: rgba(0, 0, 0, 0.6);
border: 1px solid var(--line-2);
border-radius: 4px;
padding: 1px 5px;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 24px);
background: var(--surface-2);
color: var(--ink);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
z-index: 50;
transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 520px) {
.topnav__links { display: none; }
.billboard { min-height: 88vh; }
.billboard__overlay { flex-direction: column; align-items: stretch; }
.billboard__side { margin-left: 0; justify-content: flex-start; }
.billboard__stage::after {
background:
linear-gradient(0deg, var(--bg) 8%, rgba(11, 11, 15, 0.3) 60%, transparent),
linear-gradient(90deg, rgba(11, 11, 15, 0.6), transparent 80%);
}
.btn { flex: 1; justify-content: center; }
.thumb { width: 152px; }
}(function () {
"use strict";
// --- Fictional featured catalog ---
// Backdrops are generated as inline SVG gradients so the demo is fully self-contained.
function backdrop(c1, c2, c3) {
const svg =
`<svg xmlns='http://www.w3.org/2000/svg' width='1280' height='720'>` +
`<defs><radialGradient id='g' cx='30%' cy='35%' r='90%'>` +
`<stop offset='0%' stop-color='${c1}'/><stop offset='55%' stop-color='${c2}'/>` +
`<stop offset='100%' stop-color='${c3}'/></radialGradient></defs>` +
`<rect width='1280' height='720' fill='url(#g)'/>` +
`<circle cx='980' cy='180' r='220' fill='${c1}' opacity='0.35'/>` +
`<circle cx='1120' cy='520' r='160' fill='${c2}' opacity='0.4'/>` +
`</svg>`;
return "url(\"data:image/svg+xml," + encodeURIComponent(svg) + "\")";
}
const SLIDES = [
{
rank: "#1 in Series Today",
title: "Halcyon Drift",
match: 98,
year: 2026,
seasons: "2 Seasons",
age: "16+",
tags: ["Sci-Fi", "Thriller"],
synopsis:
"A salvage pilot stranded on the edge of a dying star system discovers a signal that should not exist — and the crew willing to die to keep it buried.",
colors: ["#3a1f6b", "#1a1140", "#070512"],
},
{
rank: "New Limited Series",
title: "Iron Tide",
match: 95,
year: 2026,
seasons: "1 Season",
age: "18+",
tags: ["Crime", "Drama"],
synopsis:
"When a harbor city's last honest detective is framed for a murder she was meant to solve, she goes underground to expose the dynasty that owns the docks.",
colors: ["#0d3b4a", "#08222b", "#040b0f"],
},
{
rank: "Top Pick For You",
title: "Ember & Ash",
match: 91,
year: 2025,
seasons: "3 Seasons",
age: "13+",
tags: ["Fantasy", "Adventure"],
synopsis:
"Two estranged sisters inherit a hidden forge that can reshape the world — if they survive the rival houses that have hunted their bloodline for centuries.",
colors: ["#6b2a14", "#3d1408", "#120603"],
},
{
rank: "Trending Now",
title: "Static Bloom",
match: 89,
year: 2026,
seasons: "Film",
age: "16+",
tags: ["Mystery", "Romance"],
synopsis:
"A radio engineer keeps receiving calls from a woman who claims to be broadcasting from one week in the future. Every warning she gives comes true but one.",
colors: ["#1f5a4a", "#0e2f27", "#05100d"],
},
];
const ROTATE_MS = 7000;
let index = 0;
let timer = null;
let muted = true;
const stage = document.getElementById("stage");
const dotsWrap = document.getElementById("dots");
const thumbRow = document.getElementById("thumbRow");
const elRank = document.getElementById("rank");
const elTitle = document.getElementById("title");
const elMeta = document.getElementById("meta");
const elSyn = document.getElementById("synopsis");
const elAge = document.getElementById("ageRating");
const content = document.getElementById("content");
const toastEl = document.getElementById("toast");
document.documentElement.style.setProperty("--rotate", ROTATE_MS + "ms");
// Build slides, dots, thumbnails
SLIDES.forEach((s, i) => {
const bg = backdrop(s.colors[0], s.colors[1], s.colors[2]);
const slide = document.createElement("div");
slide.className = "slide";
slide.style.backgroundImage = bg;
stage.appendChild(slide);
const dot = document.createElement("button");
dot.className = "dot-btn";
dot.setAttribute("role", "tab");
dot.setAttribute("aria-label", s.title);
dot.innerHTML = '<span class="fill"></span>';
dot.addEventListener("click", () => goTo(i, true));
dotsWrap.appendChild(dot);
const thumb = document.createElement("button");
thumb.className = "thumb";
thumb.style.backgroundImage = bg;
thumb.setAttribute("aria-label", "Feature " + s.title);
thumb.innerHTML =
'<span class="thumb__hd">HD</span>' +
'<span class="thumb__label">' + s.title + "</span>";
thumb.addEventListener("click", () => goTo(i, true));
thumbRow.appendChild(thumb);
});
const slides = Array.from(stage.children);
const dots = Array.from(dotsWrap.children);
const thumbs = Array.from(thumbRow.children);
function render(i) {
const s = SLIDES[i];
elRank.textContent = s.rank;
elTitle.textContent = s.title;
elSyn.textContent = s.synopsis;
elAge.textContent = s.age;
elMeta.innerHTML =
'<span class="match">' + s.match + "% Match</span>" +
'<span>' + s.year + "</span><span class=\"dot\"></span>" +
'<span>' + s.seasons + "</span>" +
'<span class="tag">' + s.age + "</span>" +
s.tags.map((t) => '<span class="tag">' + t + "</span>").join("");
slides.forEach((el, n) => el.classList.toggle("is-active", n === i));
dots.forEach((el, n) => el.classList.toggle("is-active", n === i));
thumbs.forEach((el, n) => el.classList.toggle("is-active", n === i));
// restart synopsis fade
content.style.animation = "none";
void content.offsetWidth;
content.style.animation = "fadeUp 0.6s ease";
}
function goTo(i, manual) {
index = (i + SLIDES.length) % SLIDES.length;
render(index);
if (manual) restart();
}
function next() { goTo(index + 1, false); }
function start() {
stop();
timer = setInterval(next, ROTATE_MS);
}
function stop() { if (timer) { clearInterval(timer); timer = null; } }
function restart() { stop(); start(); }
// --- Controls ---
const muteBtn = document.getElementById("muteBtn");
muteBtn.addEventListener("click", () => {
muted = !muted;
muteBtn.setAttribute("aria-pressed", String(muted));
toast(muted ? "Audio muted" : "Audio on");
});
document.getElementById("replayBtn").addEventListener("click", () => {
render(index);
restart();
toast("Replaying " + SLIDES[index].title);
});
document.getElementById("playBtn").addEventListener("click", () => {
toast("Now playing — " + SLIDES[index].title);
});
document.getElementById("infoBtn").addEventListener("click", () => {
toast("Opening details for " + SLIDES[index].title);
});
// Pause rotation while pointer is over the billboard
const billboard = document.querySelector(".billboard");
billboard.addEventListener("mouseenter", stop);
billboard.addEventListener("mouseleave", start);
// Keyboard nav
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowRight") goTo(index + 1, true);
else if (e.key === "ArrowLeft") goTo(index - 1, true);
});
// Pause when tab hidden
document.addEventListener("visibilitychange", () => {
if (document.hidden) stop();
else start();
});
// Top nav shadow on scroll
const topnav = document.getElementById("topnav");
window.addEventListener("scroll", () => {
topnav.classList.toggle("scrolled", window.scrollY > 24);
}, { passive: true });
// --- Toast helper ---
let toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("show"), 2200);
}
// Inject keyframes for the content fade
const style = document.createElement("style");
style.textContent =
"@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}";
document.head.appendChild(style);
// Boot
render(0);
start();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Streaming — Hero Billboard</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>
<header class="topnav" id="topnav">
<a class="brand" href="#" aria-label="Nebula home">NEBULA</a>
<nav class="topnav__links" aria-label="Primary">
<a href="#" class="active">Home</a>
<a href="#">Series</a>
<a href="#">Films</a>
<a href="#">My List</a>
</nav>
<div class="topnav__right">
<button class="icon-btn" aria-label="Search">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="2" d="m21 21-4.3-4.3M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z"/></svg>
</button>
<span class="avatar" aria-hidden="true">JD</span>
</div>
</header>
<main>
<section class="billboard" aria-roledescription="carousel" aria-label="Featured titles">
<div class="billboard__stage" id="stage" aria-live="polite">
<!-- slides injected by script.js -->
</div>
<div class="billboard__overlay">
<div class="billboard__content" id="content">
<span class="badge badge--rank" id="rank">#1 in Series Today</span>
<h1 class="billboard__title" id="title">Title</h1>
<div class="billboard__meta" id="meta"></div>
<p class="billboard__synopsis" id="synopsis">Synopsis</p>
<div class="billboard__actions">
<button class="btn btn--play" id="playBtn">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 5v14l11-7z"/></svg>
Play
</button>
<button class="btn btn--info" id="infoBtn">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="2" d="M12 16v-5m0-3.5h.01M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z"/></svg>
More Info
</button>
</div>
</div>
<div class="billboard__side">
<button class="ctrl-btn" id="muteBtn" aria-pressed="true" aria-label="Toggle mute">
<svg class="ic-muted" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M3 9v6h4l5 5V4L7 9H3Z"/><path fill="none" stroke="currentColor" stroke-width="2" d="m16 9 5 5m0-5-5 5"/></svg>
<svg class="ic-sound" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M3 9v6h4l5 5V4L7 9H3Z"/><path fill="none" stroke="currentColor" stroke-width="2" d="M16 8a5 5 0 0 1 0 8m2.5-11a9 9 0 0 1 0 14"/></svg>
</button>
<button class="ctrl-btn" id="replayBtn" aria-label="Replay">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="2" d="M3 12a9 9 0 1 0 3-6.7M3 4v4h4"/></svg>
</button>
<span class="rating-pill" id="ageRating">16+</span>
</div>
</div>
<div class="billboard__dots" id="dots" role="tablist" aria-label="Choose featured title"></div>
</section>
<section class="row" aria-label="Featured this week">
<h2 class="row__title">Featured This Week</h2>
<div class="row__track" id="thumbRow"></div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Hero Billboard
A full-bleed featured billboard in the cinematic streaming idiom: a large backdrop sits behind a dual gradient scrim so the title, meta badges, and synopsis stay legible while the artwork slowly drifts in scale. A minimal top navigation fades over the art and gains a solid backdrop once the page scrolls. Each featured slide shows a rank tag, percent-match score, year, season count, age rating, and genre chips, with prominent Play and More Info buttons.
The billboard auto-rotates through four fictional titles every seven seconds. Progress-filled indicator dots track the active slide, and clicking any dot or the synced thumbnail row jumps straight to that title and restarts the timer. Side controls toggle mute and replay the current feature, the left and right arrow keys page through slides, and hovering the billboard pauses rotation so it never moves out from under the pointer. Every action surfaces a small toast.
All backdrops are generated as inline SVG gradients, so the component is fully self-contained — no images, frameworks, or build step. The layout reflows to a mobile-first column at narrow widths, with full-width buttons and a re-weighted scrim for readable text over the art.
Illustrative UI only — fictional titles, not a real streaming service.