Add terminal cascade animation to hero with two-column layout
Right column shows a looping cause-and-effect animation: cladm console selects projects one by one, then Claude Code terminals cascade in with spring-bounce animation showing the real welcome screen. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,43 @@ body {
|
||||
.float-delay { animation: float 3s ease-in-out 0.7s infinite; }
|
||||
.float-delay-2 { animation: float 3s ease-in-out 1.4s infinite; }
|
||||
|
||||
/* Terminal cascade */
|
||||
@keyframes cascadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(50px) scale(0.88);
|
||||
}
|
||||
65% {
|
||||
opacity: 1;
|
||||
transform: translateY(-6px) scale(1.015);
|
||||
}
|
||||
82% {
|
||||
transform: translateY(2px) scale(0.997);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes cascadeGlow {
|
||||
0%, 100% { border-color: #333333; }
|
||||
50% { border-color: #e07850; }
|
||||
}
|
||||
@keyframes flash {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
.cascade-flash {
|
||||
animation: flash 0.3s ease-in-out 2;
|
||||
}
|
||||
.cascade-in {
|
||||
animation: cascadeIn 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
}
|
||||
.cascade-glow {
|
||||
animation: cascadeIn 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both,
|
||||
cascadeGlow 2s ease-in-out 1.2s infinite;
|
||||
}
|
||||
|
||||
/* Key cap */
|
||||
.keycap {
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Image from "next/image";
|
||||
import { EmailReveal } from "./email-reveal";
|
||||
import { TerminalCascade } from "./terminal-cascade";
|
||||
import {
|
||||
SearchIcon,
|
||||
GithubIcon,
|
||||
@@ -98,9 +99,9 @@ export default function Home() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative max-w-5xl mx-auto px-6 pt-16 pb-20">
|
||||
<div className="relative max-w-6xl mx-auto px-6 pt-16 pb-20">
|
||||
{/* Nav */}
|
||||
<nav className="flex items-center justify-between mb-20">
|
||||
<nav className="flex items-center justify-between mb-16">
|
||||
<div className="font-[family-name:var(--font-pixel)] text-accent text-lg tracking-widest flex items-center gap-2">
|
||||
<SpaceInvadersIcon size={20} />
|
||||
CLADM
|
||||
@@ -116,46 +117,54 @@ export default function Home() {
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
{/* Hero content */}
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="cladm pixel art logo"
|
||||
width={320}
|
||||
height={213}
|
||||
className="pixel-render float mb-8"
|
||||
priority
|
||||
/>
|
||||
{/* Two-column hero */}
|
||||
<div className="flex flex-col lg:flex-row items-center gap-12 lg:gap-16">
|
||||
{/* Left — text + CTA */}
|
||||
<div className="flex-1 flex flex-col items-center lg:items-start text-center lg:text-left">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="cladm pixel art logo"
|
||||
width={240}
|
||||
height={160}
|
||||
className="pixel-render float mb-6"
|
||||
priority
|
||||
/>
|
||||
|
||||
<h1 className="font-[family-name:var(--font-pixel)] text-4xl md:text-5xl text-text mb-4 leading-tight">
|
||||
CLADM
|
||||
</h1>
|
||||
<h1 className="font-[family-name:var(--font-pixel)] text-4xl md:text-5xl text-text mb-4 leading-tight">
|
||||
CLADM
|
||||
</h1>
|
||||
|
||||
<p className="font-[family-name:var(--font-pixel)] text-accent text-lg md:text-xl mb-6">
|
||||
TUI LAUNCHER FOR CLAUDE CODE
|
||||
</p>
|
||||
<p className="font-[family-name:var(--font-pixel)] text-accent text-lg md:text-xl mb-5">
|
||||
TUI LAUNCHER FOR CLAUDE CODE
|
||||
</p>
|
||||
|
||||
<p className="font-[family-name:var(--font-mono)] text-dim text-sm max-w-xl leading-relaxed mb-10">
|
||||
Browse all your projects. See git status at a glance. Expand into
|
||||
sessions and branches. Launch everything in parallel Terminal
|
||||
windows.
|
||||
</p>
|
||||
<p className="font-[family-name:var(--font-mono)] text-dim text-sm max-w-md leading-relaxed mb-8">
|
||||
Browse all your projects. See git status at a glance. Expand
|
||||
into sessions and branches. Launch everything in parallel
|
||||
Terminal windows.
|
||||
</p>
|
||||
|
||||
{/* Install command */}
|
||||
<div className="pixel-border-accent bg-surface px-8 py-4 mb-6">
|
||||
<code className="font-[family-name:var(--font-mono)] text-green text-sm">
|
||||
<span className="text-dim">$</span> bun install -g cladm
|
||||
<span className="cursor-blink text-accent">_</span>
|
||||
</code>
|
||||
{/* Install command */}
|
||||
<div className="pixel-border-accent bg-surface px-8 py-4 mb-4">
|
||||
<code className="font-[family-name:var(--font-mono)] text-green text-sm">
|
||||
<span className="text-dim">$</span> bun install -g cladm
|
||||
<span className="cursor-blink text-accent">_</span>
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<p className="font-[family-name:var(--font-mono)] text-dim text-xs">
|
||||
requires{" "}
|
||||
<a href="https://bun.sh" className="text-accent hover:underline">
|
||||
bun
|
||||
</a>{" "}
|
||||
>= 1.3 + macOS
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="font-[family-name:var(--font-mono)] text-dim text-xs mb-16">
|
||||
requires{" "}
|
||||
<a href="https://bun.sh" className="text-accent hover:underline">
|
||||
bun
|
||||
</a>{" "}
|
||||
>= 1.3 + macOS
|
||||
</p>
|
||||
{/* Right — terminal cascade */}
|
||||
<div className="flex-1 w-full max-w-xl">
|
||||
<TerminalCascade />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
176
site/app/terminal-cascade.tsx
Normal file
176
site/app/terminal-cascade.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
const projects = [
|
||||
{ name: "acme-api", branch: "main", time: "25m ago" },
|
||||
{ name: "quantum-dash", branch: "feat/charts", time: "1h ago" },
|
||||
{ name: "ml-pipeline", branch: "exp/bert", time: "just now" },
|
||||
];
|
||||
|
||||
type Phase =
|
||||
| "typing" // cladm console visible, cursor selecting projects
|
||||
| "selecting" // checkboxes toggling on one by one
|
||||
| "enter" // "Enter" flash, cladm fades
|
||||
| "cascade" // terminals fly in
|
||||
| "hold" // terminals visible
|
||||
| "fadeout" // everything fades, restart
|
||||
| "pause"; // brief gap before loop
|
||||
|
||||
export function TerminalCascade() {
|
||||
const [phase, setPhase] = useState<Phase>("typing");
|
||||
const [selectedCount, setSelectedCount] = useState(0);
|
||||
const [cycle, setCycle] = useState(0);
|
||||
|
||||
const runCycle = useCallback(() => {
|
||||
setSelectedCount(0);
|
||||
setPhase("typing");
|
||||
|
||||
// Typing/appear cladm console
|
||||
const t1 = setTimeout(() => setPhase("selecting"), 800);
|
||||
|
||||
// Toggle checkboxes one by one
|
||||
const t2 = setTimeout(() => setSelectedCount(1), 1200);
|
||||
const t3 = setTimeout(() => setSelectedCount(2), 1600);
|
||||
const t4 = setTimeout(() => setSelectedCount(3), 2000);
|
||||
|
||||
// Enter pressed
|
||||
const t5 = setTimeout(() => setPhase("enter"), 2600);
|
||||
|
||||
// Cascade terminals in
|
||||
const t6 = setTimeout(() => setPhase("cascade"), 3200);
|
||||
|
||||
// Hold
|
||||
const t7 = setTimeout(() => setPhase("hold"), 3800);
|
||||
|
||||
// Fade out
|
||||
const t8 = setTimeout(() => setPhase("fadeout"), 6200);
|
||||
|
||||
// Pause then restart
|
||||
const t9 = setTimeout(() => {
|
||||
setPhase("pause");
|
||||
setCycle((c) => c + 1);
|
||||
}, 7000);
|
||||
|
||||
return [t1, t2, t3, t4, t5, t6, t7, t8, t9];
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = cycle === 0 ? 400 : 2000;
|
||||
const start = setTimeout(() => {
|
||||
const timers = runCycle();
|
||||
return () => timers.forEach(clearTimeout);
|
||||
}, delay);
|
||||
return () => clearTimeout(start);
|
||||
}, [cycle, runCycle]);
|
||||
|
||||
const showCladm = phase === "typing" || phase === "selecting" || phase === "enter";
|
||||
const showCascade = phase === "cascade" || phase === "hold" || phase === "fadeout";
|
||||
|
||||
return (
|
||||
<div className="relative w-full min-h-[340px]">
|
||||
{/* ── CLADM console (cause) ── */}
|
||||
<div
|
||||
className={`transition-all duration-500 ${
|
||||
showCladm ? "opacity-100 scale-100" : "opacity-0 scale-95 pointer-events-none absolute inset-0"
|
||||
}`}
|
||||
>
|
||||
<div className="pixel-border bg-surface overflow-hidden">
|
||||
{/* Title bar */}
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-surface-2 border-b-2 border-border">
|
||||
<div className="w-3 h-3 bg-[#ff5f56]" />
|
||||
<div className="w-3 h-3 bg-[#ffbd2e]" />
|
||||
<div className="w-3 h-3 bg-[#27c93f]" />
|
||||
<span className="ml-3 font-[family-name:var(--font-mono)] text-dim text-xs">
|
||||
cladm — {selectedCount} selected
|
||||
</span>
|
||||
</div>
|
||||
{/* Project rows */}
|
||||
<div className="p-3 font-[family-name:var(--font-mono)] text-[11px] leading-relaxed">
|
||||
<div className="text-dim mb-1 text-[10px]">
|
||||
{" PROJECT BRANCH LAST USE"}
|
||||
</div>
|
||||
{projects.map((proj, i) => {
|
||||
const checked = i < selectedCount;
|
||||
const isActive = i === selectedCount - 1 && phase === "selecting";
|
||||
return (
|
||||
<div
|
||||
key={`${proj.name}-${cycle}`}
|
||||
className={`px-1 transition-colors duration-150 ${
|
||||
isActive ? "bg-[#283457]" : ""
|
||||
}`}
|
||||
>
|
||||
<span className={checked ? "text-green" : "text-dim"}>
|
||||
{checked ? "[✓]" : "[ ]"}
|
||||
</span>
|
||||
<span className="text-text"> {proj.name.padEnd(20)}</span>
|
||||
<span className="text-magenta">{proj.branch.padEnd(13)}</span>
|
||||
<span className="text-cyan">{proj.time}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="px-1 text-dim">
|
||||
[ ] pixel-engine{" "}develop{" "}3h ago
|
||||
</div>
|
||||
|
||||
{/* Enter hint */}
|
||||
<div className="mt-3 pt-2 border-t border-border text-[10px]">
|
||||
{phase === "enter" ? (
|
||||
<span className="text-accent font-bold cascade-flash">
|
||||
⏎ Launching 3 projects...
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-dim">
|
||||
↑↓ navigate · space toggle · enter launch
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Terminal cascade (effect) ── */}
|
||||
<div
|
||||
className={`transition-opacity duration-500 ${
|
||||
showCascade ? "opacity-100" : "opacity-0 pointer-events-none absolute inset-0"
|
||||
}`}
|
||||
>
|
||||
<div className="relative h-[320px]">
|
||||
{projects.map((proj, i) => (
|
||||
<div
|
||||
key={`term-${proj.name}-${cycle}`}
|
||||
className={`absolute left-0 right-0 border-2 bg-surface overflow-hidden
|
||||
${phase === "cascade" || phase === "hold" ? "cascade-in" : ""}
|
||||
${phase === "hold" && i === projects.length - 1 ? "cascade-glow" : ""}`}
|
||||
style={{
|
||||
animationDelay: `${i * 0.2}s`,
|
||||
top: `${i * 80}px`,
|
||||
marginLeft: `${i * 16}px`,
|
||||
marginRight: `${(projects.length - 1 - i) * 16}px`,
|
||||
zIndex: i + 1,
|
||||
borderColor: "var(--color-border)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 px-3 py-1 bg-surface-2 border-b border-border">
|
||||
<div className="w-[7px] h-[7px] bg-[#ff5f56]" />
|
||||
<div className="w-[7px] h-[7px] bg-[#ffbd2e]" />
|
||||
<div className="w-[7px] h-[7px] bg-[#27c93f]" />
|
||||
<span className="ml-2 font-[family-name:var(--font-mono)] text-dim text-[9px] truncate">
|
||||
claude — {proj.name}
|
||||
</span>
|
||||
</div>
|
||||
<Image
|
||||
src="/claude-welcome.png"
|
||||
alt="Claude Code welcome screen"
|
||||
width={570}
|
||||
height={260}
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
BIN
site/public/claude-welcome.png
Normal file
BIN
site/public/claude-welcome.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Reference in New Issue
Block a user