Instant response
Every lead gets a personalised conversation within five seconds. No queues, no delays.
Chapter 12 / 12 · Media & presentation
Containers for motion: a branded HTML5 video player, a touch-friendly carousel, device chrome (browser / laptop / phone), and a presentation "laptop" that scrolls through HTML slides with keyboard nav + dot pager. Vanilla JS — ~70 lines total — so it ships straight to Cloudflare Pages with nothing to build.
Custom branded chrome over a native HTML5 <video>. Centre play button, bottom progress scrubber, time, volume, and fullscreen. Keyboard: Space = play/pause, ← → = ±10s, ↑↓ = volume, M = mute, F = fullscreen. Controls fade in on hover/focus and when paused; fade out while playing so the video has the stage.
Drop a real <video src="…"> inside; the controls wire up automatically.
<div class="vid-player" data-vid>
<!-- Drop any native <video>: the chrome wires up to it automatically -->
<video data-vid-media src="/path/to/demo.mp4" poster="/path/to/poster.jpg"
playsinline preload="metadata"></video>
<!-- Big centre play; hides once playback starts -->
<div class="vid-center">
<button class="vid-play" type="button" aria-label="Play"><svg>…play…</svg></button>
</div>
<!-- Bottom toolbar: play/pause · scrubber · time · mute+volume · fullscreen -->
<div class="vid-ctrls" role="toolbar" aria-label="Video controls">
<button data-vid-toggle aria-label="Play/pause">
<svg class="ic-play">…play…</svg><svg class="ic-pause">…pause…</svg>
</button>
<div class="vid-bar" style="--vp: 0;">
<input type="range" min="0" max="100" step="0.1" value="0" data-vid-seek aria-label="Seek">
</div>
<span class="vid-time" data-vid-time>0:00 / 0:00</span>
<div class="vid-vol" style="--vv: 100;">
<button data-vid-mute aria-label="Mute">
<svg class="ic-vol">…speaker…</svg><svg class="ic-mute">…muted…</svg>
</button>
<input type="range" min="0" max="100" step="1" value="100" data-vid-vol aria-label="Volume">
</div>
<button data-vid-fs aria-label="Fullscreen"><svg>…</svg></button>
</div>
</div>
.vid-player { position: relative; aspect-ratio: 16/9; background: var(--ink); overflow: hidden; }
.vid-player .vid-ctrls {
position: absolute; inset: auto 0 0 0; padding: 48px 20px 18px;
background: linear-gradient(to top, rgba(14,17,30,0.72), rgba(14,17,30,0));
display: grid; grid-template-columns: auto 1fr auto auto auto; gap: 12px;
opacity: 0; transition: opacity 160ms;
}
.vid-player:hover .vid-ctrls,
.vid-player:focus-within .vid-ctrls,
.vid-player.is-paused .vid-ctrls { opacity: 1; }
/* Scrubber fill via --vp (0–100); volume-slider fill via --vv (0–100).
Both are driven by the JS listeners on timeupdate / volumechange. */
.vid-player .vid-bar input::-webkit-slider-runnable-track {
background: linear-gradient(to right, var(--accent) 0%,
var(--accent) calc(var(--vp) * 1%),
rgba(255,255,255,0.22) calc(var(--vp) * 1%),
rgba(255,255,255,0.22) 100%);
}
.vid-player .vid-vol { display: inline-flex; align-items: center; gap: 4px; }
.vid-player .vid-vol input { width: 72px; }
/* Dual-icon swap keeps the DOM stable; swap via .is-playing / .is-muted on wrap */
.vid-player [data-vid-toggle] .ic-pause,
.vid-player [data-vid-mute] .ic-mute { display: none; }
.vid-player.is-playing [data-vid-toggle] .ic-play,
.vid-player.is-muted [data-vid-mute] .ic-vol { display: none; }
.vid-player.is-playing [data-vid-toggle] .ic-pause,
.vid-player.is-muted [data-vid-mute] .ic-mute { display: inline-block; }
Horizontal slide scroller with prev/next buttons and a dot pager. Uses native CSS scroll-snap so touch swiping and mouse dragging feel right without a library. Dots morph into a pill when active — the current slide is visually "held".
Any number of .carousel-slide children. Arrows are disabled at the ends; the pager auto-updates on scroll.
Every lead gets a personalised conversation within five seconds. No queues, no delays.
HAPPA methodology captures credit band, timeline, and intent before a human ever sees the lead.
The engine persists across days and channels — no dropped threads, no forgotten leads.
Permission-first outreach to the 70% of your CRM that never got a fair shot.
Your reps open Relcu to the full transcript, intent, and qualification — not a cold start.
<div class="carousel" data-carousel>
<div class="carousel-track" data-carousel-track>
<article class="carousel-slide">…slide 1…</article>
<article class="carousel-slide">…slide 2…</article>
<!-- as many as you need -->
</div>
<div class="carousel-nav">
<div class="carousel-dots" data-carousel-dots></div>
<div class="carousel-arrows">
<button data-carousel-prev aria-label="Previous">…</button>
<button data-carousel-next aria-label="Next">…</button>
</div>
</div>
</div>
<!-- Opt-in autoplay via data-autoplay="ms" on .carousel -->
<div class="carousel" data-carousel data-autoplay="5000">…</div>
.carousel-track {
display: flex; gap: var(--s-4);
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scrollbar-width: none;
}
.carousel-slide {
flex: 0 0 85%; /* mobile */
scroll-snap-align: start;
}
@media (min-width: 640px) { .carousel-slide { flex-basis: 420px; } }
/* Active dot morphs to a pill */
.carousel-dots button.is-active { width: 22px; border-radius: 4px; background: var(--accent); }
Mac-style chrome wrapping any web content. Traffic-light dots, pill URL bar with a lock icon, 16:10 viewport. Useful for hero screenshots of the MagicBlocks dashboard / website / admin UI when you want the reader to understand "this lives in a browser."
<div class="device browser">
<div class="chrome">
<div class="dots" aria-hidden="true"><span></span><span></span><span></span></div>
<div class="url">magicblocks.ai</div>
</div>
<div class="device-viewport">
<!-- drop any HTML — screenshot, dashboard, hero crop -->
</div>
</div>
.device.browser {
max-width: 960px;
border: 1px solid var(--hair); border-radius: var(--r-lg);
overflow: hidden; background: var(--bg-paper);
box-shadow: var(--sh-3);
}
.device.browser .chrome {
display: flex; align-items: center; gap: 14px;
padding: 10px 14px;
background: var(--warm-5); border-bottom: 1px solid var(--hair);
}
.device.browser .dots span { width: 11px; height: 11px; border-radius: 50%; }
.device.browser .dots span:nth-child(1) { background: #F47B6D; } /* close */
.device.browser .dots span:nth-child(2) { background: #F9C33E; } /* min */
.device.browser .dots span:nth-child(3) { background: #47DDB2; } /* max */
.device.browser .url {
flex: 1 1 auto; max-width: 520px; height: 28px;
border-radius: 999px; padding: 0 12px;
background: var(--bg-paper); border: 1px solid var(--hair);
font: 500 12px var(--f-mono); color: var(--fg-soft);
display: inline-flex; align-items: center; gap: 8px;
}
.device.browser .device-viewport { aspect-ratio: 16 / 10; }
MacBook-style bezel with pinhole camera and hinge silhouette. 16:10 viewport. Drop any HTML content inside — dashboards, conversation previews, a video player, or (§12.6) a running slide deck.
<div class="device laptop">
<div class="device-viewport">
<!-- drop any HTML — dashboard, video player, slide deck -->
</div>
</div>
.device.laptop {
max-width: 900px; margin: 0 auto;
padding: 18px 18px 0;
background: #0c0f18; /* --device-bezel */
border-radius: 22px 22px 6px 6px;
box-shadow: 0 30px 60px -30px rgba(25,30,50,0.45);
position: relative;
}
.device.laptop::before {
/* camera pinhole */
content: ""; position: absolute; top: 8px; left: 50%;
width: 6px; height: 6px; border-radius: 50%;
background: rgba(255,255,255,0.18);
transform: translateX(-50%);
}
.device.laptop .device-viewport {
aspect-ratio: 16 / 10; border-radius: 4px;
overflow: hidden; background: var(--bg-paper);
}
.device.laptop::after {
/* lid base — hinge silhouette below the screen */
content: ""; display: block;
height: 14px; margin: 18px -32px 0;
background: linear-gradient(to bottom, #0c0f18 0%, #0c0f18 40%,
color-mix(in oklab, #0c0f18 80%, #888) 100%);
border-radius: 0 0 22px 22px;
box-shadow: 0 6px 14px -4px rgba(25,30,50,0.4);
}
iPhone-style bezel with dynamic-island-ish notch. 9:19.5 viewport. Use for mobile screenshots of agent chat UIs, SMS conversation views, or responsive landing hero crops.
<div class="device phone">
<div class="notch" aria-hidden="true"></div>
<div class="device-viewport">
<!-- drop any HTML — agent chat UI, SMS view, mobile hero crop -->
</div>
</div>
.device.phone {
max-width: 340px; margin: 0 auto;
padding: 12px;
background: #0c0f18; /* --device-bezel */
border-radius: 44px;
box-shadow: 0 30px 60px -30px rgba(25,30,50,0.45),
0 0 0 1px rgba(255,255,255,0.08) inset;
position: relative;
}
.device.phone .device-viewport {
aspect-ratio: 9 / 19.5; border-radius: 34px;
overflow: hidden; background: var(--bg-paper);
position: relative;
}
.device.phone .notch {
/* dynamic-island-ish pill at the top */
position: absolute; z-index: 2;
top: 10px; left: 50%; transform: translateX(-50%);
width: 92px; height: 28px; border-radius: 999px;
background: #0c0f18;
}
A laptop frame wrapping a self-contained slide deck. Keyboard (← →, Space, Home/End), a dot pager, prev/next arrows — all wired up to the element. Each slide is a .deck-slide child; add as many as you like.
Drop-in standalone version of the deck-stage pattern. No build step, no web component — vanilla JS.
<div class="device laptop deck">
<div class="device-viewport">
<div class="deck-slides" data-deck>
<span class="deck-brand"><span class="dot"></span>magicblocks</span>
<span class="deck-count" data-deck-count>01 / 04</span>
<article class="deck-slide is-active">
<span class="eyebrow">The problem</span>
<h3 class="title">Most AI can't do <em>this job</em>.</h3>
<p class="lede">Generic LLMs hallucinate rates, forget borrowers…</p>
<div class="pill-row"><span>Hallucinates rates</span>…</div>
</article>
<!-- more .deck-slide articles -->
<div class="deck-controls">
<div class="deck-pager" data-deck-pager></div>
<div class="deck-arrows">
<button data-deck-prev aria-label="Previous">…</button>
<button data-deck-next aria-label="Next">…</button>
</div>
</div>
</div>
</div>
</div>
/* Each slide is absolutely positioned; the active one fades in */
.deck-slide { position: absolute; inset: 0; opacity: 0; pointer-events: none; transition: opacity 240ms; }
.deck-slide.is-active { opacity: 1; pointer-events: auto; }
/* Active dot morphs into a pill */
.deck-pager button.is-active { background: var(--accent); width: 34px; }
A phone-framed conversation that token-streams each message and advances through the five HAPPA stages — Hook, Align, Personalise, Pitch, Action. Ideal for narrating how an AI agent moves a conversation from cold outreach to signed paperwork. Loops continuously; honours prefers-reduced-motion.
<div class="stage-chat" data-stage-chat>
<div class="sc-track">
<div class="sc-stage" data-stage="hook">Hook</div>
<div class="sc-stage" data-stage="align">Align</div>
<div class="sc-stage" data-stage="personalise">Personalise</div>
<div class="sc-stage" data-stage="pitch">Pitch</div>
<div class="sc-stage" data-stage="action">Action</div>
</div>
<div class="sc-phone">
<div class="sc-screen">
<header class="sc-head">…</header>
<div class="sc-body" data-sc-body></div>
<footer class="sc-foot">…</footer>
</div>
</div>
</div>
/* Stage track — highlights the active stage in brand pink */
.sc-stage.is-active { background: var(--accent-soft); color: var(--accent-text); }
/* Streaming caret — blinks while tokens are still arriving */
.sc-caret { animation: sc-blink 0.85s steps(1) infinite; }
/* Typing indicator — three bouncing dots */
.sc-typing span { animation: sc-bp 1.1s infinite; }
Browser-framed “inside the product” view — sidebar nav + KPI tiles with count-up animations + a three-line analytics chart that draws itself on loop. Use in hero sections where you want a living screenshot rather than a still image. Loops every ~4.3s with values escalating each cycle so the numbers appear to climb.
<div class="product-dash" data-product-dash>
<div class="pd-chrome">…</div>
<div class="pd-shell">
<aside class="pd-side">…</aside>
<div class="pd-main">
<h3 class="pd-title">Hello, Jordan</h3>
<div class="pd-kpis">
<div class="pd-kpi">
<div class="pd-kpi-label">Revenue</div>
<div class="pd-kpi-value"
data-pd-kpi data-prefix="$" data-base="14820">$0</div>
</div>
…
</div>
<div class="pd-chart-box">
<svg viewBox="0 0 560 160">
<path class="pd-line" data-pd-line="pink"/>
…
</svg>
</div>
</div>
</div>
</div>
/* Three chart lines draw in on each loop cycle */
.pd-line {
stroke-dasharray: var(--len);
stroke-dashoffset: var(--len);
transition: stroke-dashoffset 1800ms cubic-bezier(0.22, 1, 0.36, 1);
}
.product-dash.is-drawn .pd-line { stroke-dashoffset: 0; }
/* Points pop in after the lines finish */
@keyframes pd-pop { from { opacity: 0; transform: scale(0.2); } to { opacity: 1; transform: scale(1); } }
How these pieces fit together in practice.
_shared.js so every page inherits it automatically.<video> inside the .vid-player and the existing chrome binds to it automatically. Scrubber (--vp), volume (--vv), mute-state icon swap, keyboard shortcuts and fullscreen all wire up on load. Falls back to a poster-style surface when no <video> is present, so the chrome still previews correctly in design reviews.scroll-snap, so touch feels right with zero JS. The JS only wires the dots + arrows to programmatic scroll..device-viewport slot. Drop anything inside — a dashboard screenshot, a conversation mockup, the brand kit's block pattern, or the deck itself.prefers-reduced-motion — transitions drop to 0ms and continuous motion stops.