diff --git a/apps/web/public/showcase/theming-morph-poster.jpg b/apps/web/public/showcase/theming-morph-poster.jpg
new file mode 100644
index 0000000..6314cb7
Binary files /dev/null and b/apps/web/public/showcase/theming-morph-poster.jpg differ
diff --git a/apps/web/public/showcase/theming-morph.mp4 b/apps/web/public/showcase/theming-morph.mp4
new file mode 100644
index 0000000..0ba56fe
Binary files /dev/null and b/apps/web/public/showcase/theming-morph.mp4 differ
diff --git a/apps/web/public/showcase/theming-morph.webm b/apps/web/public/showcase/theming-morph.webm
new file mode 100644
index 0000000..10f041a
Binary files /dev/null and b/apps/web/public/showcase/theming-morph.webm differ
diff --git a/apps/web/src/app/products/webshops/page.tsx b/apps/web/src/app/products/webshops/page.tsx
index e58ce7e..3e4c324 100644
--- a/apps/web/src/app/products/webshops/page.tsx
+++ b/apps/web/src/app/products/webshops/page.tsx
@@ -1,6 +1,8 @@
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { DemosGallery } from "@/components/products/demos-gallery";
+import { ShowcaseVideo } from "@/components/showcase/showcase-video";
+import { BrowserFrame } from "@/components/showcase/browser-frame";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import { JsonLd, breadcrumbLd, faqLd } from "@/components/seo/json-ld";
@@ -110,6 +112,22 @@ export default function WebshopsPage() {
maintain it on revenue share, so we only win when you make sales.
+ {/* Theming morph: the same platform themed as five real demo brands. */}
+
+
+
+
+
+ One platform, five real storefronts. Every shop here runs the same engine.
+
+
+
{/* Theming spotlight, the headline story */}
diff --git a/apps/web/src/components/showcase/browser-frame.tsx b/apps/web/src/components/showcase/browser-frame.tsx
new file mode 100644
index 0000000..359019a
--- /dev/null
+++ b/apps/web/src/components/showcase/browser-frame.tsx
@@ -0,0 +1,37 @@
+import type { ReactNode } from 'react';
+
+/**
+ * A lightweight, purely-decorative browser chrome (title bar + traffic lights +
+ * a URL pill) to wrap a storefront screenshot or the theming-morph clip, so it
+ * reads as "a real shop in a real browser." Static CSS, no JS.
+ */
+export function BrowserFrame({
+ url = 'your-brand.com',
+ children,
+ className,
+}: {
+ url?: string;
+ children: ReactNode;
+ className?: string;
+}) {
+ return (
+
+
+
+
+
+
+
+
+ {url}
+
+
+ {children}
+
+ );
+}
diff --git a/apps/web/src/components/showcase/showcase-video.tsx b/apps/web/src/components/showcase/showcase-video.tsx
new file mode 100644
index 0000000..01c2c44
--- /dev/null
+++ b/apps/web/src/components/showcase/showcase-video.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import { useEffect, useRef, useState } from 'react';
+
+type ShowcaseVideoProps = {
+ webm: string;
+ mp4: string;
+ poster: string;
+ /** Accessible description of what the clip shows. */
+ label: string;
+ className?: string;
+};
+
+/**
+ * Perf-safe showcase video. The studio's motion budget, enforced in one place:
+ * - The poster paints instantly; the video is `preload="none"`, so it
+ * downloads NOTHING until it is actually played.
+ * - It only plays while in the viewport (IntersectionObserver) and pauses
+ * when scrolled away or the tab is hidden. No offscreen decoders.
+ * - `prefers-reduced-motion`: the video is never played, so with
+ * `preload="none"` it is never even downloaded. The poster is the whole
+ * experience for those visitors.
+ * - `muted` + `playsInline` so mobile autoplay-on-view is permitted (one
+ * video at a time; this component is for a single hero/section clip, not a
+ * grid — a grid would gate to one visible at a time).
+ */
+export function ShowcaseVideo({ webm, mp4, poster, label, className }: ShowcaseVideoProps) {
+ const ref = useRef(null);
+ const [allowMotion, setAllowMotion] = useState(true);
+
+ useEffect(() => {
+ setAllowMotion(!window.matchMedia('(prefers-reduced-motion: reduce)').matches);
+ }, []);
+
+ useEffect(() => {
+ const v = ref.current;
+ if (!v || !allowMotion || typeof IntersectionObserver === 'undefined') return;
+ const io = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ // .play() on a preload="none" element triggers the deferred load,
+ // then plays once buffered. Ignore the rejection browsers throw when
+ // a play() is interrupted by a quick pause() on fast scroll.
+ v.play().catch(() => {});
+ } else {
+ v.pause();
+ }
+ },
+ { threshold: 0.25 },
+ );
+ io.observe(v);
+ return () => io.disconnect();
+ }, [allowMotion]);
+
+ return (
+
+ );
+}