diff --git a/apps/desktop/src/main/lib/agent-setup/utils.ts b/apps/desktop/src/main/lib/agent-setup/utils.ts index c53c71fb98d..6909bc71af0 100644 --- a/apps/desktop/src/main/lib/agent-setup/utils.ts +++ b/apps/desktop/src/main/lib/agent-setup/utils.ts @@ -2,7 +2,6 @@ import { execFileSync } from "node:child_process"; import os from "node:os"; import path from "node:path"; import { SUPERSET_DIR_NAMES } from "shared/constants"; -import { isValidBinaryName } from "../sanitize"; import { getDefaultShell } from "../terminal/env"; /** @@ -34,11 +33,6 @@ function findBinaryPathsWindows(name: string): string[] { * to avoid wrapper scripts calling each other. */ export function findRealBinary(name: string): string | null { - if (!isValidBinaryName(name)) { - // Ok for now because we're hard-coding the binary name in the wrapper scripts - console.error(`Unsafe binary name: ${name}`); - } - try { const isWindows = process.platform === "win32"; const allPaths = isWindows diff --git a/apps/desktop/src/main/lib/sanitize/sanitize.test.ts b/apps/desktop/src/main/lib/sanitize/sanitize.test.ts index ea6dd873f89..062990d9f7c 100644 --- a/apps/desktop/src/main/lib/sanitize/sanitize.test.ts +++ b/apps/desktop/src/main/lib/sanitize/sanitize.test.ts @@ -47,7 +47,7 @@ describe("isValidBinaryName", () => { it("should reject command substitution", () => { expect(isValidBinaryName("$(whoami)")).toBe(false); expect(isValidBinaryName("`id`")).toBe(false); - // biome-ignore lint/suspicious/noTemplateCurlyInString: Testing literal string "${PATH}" for security validation + // biome-ignore lint/suspicious/noTemplateCurlyInString: testing security validation expect(isValidBinaryName("${PATH}")).toBe(false); }); diff --git a/apps/marketing/package.json b/apps/marketing/package.json index 4dc0409f581..c24878011e8 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -30,6 +30,7 @@ "react-fast-marquee": "^1.6.5", "react-icons": "^5.5.0", "require-in-the-middle": "8.0.1", + "stripe-gradient": "^1.0.1", "three": "^0.181.2", "zod": "^4.1.13" }, diff --git a/apps/marketing/public/hero/agents.mp4 b/apps/marketing/public/hero/agents.mp4 new file mode 100644 index 00000000000..8bf054cc428 Binary files /dev/null and b/apps/marketing/public/hero/agents.mp4 differ diff --git a/apps/marketing/public/hero/change-themes.gif b/apps/marketing/public/hero/change-themes.gif deleted file mode 100644 index 0ac250d6027..00000000000 Binary files a/apps/marketing/public/hero/change-themes.gif and /dev/null differ diff --git a/apps/marketing/public/hero/changes.mp4 b/apps/marketing/public/hero/changes.mp4 new file mode 100644 index 00000000000..4324007757d Binary files /dev/null and b/apps/marketing/public/hero/changes.mp4 differ diff --git a/apps/marketing/public/hero/manage-terminals.gif b/apps/marketing/public/hero/manage-terminals.gif deleted file mode 100644 index c16a0aefa72..00000000000 Binary files a/apps/marketing/public/hero/manage-terminals.gif and /dev/null differ diff --git a/apps/marketing/public/hero/open-in.mp4 b/apps/marketing/public/hero/open-in.mp4 new file mode 100644 index 00000000000..42c419d141d Binary files /dev/null and b/apps/marketing/public/hero/open-in.mp4 differ diff --git a/apps/marketing/public/hero/open-worktrees.gif b/apps/marketing/public/hero/open-worktrees.gif deleted file mode 100644 index 90809ea3fff..00000000000 Binary files a/apps/marketing/public/hero/open-worktrees.gif and /dev/null differ diff --git a/apps/marketing/public/hero/tabs.mp4 b/apps/marketing/public/hero/tabs.mp4 new file mode 100644 index 00000000000..f3d7768ca9f Binary files /dev/null and b/apps/marketing/public/hero/tabs.mp4 differ diff --git a/apps/marketing/public/hero/use-agents.gif b/apps/marketing/public/hero/use-agents.gif deleted file mode 100644 index 9c3e84886fe..00000000000 Binary files a/apps/marketing/public/hero/use-agents.gif and /dev/null differ diff --git a/apps/marketing/public/hero/worktrees.mp4 b/apps/marketing/public/hero/worktrees.mp4 new file mode 100644 index 00000000000..1760885242e Binary files /dev/null and b/apps/marketing/public/hero/worktrees.mp4 differ diff --git a/apps/marketing/src/app/components/HeroSection/HeroSection.tsx b/apps/marketing/src/app/components/HeroSection/HeroSection.tsx index 5ed7481240c..d4821c3801d 100644 --- a/apps/marketing/src/app/components/HeroSection/HeroSection.tsx +++ b/apps/marketing/src/app/components/HeroSection/HeroSection.tsx @@ -2,11 +2,12 @@ import { COMPANY } from "@superset/shared/constants"; import { motion } from "framer-motion"; -import Image from "next/image"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { FaGithub } from "react-icons/fa"; import { DownloadButton } from "../DownloadButton"; import { WaitlistModal } from "../WaitlistModal"; +import { GridBackground } from "./components/GridBackground"; +import { ProductDemo } from "./components/ProductDemo"; import { TypewriterText } from "./components/TypewriterText"; export function HeroSection() { @@ -15,78 +16,29 @@ export function HeroSection() { return (
- {/* Grid background */} - +
-
- {/* Left column - Text content */} +
- {/* Heading */}

- Run dozens of Claude Code, Codex, or any other cli agents you - love. + Run dozens of Claude Code, Codex, or any other in parallel.

@@ -106,7 +58,6 @@ export function HeroSection() {
- {/* Right column - Product Demo */} ); } - -const SELECTOR_OPTIONS = [ - "Use Agents", - "Manage Terminals", - "Open Worktrees", - "Customize Themes", -] as const; - -const BACKGROUND_GRADIENTS: Record = { - "Use Agents": "from-rose-900/80 via-pink-950/70 to-rose-950/80", - "Manage Terminals": "from-amber-900/80 via-yellow-950/70 to-orange-950/80", - "Open Worktrees": "from-blue-900/80 via-blue-950/70 to-blue-950/80", - "Customize Themes": "from-emerald-900/80 via-teal-950/70 to-emerald-950/80", -}; - -const DEMO_GIFS: Record = { - "Use Agents": "/hero/use-agents.gif", - "Manage Terminals": "/hero/manage-terminals.gif", - "Open Worktrees": "/hero/open-worktrees.gif", - "Customize Themes": "/hero/change-themes.gif", -}; - -function ProductDemo() { - const [activeOption, setActiveOption] = useState(SELECTOR_OPTIONS[0]); - const [loadedGifs, setLoadedGifs] = useState>( - new Set([SELECTOR_OPTIONS[0]]), - ); - - // Lazy load GIFs when they become active - useEffect(() => { - if (!loadedGifs.has(activeOption)) { - setLoadedGifs((prev) => new Set([...prev, activeOption])); - } - }, [activeOption, loadedGifs]); - - return ( -
- {/* Background layers - all rendered, opacity controlled by active state */} - {SELECTOR_OPTIONS.map((option) => ( - - ))} - - {/* GIF layers - lazy loaded, centered with preserved aspect ratio */} - {SELECTOR_OPTIONS.map((option) => ( - - {loadedGifs.has(option) && DEMO_GIFS[option] && ( -
- {option} -
- )} -
- ))} - -
- {SELECTOR_OPTIONS.map((option) => ( - setActiveOption(option)} - /> - ))} -
-
- ); -} - -interface SelectorPillProps { - label: string; - active?: boolean; - onClick?: () => void; -} - -function SelectorPill({ label, active = false, onClick }: SelectorPillProps) { - return ( - - {label} - - ); -} diff --git a/apps/marketing/src/app/components/HeroSection/components/GridBackground/GridBackground.tsx b/apps/marketing/src/app/components/HeroSection/components/GridBackground/GridBackground.tsx new file mode 100644 index 00000000000..febb672c1be --- /dev/null +++ b/apps/marketing/src/app/components/HeroSection/components/GridBackground/GridBackground.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { motion } from "framer-motion"; + +export function GridBackground() { + return ( + + ); +} diff --git a/apps/marketing/src/app/components/HeroSection/components/GridBackground/index.ts b/apps/marketing/src/app/components/HeroSection/components/GridBackground/index.ts new file mode 100644 index 00000000000..df9c6c703e0 --- /dev/null +++ b/apps/marketing/src/app/components/HeroSection/components/GridBackground/index.ts @@ -0,0 +1 @@ +export { GridBackground } from "./GridBackground"; diff --git a/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx b/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx new file mode 100644 index 00000000000..74cdd3cf48f --- /dev/null +++ b/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { motion } from "framer-motion"; +import { useState } from "react"; +import { DemoVideo } from "./components/DemoVideo"; +import { MeshGradient } from "./components/MeshGradient"; +import { SelectorPill } from "./components/SelectorPill"; +import { DEMO_OPTIONS } from "./constants"; + +export function ProductDemo() { + const [activeOption, setActiveOption] = useState( + DEMO_OPTIONS[0]?.label ?? "", + ); + + return ( +
+ {/* Animated mesh gradient backgrounds - all rendered, opacity controlled */} + {DEMO_OPTIONS.map((option) => ( + + + + ))} + + {/* Content wrapper */} +
+ {/* Video container with border */} +
+ {DEMO_OPTIONS.map((option) => ( + + + + ))} +
+ + {/* Selector pills */} +
+ {DEMO_OPTIONS.map((option) => ( + setActiveOption(option.label)} + /> + ))} +
+
+
+ ); +} diff --git a/apps/marketing/src/app/components/HeroSection/components/ProductDemo/components/DemoVideo/DemoVideo.tsx b/apps/marketing/src/app/components/HeroSection/components/ProductDemo/components/DemoVideo/DemoVideo.tsx new file mode 100644 index 00000000000..a1681217ab7 --- /dev/null +++ b/apps/marketing/src/app/components/HeroSection/components/ProductDemo/components/DemoVideo/DemoVideo.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +interface DemoVideoProps { + src: string; + isActive: boolean; +} + +export function DemoVideo({ src, isActive }: DemoVideoProps) { + const videoRef = useRef(null); + + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + if (isActive) { + video.currentTime = 0; + video.play().catch(() => { + // Silently ignore autoplay restrictions - expected behavior + }); + } else { + video.pause(); + } + }, [isActive]); + + return ( +