From 5e14352caca9cb1129ffaeabafe31df6fba999b8 Mon Sep 17 00:00:00 2001 From: Marcel Veselka Date: Wed, 3 Jun 2026 09:00:24 +0200 Subject: [PATCH 1/3] feat(home): label fullscreen demo modal with How-it-works step cards The hero "Watch demo" modal plays the same 3 clips as the How-it-works section. Drive heroVideoSources from stepsData and pass the steps so the modal's progress stepper becomes clickable step cards (number, title, subtitle) with the playing clip highlighted. --- src/components/home-page/HeroVideoModal.tsx | 77 +++++++++++++++++---- src/components/home-page/HomeHeroVibe.tsx | 10 +-- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/components/home-page/HeroVideoModal.tsx b/src/components/home-page/HeroVideoModal.tsx index 1c88f2e..65ba693 100644 --- a/src/components/home-page/HeroVideoModal.tsx +++ b/src/components/home-page/HeroVideoModal.tsx @@ -1,14 +1,22 @@ import React, { useEffect, useRef, useState } from "react"; import { X } from "lucide-react"; +interface HeroVideoStep { + number?: number; + title: string; + subtitle?: string; +} + interface HeroVideoModalProps { sources: string[]; + steps?: HeroVideoStep[]; isOpen: boolean; onClose: () => void; } const HeroVideoModal: React.FC = ({ sources, + steps, isOpen, onClose, }) => { @@ -46,6 +54,7 @@ const HeroVideoModal: React.FC = ({ if (!isOpen) return null; const currentSrc = sources[index]; + const hasSteps = !!steps && steps.length === sources.length; return (
= ({ aria-label="Product walkthrough video" >
e.stopPropagation()} >
); diff --git a/src/components/home-page/HomeHeroVibe.tsx b/src/components/home-page/HomeHeroVibe.tsx index 002e0b4..92f1198 100644 --- a/src/components/home-page/HomeHeroVibe.tsx +++ b/src/components/home-page/HomeHeroVibe.tsx @@ -17,6 +17,7 @@ import { AppType } from "./vibe/enums"; import LoginDialog from "./vibe/LoginDialog"; import HeroVideoModal from "./HeroVideoModal"; import HeroTrustedByStrip from "./HeroTrustedByStrip"; +import { stepsData } from "../../data/steps"; const appTemplates = { [AppType.WEBSITE]: { @@ -70,11 +71,9 @@ const DEMO_SCENARIOS: AppType[] = [ const URL_REGEX = /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/; -const heroVideoSources = [ - "/how-it-works/step-1.webm", - "/how-it-works/step-2.webm", - "/how-it-works/step-3.webm", -]; +// Same clips (and order) as the "How it works" section, so the fullscreen +// modal can label each fragment with its step title/subtitle. +const heroVideoSources = stepsData.map((s) => s.videoSrc); const HomeHeroVibe = () => { const [appUrl, setAppUrl] = useState(appTemplates[defaultTemplate].url); @@ -383,6 +382,7 @@ const HomeHeroVibe = () => { setVideoOpen(false)} /> From 3928643fc4198e836204586a5d50842c262ed673 Mon Sep 17 00:00:00 2001 From: Marcel Veselka Date: Wed, 3 Jun 2026 09:00:57 +0200 Subject: [PATCH 2/3] feat(home): add inline autoplay demo-video component HeroVideoInline plays the demo clips muted + looping with a configurable aspect; grayscale + faded at rest, full colour + gradient border on hover; click opens the existing fullscreen modal. Not wired in yet. --- src/components/home-page/HeroVideoInline.tsx | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/components/home-page/HeroVideoInline.tsx diff --git a/src/components/home-page/HeroVideoInline.tsx b/src/components/home-page/HeroVideoInline.tsx new file mode 100644 index 0000000..d2bee76 --- /dev/null +++ b/src/components/home-page/HeroVideoInline.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useRef, useState } from "react"; +import { Maximize2 } from "lucide-react"; + +interface HeroVideoInlineProps { + sources: string[]; + poster?: string; + onExpand?: () => void; + className?: string; + aspectClassName?: string; +} + +// Muted autoplay loop of the demo clips, cycling through `sources`. Muting is +// required for browsers to allow autoplay without a user gesture; onExpand +// opens the fullscreen modal with sound + controls. +const HeroVideoInline: React.FC = ({ + sources, + poster, + onExpand, + className = "", + aspectClassName = "aspect-video", +}) => { + const [index, setIndex] = useState(0); + const videoRef = useRef(null); + + useEffect(() => { + videoRef.current?.play().catch(() => {}); + }, [index]); + + const currentSrc = sources[index]; + + return ( +
+ {/* gradient frame, faded in on hover */} +
+ ); +}; + +export default HeroVideoInline; From 2b9210c8f963f92ad88f9adc1be4c311316605db Mon Sep 17 00:00:00 2001 From: Marcel Veselka Date: Wed, 3 Jun 2026 09:01:42 +0200 Subject: [PATCH 3/3] =?UTF-8?q?feat(home):=20redesign=20hero=20=E2=80=94?= =?UTF-8?q?=20full-screen,=20form=20over=20a=20faint=20video=20backdrop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Segmented "Your app / Demo app" toggle (purple) in the form's top-right; "Demo app" loads a demo and opens the Website / E-commerce / Banking picker. - Full-width "Generate my first test" CTA + green trust row (Free to start / No credit card required / Works with your Playwright setup). - Demo clips as a faint backdrop (~28% opacity, no fill so the page gradient shows through); the subtitle sits over the video above the form and a "Watch 90-sec demo" affordance below it, both reclaiming the reveal space. - Hero fills the full screen (min-h-screen); spacing tuned so the form, CTA, trust row, and company logos all stay in the viewport. - Drop the old "Watch demo" badge link. --- src/components/home-page/HomeHeroVibe.tsx | 279 +++++++++++++--------- src/css/custom.css | 3 + 2 files changed, 166 insertions(+), 116 deletions(-) diff --git a/src/components/home-page/HomeHeroVibe.tsx b/src/components/home-page/HomeHeroVibe.tsx index 92f1198..4a98d47 100644 --- a/src/components/home-page/HomeHeroVibe.tsx +++ b/src/components/home-page/HomeHeroVibe.tsx @@ -2,6 +2,7 @@ import React, { useRef, useState, useEffect } from "react"; import { Globe, AppWindow, + FlaskConical, Landmark, ShoppingCart, Play, @@ -9,6 +10,7 @@ import { Sparkles, ChevronDown, Check, + CheckCircle2, } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -16,6 +18,7 @@ import { Button } from "@/components/ui/button"; import { AppType } from "./vibe/enums"; import LoginDialog from "./vibe/LoginDialog"; import HeroVideoModal from "./HeroVideoModal"; +import HeroVideoInline from "./HeroVideoInline"; import HeroTrustedByStrip from "./HeroTrustedByStrip"; import { stepsData } from "../../data/steps"; @@ -134,10 +137,9 @@ const HomeHeroVibe = () => { const triggerDemo = DEMO_SCENARIOS.includes(appType) ? appType : AppType.E_COMMERCE; - const TriggerIcon = appTemplates[triggerDemo].icon; return ( -
+
{ }} /> -
- +

AI Testing Agents @@ -182,13 +175,125 @@ const HomeHeroVibe = () => {

-
-
+
+ {/* Faint demo video backdrop behind the content. */} + setVideoOpen(true)} + className="col-start-1 row-start-1 z-0 w-[104%]" + aspectClassName="aspect-video" + /> +
+

+ {"Enter a URL, choose what to test, and Wopee's AI agent creates runnable tests with screenshots, checks, and self-healing steps."} +

+
- +
+ + {/* App selector — segmented "Your app" / "Demo app" toggle. + "Demo app" switches to a demo and opens the picker so a + specific demo (Website / E-commerce / Banking) stays + selectable. */} +
+
handleAppTypeChange(AppType.YOUR_APPLICATION)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleAppTypeChange(AppType.YOUR_APPLICATION); + } + }} + className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold leading-none cursor-pointer select-none transition-all ${ + appType === AppType.YOUR_APPLICATION + ? "bg-secondary-wopee text-white shadow-sm" + : "text-secondary-wopee/70 hover:text-secondary-wopee hover:bg-secondary-wopee/10 dark:text-white/70 dark:hover:text-white dark:hover:bg-white/10" + }`} + > + + Your app +
+
+
{ + if (!DEMO_SCENARIOS.includes(appType)) { + handleAppTypeChange(triggerDemo); + } + setDemoMenuOpen((open) => !open); + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + if (!DEMO_SCENARIOS.includes(appType)) { + handleAppTypeChange(triggerDemo); + } + setDemoMenuOpen((open) => !open); + } + }} + className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold leading-none cursor-pointer select-none transition-all ${ + DEMO_SCENARIOS.includes(appType) + ? "bg-secondary-wopee text-white shadow-sm" + : "text-secondary-wopee/70 hover:text-secondary-wopee hover:bg-secondary-wopee/10 dark:text-white/70 dark:hover:text-white dark:hover:bg-white/10" + }`} + > + + Demo app + +
+ {demoMenuOpen && ( +
+ {DEMO_SCENARIOS.map((type) => { + const tpl = appTemplates[type]; + const Icon = tpl.icon; + const selected = appType === type; + return ( +
handleAppTypeChange(type)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleAppTypeChange(type); + } + }} + className={`flex items-center gap-2 px-3 py-1.5 text-[11px] cursor-pointer select-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800 ${selected ? "text-secondary-wopee dark:text-primary-wopee font-semibold" : "text-gray-700 dark:text-gray-300"}`} + > + + {tpl.label} + {selected && ( + + )} +
+ ); + })} +
+ )} +
+
+
{ })()}
-
- {/* Your app chip on the left, then "or try a demos:" label, - then a single expander showing the selected demo. Clicking - it reveals the three demo options; picking one pre-fills URL - + instructions and collapses the menu. One click on "Your - app" returns to the empty custom-URL state. */} -
- {(() => { - const tpl = appTemplates[AppType.YOUR_APPLICATION]; - const Icon = tpl.icon; - // "Your app" is only visually selected once the visitor has - // typed a URL in Your-app mode — otherwise the chip would - // read as active on landing while the CTA is disabled. - const selected = - appType === AppType.YOUR_APPLICATION && appUrl.length > 0; - return ( -
- handleAppTypeChange(AppType.YOUR_APPLICATION) - } - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - handleAppTypeChange(AppType.YOUR_APPLICATION); - } - }} - className={`scenario-chip ${selected ? "scenario-chip--selected" : ""} inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-normal cursor-pointer select-none transition-colors`} - aria-label={tpl.label} - > - - {tpl.label} -
- ); - })()} - - or try a demos: - -
- - {demoMenuOpen && ( -
- {DEMO_SCENARIOS.map((type) => { - const tpl = appTemplates[type]; - const Icon = tpl.icon; - const selected = appType === type; - return ( -
handleAppTypeChange(type)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - handleAppTypeChange(type); - } - }} - className={`flex items-center gap-2 px-3 py-1.5 text-[11px] cursor-pointer select-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800 ${selected ? "text-secondary-wopee dark:text-primary-wopee font-semibold" : "text-gray-700 dark:text-gray-300"}`} - > - - {tpl.label} - {selected && ( - - )} -
- ); - })} -
- )} -
-
- +
+
+ + + Free to start + + + + No credit card required + + + + Works with your Playwright setup + +
+ +
+
+ {/* Watch-demo affordance over the video below the form. */} +
setVideoOpen(true)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setVideoOpen(true); + } + }} + aria-label="Watch 90-second demo" + className="group/play inline-flex items-center gap-2.5 cursor-pointer text-white/60 transition-colors duration-300 hover:text-white" + > + + + + Watch 90-sec demo
diff --git a/src/css/custom.css b/src/css/custom.css index d190ed1..0619c90 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -397,6 +397,9 @@ body.is-home .navbar.navbar--revealed { background-color: rgb(0 0 0 / 0.05); color: rgb(75 85 99); /* gray-600 */ border: 1px solid rgb(0 0 0 / 0.08); + /* Normalise line-height so the
"Your app" chip and the