A Pen by mike-at-redspace on CodePen.
Created
October 28, 2025 09:10
-
-
Save mike-at-redspace/f81c36ae1b792dc162112bf5aae6cccb to your computer and use it in GitHub Desktop.
Ambient Glow Showcase
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script src="https://cdn.tailwindcss.com"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @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