Skip to content

Instantly share code, notes, and snippets.

@mike-at-redspace
Created October 28, 2025 09:10
Show Gist options
  • Select an option

  • Save mike-at-redspace/f81c36ae1b792dc162112bf5aae6cccb to your computer and use it in GitHub Desktop.

Select an option

Save mike-at-redspace/f81c36ae1b792dc162112bf5aae6cccb to your computer and use it in GitHub Desktop.
Ambient Glow Showcase
main.min-h-screen.flex.justify-center.p-2.mt-2.h-screen
.w-full.grid.grid-cols-1.gap-4.ps-2(
class="max-w-[1920px] lg:grid-cols-[minmax(0,75%)_minmax(0,420px)]"
)
section.overflow-visible.h-full
.video-container.relative.flex.justify-center.items-center.max-w-full.bg-black.rounded-2xl(
class="min-h-[360px] lg:aspect-video"
)
canvas#glow
video#player.rounded-lg.w-full.relative.z-10(
muted,
loop,
playsinline,
crossorigin="anonymous"
)
source(
src="https://videos.pexels.com/video-files/1943483/1943483-uhd_3840_r2160_25fps.mp4",
type="video/mp4"
)
#spinner
section.mt-4
h1#video-title.text-2xl.font-semibold.text-shadow-xs Demo Video Title — Ambient Glow Showcase
p.text-sm.text-neutral-400.mt-1
| Uploaded by
span.text-purple-400.font-medium Lore M. Media
| • 15k views • Oct 2025
p.mt-3.text-md.text-neutral-300 This demo replicates YouTube's video layout with an ambient glow behind the video that reacts to its colors every second, easing between frames.
section.mt-6.rounded-xl.p-4(class="bg-neutral-900/40")
h3.text-sm.font-semibold.mb-3 Comments
.space-y-3
.flex.gap-3
.w-10.h-10.bg-neutral-700.rounded-full
div
.text-sm.font-medium PixelWatcher
span.text-xs.text-neutral-500 • 2h ago
.text-sm.text-neutral-300 That ambient glow feels futuristic, love it!
.flex.gap-3
.w-10.h-10.bg-neutral-700.rounded-full
div
.text-sm.font-medium MediaNerd
span.text-xs.text-neutral-500 • 1d ago
.text-sm.text-neutral-300 Looks exactly like YouTube Premium Dark — nice job.
aside.sidebar.p-3.pr-0.mr-1.rounded-xl.space-y-4
h4.text-lg.font-semibold Up next
#cards.overflow-y-auto.h-full.space-y-3.mr-1.snap-y.pr-1
each i in [...Array(16).keys()]
- const index = i + 1
- const isPlaying = index === 1 ? 'playing' : ''
article.flex.items-start.gap-3.p-3.rounded-xl.border.cursor-pointer.snap-start.card(
class=`bg-gradient-to-br from-neutral-900/80 to-neutral-900/40 border-neutral-800/50 hover:bg-neutral-800/60 transition-all duration-300 hover:shadow-lg hover:shadow-zinc-500/10 [&.playing]:bg-zinc-950/30 [&.playing]:border-zinc-500/50 ${isPlaying}`
id=`card-${index}`
)
.w-32.h-20.rounded-lg.flex-shrink-0.shadow-lg.relative.thumb(
style=`background: var(--gradient-${index % 16});`
)
div
h3.text-md.pt-1.font-semibold.text-neutral-50.line-clamp-2(class="mb-1.5") Sample Video #{index}
p.text-sm.text-neutral-400 Lore M. Media
.text-xs.text-neutral-500.flex.items-center.gap-2(class="mt-0.5")
span #{(index * 2.1).toFixed(1)}k views
span.text-neutral-600(aria-hidden="true")
span #{Math.floor(Math.random() * 7) + 1} days ago
import { Pane } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/tweakpane.min.js";
// Elements & Contexts
const video = document.getElementById("player");
const videoTitleEl = document.getElementById("video-title");
const glowCanvas = document.getElementById("glow");
const spinner = document.getElementById("spinner");
const cards = document.querySelectorAll(".card");
const ctx = glowCanvas.getContext("2d", { willReadFrequently: true });
// Offscreen canvas
const tempCanvas = document.createElement("canvas");
const tempCtx = tempCanvas.getContext("2d", { willReadFrequently: true });
// Defaults
const defaultParams = {
downscale: 0.08,
cssScale: 1.08,
blendOld: 0.85,
blendNew: 0.15,
updateInterval: 900,
glowBlur: 96,
glowOpacity: 0.65,
glowBrightness: 1.1,
glowSaturate: 1.2
};
// tweakpane reset to defaults
const params = { ...defaultParams };
const VIDEO_SOURCES = [
"https://videos.pexels.com/video-files/33942941/14403293_2560_1440_25fps.mp4",
"https://videos.pexels.com/video-files/29448550/12676721_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/33624499/14290410_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/6528444/6528444-uhd_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/1943483/1943483-uhd_3840_2160_25fps.mp4",
"https://videos.pexels.com/video-files/33410351/14221566_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/33017899/14070378_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/2287044/2287044-uhd_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/7771041/7771041-uhd_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/19722972/19722972-uhd_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/29570755/12728548_2560_1440_30fps.mp4",
"https://videos.pexels.com/video-files/34165002/14484190_2560_1440_25fps.mp4",
"https://videos.pexels.com/video-files/4129702/4129702-uhd_2560_1440_25fps.mp4",
"https://videos.pexels.com/video-files/3640406/3640406-uhd_2560_1440_25fps.mp4",
"https://videos.pexels.com/video-files/2256074/2256074-uhd_2732_1440_24fps.mp4",
"https://videos.pexels.com/video-files/28295448/12353428_1920_1080_25fps.mp4",
"https://videos.pexels.com/video-files/3015527/3015527-hd_1920_1080_24fps.mp4",
"https://videos.pexels.com/video-files/3434819/3434819-hd_1280_720_30fps.mp4"
];
// State
let lastImage = null;
let resizeTimeout = null;
let animationFrameId = null;
let lastUpdateTime = 0;
let isLooping = false;
// Core Functions
const resizeGlowCanvas = () => {
const { downscale } = params;
const rect = video.getBoundingClientRect();
const videoW = video.videoWidth || rect.width;
const videoH = video.videoHeight || rect.height;
// Calculate scaled dimensions
const w = Math.max(1, Math.round(videoW * downscale));
const h = Math.max(1, Math.round(videoH * downscale));
// Only resize if dimensions changed
if (glowCanvas.width !== w || glowCanvas.height !== h) {
glowCanvas.width = w;
glowCanvas.height = h;
tempCanvas.width = w;
tempCanvas.height = h;
lastImage = null;
}
// Update CSS height (preserve aspect ratio)
const cssHeight = rect.height * params.cssScale;
glowCanvas.style.height = `${cssHeight}px`;
};
const debouncedResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
resizeGlowCanvas();
drawGlowFrame();
}, 150);
};
const drawGlowFrame = () => {
if (video.readyState < 2 || glowCanvas.width === 0) return;
const { blendOld, blendNew } = params;
const w = glowCanvas.width;
const h = glowCanvas.height;
tempCtx.drawImage(video, 0, 0, w, h);
const newFrame = tempCtx.getImageData(0, 0, w, h);
if (lastImage) {
const oldData = lastImage.data;
const newData = newFrame.data;
const len = oldData.length;
for (let i = 0; i < len; i++) {
oldData[i] = oldData[i] * blendOld + newData[i] * blendNew;
}
ctx.putImageData(lastImage, 0, 0);
} else {
lastImage = newFrame;
ctx.putImageData(lastImage, 0, 0);
}
};
const animationLoop = (currentTime) => {
const { updateInterval } = params;
if (!isLooping) {
animationFrameId = null;
return;
}
animationFrameId = requestAnimationFrame(animationLoop);
if (!lastUpdateTime) lastUpdateTime = currentTime;
const elapsed = currentTime - lastUpdateTime;
if (elapsed >= updateInterval) {
lastUpdateTime = currentTime - (elapsed % updateInterval);
drawGlowFrame();
}
};
const play = (i) => {
// Remove playing class from all cards
cards.forEach((card) => {
card.classList.remove("playing");
});
// Add playing class to selected card
const selectedCard = document.querySelector(`#card-${i}`);
selectedCard.classList.add("playing");
// Update video source and title
video.src = VIDEO_SOURCES[i];
const nextTitle = selectedCard.querySelector("h3").textContent;
videoTitleEl.textContent = `${nextTitle} — Ambient Glow Showcase`;
// Load and play video
video.load();
video.play();
// clear state
lastImage = null;
lastUpdateTime = 0;
};
const showSpinner = () => (spinner.style.display = "block");
const hideSpinner = () => (spinner.style.display = "none");
// Event Listeners
const videoEvents = [
["loadedmetadata", resizeGlowCanvas],
[
"canplay",
() => {
resizeGlowCanvas();
drawGlowFrame();
}
],
["playing", hideSpinner],
["error", hideSpinner],
["loadstart", showSpinner],
["waiting", showSpinner],
[
"play",
() => {
isLooping = true;
if (!animationFrameId) {
lastUpdateTime = 0;
animationFrameId = requestAnimationFrame(animationLoop);
}
}
],
["pause", () => (isLooping = false)],
["ended", () => (isLooping = false)]
];
videoEvents.forEach(([event, handler]) =>
video.addEventListener(event, handler)
);
window.addEventListener("resize", debouncedResize);
cards.forEach((card, index) => {
card.addEventListener("click", () => play(index + 1));
});
// Manual Autoplay
play(1)
// Tweakpane
const updateCSSVariables = () => {
const root = document.documentElement.style;
root.setProperty("--glow-blur", `${params.glowBlur}px`);
root.setProperty("--glow-opacity", params.glowOpacity);
root.setProperty("--glow-brightness", params.glowBrightness);
root.setProperty("--glow-saturate", params.glowSaturate);
};
const pane = new Pane({
title: "Ambient Glow Settings",
container: document.body
});
// Performance
pane
.addBinding(params, "cssScale", {
min: 0.5,
max: 3,
step: 0.01,
label: "Scale",
format: (v) => `${v.toFixed(2)}x`
})
.on("change", resizeGlowCanvas);
pane
.addBinding(params, "downscale", {
min: 0.01,
max: 0.5,
step: 0.01,
label: "Quality",
format: (v) => `${v.toFixed(2)}x`
})
.on("change", () => {
resizeGlowCanvas();
drawGlowFrame();
});
pane.addBinding(params, "updateInterval", {
min: 16,
max: 2000,
step: 16,
label: "Update Freq",
format: (v) => `${v}ms`
});
// Blending
pane.addBinding(params, "blendOld", {
min: 0,
max: 1,
step: 0.01,
label: "Blend Old",
format: (v) => v.toFixed(2)
});
pane.addBinding(params, "blendNew", {
min: 0,
max: 1,
step: 0.01,
label: "Blend New",
format: (v) => v.toFixed(2)
});
// Styling
const glowBindings = [
[
"glowBlur",
{ min: 0, max: 200, step: 1, label: "Blur", format: (v) => `${v}px` }
],
[
"glowOpacity",
{
min: 0,
max: 1,
step: 0.01,
label: "Opacity",
format: (v) => v.toFixed(2)
}
],
[
"glowBrightness",
{
min: 0.5,
max: 2,
step: 0.05,
label: "Brightness",
format: (v) => `${v.toFixed(2)}x`
}
],
[
"glowSaturate",
{
min: 0,
max: 2,
step: 0.05,
label: "Saturate",
format: (v) => `${v.toFixed(2)}x`
}
]
];
glowBindings.forEach(([param, config]) => {
pane.addBinding(params, param, config).on("change", () => {
updateCSSVariables();
drawGlowFrame();
});
});
// Reset
pane.addButton({ title: "Reset" }).on("click", () => {
Object.assign(params, defaultParams);
pane.refresh();
resizeGlowCanvas();
updateCSSVariables();
drawGlowFrame();
});
<script src="https://cdn.tailwindcss.com"></script>
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
:root {
--scale: 1.18;
--glow-blur: 96px;
--glow-opacity: 0.65;
--glow-brightness: 1.1;
--glow-saturate: 1.2;
/* Tweakpane theme */
--tp-base-background-color: hsla(0, 0%, 10%, 0.55);
--tp-base-shadow-color: hsla(0, 0%, 0%, 0.3);
--tp-button-background-color: hsla(0, 0%, 70%, 1);
--tp-button-background-color-active: hsla(0, 0%, 95%, 1);
--tp-button-background-color-focus: hsla(0, 0%, 90%, 1);
--tp-button-background-color-hover: hsla(0, 0%, 78%, 1);
--tp-button-foreground-color: hsla(0, 0%, 0%, 0.85);
--tp-container-background-color: hsla(0, 0%, 0%, 0.25);
--tp-container-background-color-active: hsla(0, 0%, 0%, 0.55);
--tp-container-background-color-focus: hsla(0, 0%, 0%, 0.45);
--tp-container-background-color-hover: hsla(0, 0%, 0%, 0.35);
--tp-container-foreground-color: hsla(0, 0%, 100%, 0.55);
--tp-groove-foreground-color: hsla(0, 0%, 0%, 0.25);
--tp-input-background-color: hsla(0, 0%, 0%, 0.25);
--tp-input-background-color-active: hsla(0, 0%, 0%, 0.55);
--tp-input-background-color-focus: hsla(0, 0%, 0%, 0.45);
--tp-input-background-color-hover: hsla(0, 0%, 0%, 0.35);
--tp-input-foreground-color: hsla(0, 0%, 100%, 0.55);
--tp-label-foreground-color: hsla(0, 0%, 100%, 0.55);
--tp-monitor-background-color: hsla(0, 0%, 0%, 0.25);
--tp-monitor-foreground-color: hsla(0, 0%, 100%, 0.35);
/* Gradient definitions */
--gradient-0: linear-gradient(336deg, #e5737355 0%, #f5b97155 16%, #f7e48355 32%, #81c99555 48%, #6ec5e955 64%, #a18cd155 80%, #dba0d655 100%);
--gradient-1: linear-gradient(135deg, #550522 0%, #004dc0 140%);
--gradient-2: linear-gradient(135deg, #712c05 0%, #764ba288 40%);
--gradient-3: linear-gradient(135deg, #a18cd188 0%, #fbc2eb88 100%);
--gradient-4: linear-gradient(135deg, #d47073 0%, #c2c7ba 80%);
--gradient-5: linear-gradient(135deg, #9dff8d 0%, #ff578c 130%);
--gradient-6: linear-gradient(135deg, #d1c8c3 0%, #234d3c 100%);
--gradient-7: linear-gradient(135deg, #fddb9288 0%, #d1fdff88 100%);
--gradient-8: linear-gradient(135deg, #30cfd088 0%, #330a4588 100%);
--gradient-9: linear-gradient(135deg, #333240 0%, #f7f8fa 100%);
--gradient-10: linear-gradient(135deg, #43e97b88 0%, #38f9d788 100%);
--gradient-11: linear-gradient(350deg, #9e011f 0%, #5cb4eb 100%);
--gradient-12: linear-gradient(135deg, #ff6e7f88 0%, #bfe9ff88 100%);
--gradient-13: linear-gradient(135deg, #4facfe88 0%, #00f2fe88 100%);
--gradient-14: linear-gradient(135deg, #1356bb 0%, #c0bcea 100%);
--gradient-15: linear-gradient(135deg, #a37b70 0%, #8790a8 100%);
}
body {
background: #0f0f0f;
color: #f1f1f1;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
overflow: hidden;
}
.sidebar {
background: hsla(0, 0%, 0%, 0.3);
height: 100vh;
display: flex;
flex-direction: column;
scrollbar-width: thin;
scrollbar-color: #404040 transparent;
&::-webkit-scrollbar {
width: 6px;
&-track {
background: transparent;
}
&-thumb {
background-color: #404040;
border-radius: 3px;
border: 1px solid transparent;
&:hover {
background-color: #525252;
}
}
}
}
#glow {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
filter: blur(var(--glow-blur)) brightness(var(--glow-brightness)) saturate(var(--glow-saturate));
opacity: var(--glow-opacity);
border-radius: 12px;
pointer-events: none;
transition: filter 0.4s ease, opacity 0.4s ease;
z-index: -1;
}
#spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.2);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
display: none;
z-index: 100;
}
@keyframes spin {
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.thumb::after {
content: "\25B6";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 50px;
color: rgba(255, 255, 255, 0.75);
opacity: 0;
pointer-events: none;
transition: opacity 150ms ease-in-out;
z-index: 10;
}
.playing .thumb::after {
opacity: 1;
}
.tp-cntv {
position: fixed;
bottom: 1rem;
left: 1rem;
z-index: 1000;
}
.tp-rotv {
backdrop-filter: blur(32px);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment