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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ next-env.d.ts
!.superset/teardown.sh

# tsbuildinfo
*.tsbuildinfo
*.tsbuildinfo

# Environment files (secrets)
.envrc
.env.local
.env
29 changes: 20 additions & 9 deletions apps/marketing/src/app/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
"use client";

import { motion } from "framer-motion";
import Image from "next/image";
import { SocialLinks } from "../SocialLinks";

function SupersetLogo() {
return (
<svg
viewBox="0 0 392 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="Superset"
className="h-5 w-auto"
>
<title>Superset</title>
<path
d="M25.2727 -0.00017944H37.9091V12.6362H25.2727V-0.00017944ZM12.6364 -0.00017944H25.2727V12.6362H12.6364V-0.00017944ZM0 12.6362H12.6364V25.2725H0V12.6362ZM0 25.2725H12.6364V37.9089H0V25.2725ZM12.6364 25.2725H25.2727V37.9089H12.6364V25.2725ZM25.2727 25.2725H37.9091V37.9089H25.2727V25.2725ZM25.2727 37.9089H37.9091V50.5453H25.2727V37.9089ZM25.2727 50.5453H37.9091V63.1816H25.2727V50.5453ZM12.6364 50.5453H25.2727V63.1816H12.6364V50.5453ZM0 50.5453H12.6364V63.1816H0V50.5453ZM0 -0.00017944H12.6364V12.6362H0V-0.00017944ZM50.4961 -0.00017944H63.1325V12.6362H50.4961V-0.00017944ZM50.4961 12.6362H63.1325V25.2725H50.4961V12.6362ZM50.4961 25.2725H63.1325V37.9089H50.4961V25.2725ZM50.4961 37.9089H63.1325V50.5453H50.4961V37.9089ZM50.4961 50.5453H63.1325V63.1816H50.4961V50.5453ZM63.1325 50.5453H75.7688V63.1816H63.1325V50.5453ZM75.7688 50.5453H88.4052V63.1816H75.7688V50.5453ZM75.7688 37.9089H88.4052V50.5453H75.7688V37.9089ZM75.7688 25.2725H88.4052V37.9089H75.7688V25.2725ZM75.7688 12.6362H88.4052V25.2725H75.7688V12.6362ZM75.7688 -0.00017944H88.4052V12.6362H75.7688V-0.00017944ZM100.992 -0.00017944H113.629V12.6362H100.992V-0.00017944ZM100.992 12.6362H113.629V25.2725H100.992V12.6362ZM100.992 25.2725H113.629V37.9089H100.992V25.2725ZM100.992 37.9089H113.629V50.5453H100.992V37.9089ZM100.992 50.5453H113.629V63.1816H100.992V50.5453ZM113.629 -0.00017944H126.265V12.6362H113.629V-0.00017944ZM126.265 -0.00017944H138.901V12.6362H126.265V-0.00017944ZM126.265 12.6362H138.901V25.2725H126.265V12.6362ZM126.265 25.2725H138.901V37.9089H126.265V25.2725ZM113.629 25.2725H126.265V37.9089H113.629V25.2725ZM151.488 -0.00017944H164.125V12.6362H151.488V-0.00017944ZM151.488 12.6362H164.125V25.2725H151.488V12.6362ZM151.488 25.2725H164.125V37.9089H151.488V25.2725ZM151.488 37.9089H164.125V50.5453H151.488V37.9089ZM151.488 50.5453H164.125V63.1816H151.488V50.5453ZM164.125 -0.00017944H176.761V12.6362H164.125V-0.00017944ZM164.125 50.5453H176.761V63.1816H164.125V50.5453ZM164.125 25.2725H176.761V37.9089H164.125V25.2725ZM176.761 -0.00017944H189.397V12.6362H176.761V-0.00017944ZM176.761 50.5453H189.397V63.1816H176.761V50.5453ZM201.984 50.5453H214.621V63.1816H201.984V50.5453ZM201.984 37.9089H214.621V50.5453H201.984V37.9089ZM201.984 25.2725H214.621V37.9089H201.984V25.2725ZM201.984 12.6362H214.621V25.2725H201.984V12.6362ZM201.984 -0.00017944H214.621V12.6362H201.984V-0.00017944ZM214.621 -0.00017944H227.257V12.6362H214.621V-0.00017944ZM227.257 -0.00017944H239.893V12.6362H227.257V-0.00017944ZM227.257 12.6362H239.893V25.2725H227.257V12.6362ZM214.621 25.2725H227.257V37.9089H214.621V25.2725ZM227.257 37.9089H239.893V50.5453H227.257V37.9089ZM227.257 50.5453H239.893V63.1816H227.257V50.5453ZM277.753 -0.00017944H290.39V12.6362H277.753V-0.00017944ZM265.117 -0.00017944H277.753V12.6362H265.117V-0.00017944ZM252.48 12.6362H265.117V25.2725H252.48V12.6362ZM252.48 25.2725H265.117V37.9089H252.48V25.2725ZM265.117 25.2725H277.753V37.9089H265.117V25.2725ZM277.753 25.2725H290.39V37.9089H277.753V25.2725ZM277.753 37.9089H290.39V50.5453H277.753V37.9089ZM277.753 50.5453H290.39V63.1816H277.753V50.5453ZM265.117 50.5453H277.753V63.1816H265.117V50.5453ZM252.48 50.5453H265.117V63.1816H252.48V50.5453ZM252.48 -0.00017944H265.117V12.6362H252.48V-0.00017944ZM302.977 -0.00017944H315.613V12.6362H302.977V-0.00017944ZM302.977 12.6362H315.613V25.2725H302.977V12.6362ZM302.977 25.2725H315.613V37.9089H302.977V25.2725ZM302.977 37.9089H315.613V50.5453H302.977V37.9089ZM302.977 50.5453H315.613V63.1816H302.977V50.5453ZM315.613 -0.00017944H328.249V12.6362H315.613V-0.00017944ZM315.613 50.5453H328.249V63.1816H315.613V50.5453ZM315.613 25.2725H328.249V37.9089H315.613V25.2725ZM328.249 -0.00017944H340.886V12.6362H328.249V-0.00017944ZM328.249 50.5453H340.886V63.1816H328.249V50.5453ZM353.473 -0.00017944H366.109V12.6362H353.473V-0.00017944ZM366.109 -0.00017944H378.745V12.6362H366.109V-0.00017944ZM378.745 -0.00017944H391.382V12.6362H378.745V-0.00017944ZM366.109 12.6362H378.745V25.2725H366.109V12.6362ZM366.109 25.2725H378.745V37.9089H366.109V25.2725ZM366.109 37.9089H378.745V50.5453H366.109V37.9089ZM366.109 50.5453H378.745V63.1816H366.109V50.5453Z"
fill="currentColor"
/>
</svg>
Comment on lines +8 to +20
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Ensure the home link has an explicit accessible name (not just the SVG).
Consider adding aria-label on the <motion.a> and making the SVG either decorative (aria-hidden) or strictly aria-labelledby-based to avoid duplicate naming paths.

- <motion.a
-   href="/"
-   className="flex items-center gap-2 text-neutral-100 hover:text-white transition-colors"
- >
-   <SupersetLogo />
- </motion.a>
+ <motion.a
+   href="/"
+   aria-label="Superset home"
+   className="flex items-center gap-2 text-neutral-100 hover:text-white transition-colors"
+ >
+   <SupersetLogo />
+ </motion.a>

Also applies to: 43-51

🤖 Prompt for AI Agents
In apps/marketing/src/app/components/Header/Header.tsx around lines 11-23 (and
similarly for 43-51), the anchor wrapping the logo must have an explicit
accessible name instead of relying on the SVG; add a clear aria-label (e.g.,
"Home" or "Go to homepage") to the <motion.a> element, and make the SVG purely
decorative by removing its aria-label/title or setting aria-hidden="true" (or
alternatively wire the SVG to the link via aria-labelledby if you need the SVG
to provide the name). Ensure you do not leave both the link and SVG with
separate accessible names to avoid duplicate announcements.

);
}

interface HeaderProps {
ctaButtons: React.ReactNode;
}
Expand All @@ -17,18 +34,12 @@ export function Header({ ctaButtons }: HeaderProps) {
{/* Logo */}
<motion.a
href="/"
className="flex items-center gap-2 group"
className="flex items-center gap-2 text-foreground hover:text-foreground/80 transition-colors"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<Image
src="/title.svg"
alt="Superset"
width={200}
height={61}
className="h-8 sm:h-10 md:h-12 w-auto group-hover:scale-[1.02] transition-transform duration-200 dark:invert-0 invert"
/>
<SupersetLogo />
</motion.a>

{/* Right side */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useEffect, useState } from "react";
import { FaGithub } from "react-icons/fa";
import { DownloadButton } from "../DownloadButton";
import { WaitlistModal } from "../WaitlistModal";
import { TypewriterText } from "./components/TypewriterText";

export function HeroSection() {
const [isWaitlistOpen, setIsWaitlistOpen] = useState(false);
Expand Down Expand Up @@ -77,7 +78,11 @@ export function HeroSection() {
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)" }}
>
The terminal app for parallel cli agents.
<TypewriterText
text="The terminal app for parallel cli 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import { motion } from "framer-motion";
import { useEffect, useState } from "react";

interface TypewriterTextProps {
text: string;
className?: string;
style?: React.CSSProperties;
speed?: number;
delay?: number;
showCursor?: boolean;
}

export function TypewriterText({
text,
className,
style,
speed = 50,
delay = 500,
showCursor = true,
}: TypewriterTextProps) {
const [displayedText, setDisplayedText] = useState("");
const [isTyping, setIsTyping] = useState(false);

useEffect(() => {
const startTimeout = setTimeout(() => {
setIsTyping(true);
}, delay);

return () => clearTimeout(startTimeout);
}, [delay]);

useEffect(() => {
if (!isTyping) return;

if (displayedText.length < text.length) {
const timeout = setTimeout(() => {
setDisplayedText(text.slice(0, displayedText.length + 1));
}, speed);

return () => clearTimeout(timeout);
}
}, [displayedText, isTyping, speed, text]);

Comment on lines +26 to +45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reset typing state when text changes (and handle shorter text).
Right now, changing text mid-stream can produce jumps or leave stale characters when the new string is shorter.

 useEffect(() => {
+  // Reset when the content changes.
+  setDisplayedText("");
+  setIsTyping(false);
   const startTimeout = setTimeout(() => {
     setIsTyping(true);
   }, delay);

   return () => clearTimeout(startTimeout);
-}, [delay]);
+}, [delay, text]);
🤖 Prompt for AI Agents
In
apps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsx
around lines 26 to 45, the component doesn't reset typing state when the
incoming `text` prop changes, causing jumps or leftover characters if the new
text is shorter; add an effect that watches `text` and on change clears any
pending timers, resets `displayedText` to either '' or the truncated new text
(if new text is shorter than current displayedText), and resets `isTyping` (or
restarts the initial delay) so the typewriter restarts cleanly; ensure timers
are cleared in the cleanup to avoid races.

return (
<span className={className} style={style}>
{displayedText}
{showCursor && (
<motion.span
className="inline-block ml-0.5"
animate={{ opacity: [1, 0] }}
transition={{
duration: 0.5,
repeat: Number.POSITIVE_INFINITY,
repeatType: "reverse",
}}
>
|
</motion.span>
)}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TypewriterText } from "./TypewriterText";