update landing page styles

This commit is contained in:
Aman Varshney
2025-04-05 22:57:09 +05:30
parent 6b2cde8030
commit b5f106dd47
10 changed files with 169 additions and 321 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { motion } from "framer-motion";
import { Check, CircleCheck, ClipboardCopy, Terminal } from "lucide-react";
import { Check, ClipboardCopy, Terminal } from "lucide-react";
import { useEffect, useRef, useState } from "react";
const CodeContainer = () => {
@@ -8,24 +8,37 @@ const CodeContainer = () => {
const [selectedPM, setSelectedPM] = useState<"npm" | "pnpm" | "bun">("bun");
const [copied, setCopied] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const [typingComplete, setTypingComplete] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const [showCursor, setShowCursor] = useState(true);
const [step, setStep] = useState(0);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
const handleClickOutside = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node))
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
useEffect(() => {
const interval = setInterval(() => setShowCursor((p) => !p), 500);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (step < 5) {
const timer = setTimeout(
() => setStep((s) => s + 1),
step === 0 ? 1000 : 400,
);
return () => clearTimeout(timer);
}
}, [step]);
const commands = {
npm: "npx create-better-t-stack@latest my-better-t-app --yes",
pnpm: "pnpm create better-t-stack@latest my-better-t-app --yes",
bun: "bun create better-t-stack@latest my-better-t-app --yes",
npm: "npx create-better-t-stack@latest my-app",
pnpm: "pnpm create better-t-stack@latest my-app",
bun: "bun create better-t-stack@latest my-app",
};
const copyToClipboard = async (pm: "npm" | "pnpm" | "bun") => {
@@ -36,54 +49,35 @@ const CodeContainer = () => {
setTimeout(() => setCopied(false), 2000);
};
useEffect(() => {
if (!typingComplete) {
const timer = setTimeout(() => {
setTypingComplete(true);
}, 1000);
return () => clearTimeout(timer);
}
if (typingComplete && currentStep < 5) {
const timer = setTimeout(() => {
setCurrentStep((prev) => prev + 1);
}, 800);
return () => clearTimeout(timer);
}
}, [typingComplete, currentStep]);
return (
<div className="mx-auto mt-4 w-full max-w-3xl sm:mt-8">
<div className="overflow-hidden rounded-xl border border-gray-300 bg-gray-100 text-gray-800 shadow-xl dark:border-gray-700 dark:bg-black dark:text-white">
<div className="flex items-center justify-between bg-gray-200 px-3 py-2 sm:px-4 dark:bg-gray-800">
<div className="flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500" />
<div className="h-3 w-3 rounded-full bg-yellow-500" />
<div className="h-3 w-3 rounded-full bg-green-500" />
<div className="mx-auto mt-4 w-full max-w-3xl">
<div className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-md dark:border-gray-800 dark:bg-gray-950">
<div className="flex items-center justify-between bg-gray-100 px-3 py-2 dark:bg-gray-900">
<div className="flex gap-1.5">
<div className="h-2.5 w-2.5 rounded-full bg-red-500" />
<div className="h-2.5 w-2.5 rounded-full bg-yellow-500" />
<div className="h-2.5 w-2.5 rounded-full bg-green-500" />
</div>
<div className="xs:block hidden font-mono text-[10px] text-gray-600 sm:text-xs dark:text-gray-400">
Quick Install Terminal
<div className="text-gray-600 text-xs dark:text-gray-400">
Terminal
</div>
<div className="relative" ref={menuRef}>
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="flex items-center rounded border border-gray-300 bg-gray-300/50 px-1.5 py-1 text-[10px] hover:bg-gray-300/80 sm:px-2 sm:text-xs dark:border-gray-700 dark:bg-gray-800/50 dark:hover:bg-gray-700/50"
className="flex items-center gap-1 rounded border border-gray-200 bg-white px-2 py-1 text-gray-700 text-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
>
<Terminal className="mr-1 h-3 w-3 text-gray-600 dark:text-gray-400">
<title>Package Manager</title>
</Terminal>
<span className="mr-1 text-gray-700 dark:text-gray-300">
{selectedPM}
</span>
<Terminal className="h-3 w-3 text-gray-600 dark:text-gray-400" />
<span>{selectedPM}</span>
<svg
className="h-3 w-3 text-gray-700 dark:text-gray-400"
className="h-3 w-3 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<title>Toggle Dropdown</title>
<title>arrow</title>
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -97,254 +91,137 @@ const CodeContainer = () => {
<motion.div
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
className="absolute right-0 z-50 mt-1 w-32 rounded-md border border-gray-300 bg-white shadow-lg sm:w-36 dark:border-gray-700 dark:bg-gray-900"
className="absolute right-0 z-50 mt-1 w-28 rounded-md border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
>
<ul>
{(Object.keys(commands) as Array<"npm" | "pnpm" | "bun">).map(
(pm) => (
<li key={pm}>
<button
type="button"
className={`block w-full px-3 py-1.5 text-left text-[10px] sm:text-xs ${
selectedPM === pm
? "border-blue-500 border-l-2 bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
: "text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
}`}
onClick={() => copyToClipboard(pm)}
>
{pm === "npm" && "npm"}
{pm === "pnpm" && "pnpm"}
{pm === "bun" && (
<span className="flex items-center">
<span className="mr-1">🥟</span> bun
</span>
)}
</button>
</li>
),
)}
</ul>
{(["npm", "pnpm", "bun"] as const).map((pm) => (
<button
type="button"
key={pm}
className={`block w-full px-3 py-1.5 text-left text-xs ${
selectedPM === pm
? "bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-gray-200"
: "text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700/50"
}`}
onClick={() => copyToClipboard(pm)}
>
{pm === "bun" ? "🥟 bun" : pm}
</button>
))}
</motion.div>
)}
</div>
</div>
<div className="overflow-x-auto bg-gray-50 p-3 font-mono text-xs sm:p-4 sm:text-sm dark:bg-gray-900">
<div className="bg-gray-50 p-4 text-left font-mono text-sm dark:bg-gray-900">
<div className="flex items-center">
<div className="flex-grow overflow-x-auto">
<span className="mr-2 text-green-600 dark:text-green-400">$</span>
<span className="text-gray-700 dark:text-gray-300">
<span className="mr-2 text-gray-600 dark:text-gray-400">$</span>
<div className="flex-grow">
<span className="text-gray-800 dark:text-gray-200">
{commands[selectedPM]}
</span>
<span
className={
typingComplete
? "hidden"
: "ml-1 animate-pulse text-blue-600 dark:text-blue-500"
}
>
</span>
{step === 0 && (
<span
className={`ml-0.5 inline-block h-4 w-2 bg-gray-800 dark:bg-white ${showCursor ? "opacity-100" : "opacity-0"}`}
/>
)}
</div>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
<button
type="button"
onClick={() => copyToClipboard(selectedPM)}
className="ml-2 flex-shrink-0 text-gray-600 transition-colors hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
title="Copy command"
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
>
{copied ? (
<Check className="h-4 w-4">
<title>Copied!</title>
</Check>
<Check className="h-4 w-4 text-gray-600 dark:text-gray-400" />
) : (
<ClipboardCopy className="h-4 w-4">
<title>Copy to clipboard</title>
</ClipboardCopy>
<ClipboardCopy className="h-4 w-4" />
)}
</motion.button>
</button>
</div>
{typingComplete && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="overflow-x-auto"
>
<div className="mt-3 pl-2 text-amber-600 sm:pl-4 dark:text-amber-400">
{currentStep >= 1 && (
<motion.p
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
Creating a new Better-T-Stack project
</motion.p>
)}
{currentStep >= 2 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="mt-2"
>
<p className="text-gray-700 dark:text-gray-300">
Project name:{" "}
<span className="text-amber-600 dark:text-amber-400">
my-better-t-app
</span>
</p>
<p className="text-gray-700 dark:text-gray-300">
Frontend:{" "}
<span className="text-amber-600 dark:text-amber-400">
React Web
</span>
</p>
<p className="text-gray-700 dark:text-gray-300">
Runtime:{" "}
<span className="text-amber-600 dark:text-amber-400">
Bun
</span>
</p>
<p className="text-gray-700 dark:text-gray-300">
Backend:{" "}
<span className="text-amber-600 dark:text-amber-400">
Hono
</span>
</p>
<p className="text-gray-700 dark:text-gray-300">
Database:{" "}
<span className="text-amber-600 dark:text-amber-400">
SQLite + Drizzle
</span>
</p>
</motion.div>
)}
</div>
{step > 0 && (
<div className="mt-3 space-y-1.5 text-sm">
{step > 0 && (
<div className="text-gray-600 dark:text-gray-400">
Creating a new Better-T-Stack project
</div>
)}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
className="mt-3 pl-2 sm:pl-4"
>
{currentStep >= 3 && (
<motion.p
initial={{ opacity: 0, x: -5 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.4 }}
className="flex items-center text-blue-600 dark:text-blue-400"
>
<CircleCheck className="mr-1 h-4 w-4 flex-shrink-0">
<title>Completed</title>
</CircleCheck>
<span>Creating project structure</span>
</motion.p>
)}
{currentStep >= 4 && (
<motion.p
initial={{ opacity: 0, x: -5 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.5 }}
className="flex items-center text-blue-600 dark:text-blue-400"
>
<CircleCheck className="mr-1 h-4 w-4 flex-shrink-0">
<title>Completed</title>
</CircleCheck>
<span>Installing dependencies</span>
</motion.p>
)}
{currentStep >= 5 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
>
<motion.p
initial={{ opacity: 0, x: -5 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.7 }}
className="flex items-center text-blue-600 dark:text-blue-400"
>
<CircleCheck className="mr-1 h-4 w-4 flex-shrink-0">
<title>Completed</title>
</CircleCheck>
<span>Setting up database schema</span>
</motion.p>
<motion.p
initial={{ opacity: 0, x: -5 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.8 }}
className="flex items-center text-blue-600 dark:text-blue-400"
>
<CircleCheck className="mr-1 h-4 w-4 flex-shrink-0">
<title>Completed</title>
</CircleCheck>
<span>Configuring authentication</span>
</motion.p>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.9 }}
className="mt-4 flex xs:flex-row flex-col xs:items-center rounded border border-blue-300 bg-blue-100 px-2 py-2 text-[10px] text-blue-800 sm:text-xs dark:border-blue-800/30 dark:bg-blue-900/20 dark:text-blue-300"
>
<svg
className="xs:mr-2 mb-1 xs:mb-0 h-4 w-4 flex-shrink-0 text-blue-600 dark:text-blue-400"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<title>Success</title>
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
<div className="flex flex-wrap">
<span className="mr-1">Project ready! Run</span>
<code className="mr-1 mb-1 xs:mb-0 rounded bg-blue-200 px-1 py-0.5 dark:bg-blue-800/50">
cd my-better-t-app
</code>
<span className="mr-1">and</span>
<code className="rounded bg-blue-200 px-1 py-0.5 dark:bg-blue-800/50">
{selectedPM === "npm" && "npm run dev"}
{selectedPM === "pnpm" && "pnpm dev"}
{selectedPM === "bun" && "bun dev"}
</code>
</div>
</motion.div>
</motion.div>
)}
</motion.div>
</motion.div>
{step > 1 && (
<div className="ml-2 grid grid-cols-[80px_1fr] gap-x-2 text-gray-700 text-xs dark:text-gray-300">
<span>Project:</span>
<span className="text-gray-800 dark:text-gray-200">
my-app
</span>
<span>Frontend:</span>
<span className="text-gray-800 dark:text-gray-200">
React Web
</span>
<span>Backend:</span>
<span className="text-gray-800 dark:text-gray-200">Hono</span>
<span>Database:</span>
<span className="text-gray-800 dark:text-gray-200">
SQLite + Drizzle
</span>
</div>
)}
{step > 2 && (
<div className="text-gray-600 dark:text-gray-400">
Creating project structure
</div>
)}
{step > 3 && (
<div className="text-gray-600 dark:text-gray-400">
Installing dependencies
</div>
)}
{step > 4 && (
<div className="mt-2 border-gray-400 border-l-2 bg-gray-100 py-2 pl-3 text-xs dark:border-gray-600 dark:bg-gray-800">
<span className="font-semibold text-gray-800 dark:text-gray-200">
Project created successfully! Run:
</span>
<div className="mt-1 flex flex-wrap gap-1">
<code className="rounded bg-gray-200 px-1 py-0.5 text-gray-800 dark:bg-gray-700 dark:text-white">
cd my-app
</code>
<span className="text-gray-700 dark:text-gray-300">
and
</span>
<code className="rounded bg-gray-200 px-1 py-0.5 text-gray-800 dark:bg-gray-700 dark:text-white">
{selectedPM === "npm"
? "npm run dev"
: selectedPM === "pnpm"
? "pnpm dev"
: "bun dev"}
</code>
</div>
</div>
)}
</div>
)}
<div
className={`mt-4 flex ${
currentStep >= 5 && typingComplete ? "" : "hidden"
}`}
>
<span className="mr-2 text-green-600 dark:text-green-400">$</span>
<span className="animate-pulse text-blue-600 dark:text-blue-500">
</span>
</div>
{step > 4 && (
<div className="mt-3 flex items-center">
<span className="mr-2 text-gray-600 dark:text-gray-400">$</span>
<span
className={`inline-block h-4 w-2 bg-gray-800 dark:bg-white ${showCursor ? "opacity-100" : "opacity-0"}`}
/>
</div>
)}
</div>
<div className="border-gray-300 border-t bg-gray-200 px-2 py-2 sm:px-4 dark:border-gray-700 dark:bg-gray-900">
<div className="flex items-center justify-center text-center text-[10px] text-gray-600 sm:text-xs dark:text-gray-400">
<span className="inline-flex flex-wrap items-center justify-center gap-1">
<span>For custom options, use</span>
<code className="whitespace-nowrap rounded bg-gray-300 px-1 py-0.5 dark:bg-gray-700">
{selectedPM === "npm" && "npx"}
{selectedPM === "pnpm" && "pnpm dlx"}
{selectedPM === "bun" && "bunx"} create-better-t-stack
</code>
<span>without flags</span>
</span>
</div>
<div className="border-gray-200 border-t bg-gray-50 px-4 py-1.5 text-left text-gray-600 text-xs dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400">
For customization options:{" "}
<code className="rounded bg-gray-200 px-1 py-0.5 text-gray-700 dark:bg-gray-700 dark:text-gray-300">
{selectedPM === "npm"
? "npx"
: selectedPM === "pnpm"
? "pnpm dlx"
: "bunx"}{" "}
create-better-t-stack
</code>
</div>
</div>
</div>

View File

@@ -13,11 +13,8 @@ export default function CustomizableSection() {
transition={{ duration: 0.5 }}
className="relative"
>
<h2 className="font-bold text-3xl sm:text-4xl">
<span className="mr-1 font-mono text-blue-500 dark:text-blue-400">
{">"}
</span>
<span className="bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent dark:from-blue-400 dark:to-indigo-500">
<h2 className="font-bold font-mono text-2xl tracking-tight sm:text-3xl md:text-4xl lg:text-5xl">
<span className="border-blue-500 border-b-2 pb-1 text-gray-900 dark:text-blue-100">
Your Stack, Your Choice
</span>
</h2>

View File

@@ -1,18 +0,0 @@
const SideCircles = () => {
return (
<>
<div>
<div className="-translate-y-1/2 -left-[30%] fixed top-1/2 z-40 h-[50vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl" />
<div className="-translate-y-1/2 -left-[35%] fixed top-1/2 z-50 h-[40vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl" />
<div className="-translate-y-1/2 -left-[25%] fixed top-1/2 z-30 h-[60vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl" />
</div>
<div>
<div className="-translate-y-1/2 -right-[35%] fixed top-1/2 z-50 h-[40vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl" />
<div className="-translate-y-1/2 -right-[30%] fixed top-1/2 z-40 h-[50vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl" />
<div className="-translate-y-1/2 -right-[25%] fixed top-1/2 z-30 h-[60vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl" />
</div>
</>
);
};
export default SideCircles;

View File

@@ -19,7 +19,9 @@ const TWEET_IDS = [
"1904301540422070671",
"1904338606409531710",
"1904318186750652606",
"1908568583799484519",
"1904179661086556412",
"1908558365128876311",
"1907772878139072851",
"1906149740095705265",
"1906001923456790710",
@@ -82,11 +84,8 @@ export default function Testimonials() {
transition={{ duration: 0.5 }}
className="relative"
>
<h2 className="font-bold text-3xl sm:text-4xl">
<span className="mr-1 font-mono text-blue-500 dark:text-blue-400">
{">"}
</span>
<span className="bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent dark:from-blue-400 dark:to-indigo-500">
<h2 className="font-bold font-mono text-2xl tracking-tight sm:text-3xl md:text-4xl lg:text-5xl">
<span className="border-blue-500 border-b-2 pb-1 text-gray-900 dark:text-blue-100">
Developer Feedback
</span>
</h2>