Chapter 12 / 12 · Media & presentation

Video, carousel, devices — and the deck

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.

12.1 Video player

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.

Branded video player

.vid-player

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; }

12.3 Browser frame

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."

Browser chrome

.device.browser
magicblocks.ai
Every lead · Every time

The conversion engine for mortgage.

Five-second response. Pre-handoff qualification. All running natively inside Relcu.

<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; }

12.4 Laptop frame

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.

Laptop

.device.laptop
Live demo

Conversations that look human.

Every message runs through the MagicBlocks production layer — rules engine, rules monitor, jailbreak prevention, PII redaction, FAQ grounding.

<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);
}

12.5 Phone frame

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.

Phone

.device.phone
SMS · 9:47 AM
Hi Morgan — saw you were looking at rates for a 30-yr fixed. I can run some numbers in under a minute. Want me to?
Yes — mostly curious about the APR gap vs last month.
On it. Rates are down ~0.18% since last Tuesday — I’ll text back in 30 seconds.
MAGICBLOCKS · AUTO-REPLY
<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;
}

12.6 Deck device

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.

Framed presentation

.device.laptop.deck

Drop-in standalone version of the deck-stage pattern. No build step, no web component — vanilla JS.

01 / 04
The problem

Most AI can't do this job.

Generic LLMs hallucinate rates, forget borrowers mid-conversation, and break at handoff. Production sales needs guardrails on day one.

Hallucinates ratesNo memoryBreaks at handoff
The fix

Built for mortgage. Wired into Relcu.

Quotes only what you authorise. Rate, APR, product, and fees gated by your rules.

GuardrailsMemoryClean handover
The numbers

5-second response, every time.

World's first AI sales agent for mortgage. Shipped in 2023. Three years of edge cases already solved — you skip the year of pain.

SOC 2 Type IIISO 27001TCPA3,000+ edge servers
Turn it on

Talk to your Relcu rep about activating MagicBlocks.

We're already integrated. Ask for MagicBlocks by name.

magicblocks.ai
<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; }

12.7 Stage-progress chat

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.

Streaming HAPPA chat

.stage-chat
Hook
Align
Personalise
Pitch
Action
MagicBlocks · Agent
Simulated conversation · no real data sent
Type a reply…
Stage · Hook MAGICBLOCKS
<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; }

12.8 Product dashboard

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.

Animated dashboard

.product-dash
app.magicblocks.com / dashboard

Hello, Jordan

Revenue
$0
Conversations
0
Qualified
0
Conv. rate
0%
Last 14 days
Conversations Qualified Booked
<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); } }

12.9 Anatomy & notes

How these pieces fit together in practice.

  • Every component is vanilla HTML/CSS/JS. No framework, no build step. The wiring JS is in _shared.js so every page inherits it automatically.
  • The video player is agnostic — drop a real <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.
  • The carousel uses native CSS scroll-snap, so touch feels right with zero JS. The JS only wires the dots + arrows to programmatic scroll.
  • Device frames are pure CSS chrome around a .device-viewport slot. Drop anything inside — a dashboard screenshot, a conversation mockup, the brand kit's block pattern, or the deck itself.
  • The deck device is the laptop frame + a mini slide-switcher. Slides are HTML elements; navigation is keyboard + click + swipe (touch swipe works via the native scroll-snap beneath, for touch devices).
  • Every component honours prefers-reduced-motion — transitions drop to 0ms and continuous motion stops.