Skip to content
Black FridayBlack Friday launch price: Lifetime $149 until 30 Nov. Regular price $299 from 1 Dec.ends in 15d 03:13:31Get Lifetime →
loopkit
proNew426 lines · framer-motion

Video Showcase Modal

Press play on a demo that was never recorded. A product-demo section with a generated poster card and play button that opens a custom modal playing an entirely generated abstract 'video' — drawn live on canvas, with real play/pause and a progress bar.

Use it for
The feature or product section of a SaaS site that wants a 'watch the demo' moment without shipping a real video file or an iframe embed.
Themes
Light + dark
License
Commercial use
Live

Product demo

See it move before you ship it

A full walkthrough of the product, from first login to the moment it pays off.

tsx
"use client";

import * as React from "react";
import { createPortal } from "react-dom";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { cn } from "@/lib/utils";

function useReducedMotionSafe() {
  const prefersReduced = useReducedMotion();
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  return mounted ? !!prefersReduced : false;
}

/**
 * Full-width product-demo section: a generated poster card with a play button opens a
 * custom modal (portaled to document.body) containing an entirely generated "video" —
 * a canvas loop of drifting abstract shapes, with real play/pause and an elapsed-time
 * progress bar. No <video> tag, no external media, no native dialog/confirm.
 */

export interface VideoShowcaseModalProps {

The rest is in Pro.

Full source, the rebuild prompt and registry install unlock with Pro or Lifetime.

Get Pro

How it behaves

  • →Poster frame is a static generated CSS/SVG composition (gradient + traced circles) with a centred play button; clicking it opens a modal rendered via createPortal to document.body.
  • →The modal's canvas draws one of three generated motion patterns (flowing blurred blobs, orbiting rings, or drifting particles) on a requestAnimationFrame loop; colours are read from --signal/--info/--lime/--ok and re-read on every class change on <html> via MutationObserver, so both themes look correct.
  • →A real play/pause button toggles a ref-backed flag that gates whether elapsed time advances in the raf loop; the canvas keeps rendering the current (frozen) frame while paused instead of stopping outright.
  • →A thin progress bar fills as the generated loop plays and resets every duration seconds; it's driven by accumulated performance.now() deltas inside the effect, never by Date.now() in render.
  • →Escape, a backdrop click, or the close button all close the modal; the raf loop, ResizeObserver, IntersectionObserver and MutationObserver are torn down on close and on unmount.
  • →An IntersectionObserver on the video host pauses time advancement when it scrolls offscreen, and document.hidden pauses it when the tab isn't visible.
  • →prefers-reduced-motion renders one static generated frame in the modal instead of animating.

Accessibility

  • ✓The poster is a real <button aria-haspopup="dialog"> with a visible focus ring, not a clickable div.
  • ✓The modal has role="dialog" and aria-modal="true"; focus moves to the close button on open and Escape closes it.
  • ✓Play/pause button exposes aria-pressed and an accurate aria-label ("Play" / "Pause"); the progress bar is a role="progressbar" with aria-valuemin/aria-valuemax.
  • ✓All decorative canvas and SVG layers are aria-hidden so only the real controls reach the accessibility tree.
  • ✓Honours prefers-reduced-motion.