Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions apps/desktop/src/main/lib/agent-setup/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/sanitize/sanitize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
1 change: 1 addition & 0 deletions apps/marketing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
Binary file added apps/marketing/public/hero/agents.mp4
Binary file not shown.
Binary file removed apps/marketing/public/hero/change-themes.gif
Binary file not shown.
Binary file added apps/marketing/public/hero/changes.mp4
Binary file not shown.
Binary file removed apps/marketing/public/hero/manage-terminals.gif
Binary file not shown.
Binary file added apps/marketing/public/hero/open-in.mp4
Binary file not shown.
Binary file removed apps/marketing/public/hero/open-worktrees.gif
Binary file not shown.
Binary file added apps/marketing/public/hero/tabs.mp4
Binary file not shown.
Binary file removed apps/marketing/public/hero/use-agents.gif
Binary file not shown.
Binary file added apps/marketing/public/hero/worktrees.mp4
Binary file not shown.
184 changes: 7 additions & 177 deletions apps/marketing/src/app/components/HeroSection/HeroSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -15,78 +16,29 @@ export function HeroSection() {
return (
<div>
<div className="flex mt-14 min-h-[calc(100vh-64px)] items-center overflow-hidden">
{/* Grid background */}
<motion.div
className="absolute inset-0 pointer-events-none z-0"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
aria-hidden="true"
>
<svg
className="absolute inset-0 w-full h-full"
xmlns="http://www.w3.org/2000/svg"
>
<title>grid</title>
<defs>
<pattern
id="hero-grid"
width="60"
height="60"
patternUnits="userSpaceOnUse"
>
<path
d="M 60 0 L 0 0 0 60"
fill="none"
stroke="rgba(255,255,255,0.06)"
strokeWidth="1"
/>
</pattern>
<radialGradient id="grid-fade" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="white" stopOpacity="1" />
<stop offset="75%" stopColor="white" stopOpacity="0.95" />
<stop offset="85%" stopColor="white" stopOpacity="0.7" />
<stop offset="92%" stopColor="white" stopOpacity="0.3" />
<stop offset="96%" stopColor="white" stopOpacity="0.1" />
<stop offset="100%" stopColor="white" stopOpacity="0" />
</radialGradient>
<mask id="grid-mask">
<rect width="100%" height="100%" fill="url(#grid-fade)" />
</mask>
</defs>
<rect
width="100%"
height="100%"
fill="url(#hero-grid)"
mask="url(#grid-mask)"
/>
</svg>
</motion.div>
<GridBackground />

<div className="relative w-full max-w-[1600px] mx-auto px-8 lg:px-[30px] py-16">
<div className="grid grid-cols-1 lg:grid-cols-[42%_58%] gap-8 lg:gap-12 items-center">
{/* Left column - Text content */}
<div className="grid grid-cols-1 lg:grid-cols-[42%_58%] gap-12 lg:gap-16 items-center">
<motion.div
className="space-y-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
{/* Heading */}
<div className="space-y-2 sm:space-y-6">
<h1
className="text-2xl sm:text-3xl lg:text-4xl font-normal tracking-normal leading-[1.3em] text-foreground"
style={{ fontFamily: "var(--font-ibm-plex-mono)" }}
>
<TypewriterText
text="The terminal app for parallel cli agents."
text="The Terminal for Coding Agents."
speed={40}
delay={600}
/>
</h1>
<p className="text-md sm:text-lg font-light text-muted-foreground max-w-[400px]">
Run dozens of Claude Code, Codex, or any other cli agents you
love.
Run dozens of Claude Code, Codex, or any other in parallel.
</p>
</div>

Expand All @@ -106,7 +58,6 @@ export function HeroSection() {
</div>
</motion.div>

{/* Right column - Product Demo */}
<motion.div
className="relative"
initial={{ opacity: 0, x: 20 }}
Expand All @@ -125,124 +76,3 @@ export function HeroSection() {
</div>
);
}

const SELECTOR_OPTIONS = [
"Use Agents",
"Manage Terminals",
"Open Worktrees",
"Customize Themes",
] as const;

const BACKGROUND_GRADIENTS: Record<string, string> = {
"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<string, string> = {
"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<string>(SELECTOR_OPTIONS[0]);
const [loadedGifs, setLoadedGifs] = useState<Set<string>>(
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 (
<div
className="relative w-full rounded-lg overflow-hidden"
style={{ aspectRatio: "710/500" }}
>
{/* Background layers - all rendered, opacity controlled by active state */}
{SELECTOR_OPTIONS.map((option) => (
<motion.div
key={option}
className={`absolute inset-0 bg-linear-to-br ${BACKGROUND_GRADIENTS[option]}`}
initial={false}
animate={{ opacity: activeOption === option ? 1 : 0 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
))}

{/* GIF layers - lazy loaded, centered with preserved aspect ratio */}
{SELECTOR_OPTIONS.map((option) => (
<motion.div
key={option}
className="absolute inset-6 bottom-16 flex items-center justify-center"
initial={false}
animate={{ opacity: activeOption === option ? 1 : 0 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
>
{loadedGifs.has(option) && DEMO_GIFS[option] && (
<div
className="relative w-full h-full max-w-[90%] max-h-[90%]"
style={{ aspectRatio: "1812/1080" }}
>
<Image
src={DEMO_GIFS[option]}
alt={option}
fill
className="object-contain"
unoptimized
priority={option === SELECTOR_OPTIONS[0]}
/>
</div>
)}
</motion.div>
))}

<div className="absolute bottom-3 left-3 right-3 flex items-center gap-2 overflow-x-auto pb-1">
{SELECTOR_OPTIONS.map((option) => (
<SelectorPill
key={option}
label={option}
active={activeOption === option}
onClick={() => setActiveOption(option)}
/>
))}
</div>
</div>
);
}

interface SelectorPillProps {
label: string;
active?: boolean;
onClick?: () => void;
}

function SelectorPill({ label, active = false, onClick }: SelectorPillProps) {
return (
<motion.button
type="button"
onClick={onClick}
className={`
inline-flex items-center justify-center py-2 text-sm whitespace-nowrap cursor-pointer
${
active
? "bg-foreground/90 border border-foreground text-background/80"
: "bg-foreground/5 border border-foreground/20 text-foreground/80 hover:bg-foreground/10 hover:border-foreground/30"
}
`}
animate={{
paddingLeft: active ? 22 : 16,
paddingRight: active ? 22 : 16,
}}
transition={{ duration: 0.2, ease: "easeOut" }}
>
{label}
</motion.button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { motion } from "framer-motion";

export function GridBackground() {
return (
<motion.div
className="absolute inset-0 pointer-events-none z-0"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
aria-hidden="true"
>
<svg
className="absolute inset-0 w-full h-full"
xmlns="http://www.w3.org/2000/svg"
>
<title>grid</title>
<defs>
<pattern
id="hero-grid"
width="60"
height="60"
patternUnits="userSpaceOnUse"
>
<path
d="M 60 0 L 0 0 0 60"
fill="none"
stroke="rgba(255,255,255,0.06)"
strokeWidth="1"
/>
</pattern>
<radialGradient id="grid-fade" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="white" stopOpacity="1" />
<stop offset="75%" stopColor="white" stopOpacity="0.95" />
<stop offset="85%" stopColor="white" stopOpacity="0.7" />
<stop offset="92%" stopColor="white" stopOpacity="0.3" />
<stop offset="96%" stopColor="white" stopOpacity="0.1" />
<stop offset="100%" stopColor="white" stopOpacity="0" />
</radialGradient>
<mask id="grid-mask">
<rect width="100%" height="100%" fill="url(#grid-fade)" />
</mask>
</defs>
<rect
width="100%"
height="100%"
fill="url(#hero-grid)"
mask="url(#grid-mask)"
/>
</svg>
</motion.div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GridBackground } from "./GridBackground";
Original file line number Diff line number Diff line change
@@ -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<string>(
DEMO_OPTIONS[0]?.label ?? "",
);

return (
<div className="relative w-full rounded-lg overflow-hidden">
{/* Animated mesh gradient backgrounds - all rendered, opacity controlled */}
{DEMO_OPTIONS.map((option) => (
<motion.div
key={`gradient-${option.label}`}
className="absolute inset-0"
initial={false}
animate={{ opacity: activeOption === option.label ? 1 : 0 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
>
<MeshGradient
colors={option.colors}
className="absolute inset-0 w-full h-full"
/>
</motion.div>
))}

{/* Content wrapper */}
<div className="relative flex flex-col gap-4 p-6">
{/* Video container with border */}
<div
className="relative w-full rounded-lg overflow-hidden "
style={{ aspectRatio: "1728/1080" }}
>
{DEMO_OPTIONS.map((option) => (
<motion.div
key={option.label}
className="absolute -inset-px"
initial={false}
animate={{ opacity: activeOption === option.label ? 1 : 0 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
>
<DemoVideo
src={option.videoPath}
isActive={activeOption === option.label}
/>
</motion.div>
))}
</div>

{/* Selector pills */}
<div className="flex items-center gap-2 overflow-x-auto">
{DEMO_OPTIONS.map((option) => (
<SelectorPill
key={option.label}
label={option.label}
active={activeOption === option.label}
onClick={() => setActiveOption(option.label)}
/>
))}
</div>
</div>
</div>
);
}
Loading