-
- --multiple-database-options
-
-
- --flexible-authentication
-
-
- --alternative-orms
-
-
- --framework-choices
-
+
+
+ --multiple-runtimes
+
+
+
+ --framework-choices
+
+
+
+ --database-options
+
+
+
+ --customizable-addons
+
+
+
+ Bun or Node
+ Hono or Elysia
+ SQLite or PostgreSQL
+ Drizzle or Prisma
+ Authentication Options
+ Optional Addons
+
+
-
-
+
+
);
}
+
+// Helper component for colored badge pills
+function Badge({
+ children,
+ color,
+}: { children: React.ReactNode; color: string }) {
+ const colorMap = {
+ amber:
+ "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300",
+ blue: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300",
+ indigo:
+ "bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300",
+ cyan: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300",
+ green:
+ "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300",
+ violet:
+ "bg-violet-100 text-violet-800 dark:bg-violet-900/30 dark:text-violet-300",
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/web/src/app/(home)/_components/CustomizableStack.tsx b/apps/web/src/app/(home)/_components/CustomizableStack.tsx
deleted file mode 100644
index 91f2410..0000000
--- a/apps/web/src/app/(home)/_components/CustomizableStack.tsx
+++ /dev/null
@@ -1,463 +0,0 @@
-"use client";
-
-import {
- Background,
- type Connection,
- ReactFlow,
- useEdgesState,
- useNodesState,
-} from "@xyflow/react";
-import { useCallback, useEffect, useState } from "react";
-import { TechSelector } from "./TechSelector";
-import "@xyflow/react/dist/style.css";
-import { initialNodes } from "@/lib/constant";
-import { CommandDisplay } from "./CommandDisplay";
-import { TechNodeComponent } from "./TechNodeComponent";
-
-// Define initial edges with proper connections
-const initialEdges = [
- { id: "bun-hono", source: "bun", target: "hono", animated: true },
- { id: "bun-tanstack", source: "bun", target: "tanstack", animated: true },
- { id: "hono-sqlite", source: "hono", target: "sqlite", animated: true },
- { id: "sqlite-drizzle", source: "sqlite", target: "drizzle", animated: true },
- {
- id: "hono-better-auth",
- source: "hono",
- target: "better-auth",
- animated: true,
- },
- { id: "bun-tailwind", source: "bun", target: "tailwind", animated: true },
- {
- id: "tailwind-shadcn",
- source: "tailwind",
- target: "shadcn",
- animated: true,
- },
-];
-
-const nodeTypes = {
- techNode: TechNodeComponent,
-};
-
-interface ActiveNodes {
- backend: string;
- database: string;
- orm: string;
- auth: string;
- packageManager: string;
- addons: {
- docker: boolean;
- githubActions: boolean;
- seo: boolean;
- git: boolean;
- };
-}
-
-const CustomizableStack = () => {
- const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
- const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
- const [activeNodes, setActiveNodes] = useState
({
- backend: "hono",
- database: "sqlite",
- orm: "drizzle",
- auth: "better-auth",
- packageManager: "npm",
- addons: {
- docker: false,
- githubActions: false,
- seo: false,
- git: true,
- },
- });
- const [windowSize, setWindowSize] = useState("lg");
- const [command, setCommand] = useState("npx create-better-t-stack my-app -y");
-
- useEffect(() => {
- const handleResize = () => {
- if (window.innerWidth < 1024 && window.innerWidth > 768) {
- setWindowSize("md");
- } else if (window.innerWidth < 768) {
- setWindowSize("sm");
- } else {
- setWindowSize("lg");
- }
- };
-
- handleResize();
- window.addEventListener("resize", handleResize);
- return () => window.removeEventListener("resize", handleResize);
- }, []);
-
- // biome-ignore lint/correctness/useExhaustiveDependencies:
- useEffect(() => {
- // Generate command whenever activeNodes changes and update the command state
- setCommand(generateCommand());
- }, [activeNodes]);
-
- // Function to remove connections related to specific category
- const removeConnectionsByCategory = useCallback(
- (category: string) => {
- setEdges((eds) => {
- return eds.filter((edge) => {
- // Find source and target nodes
- const sourceNode = nodes.find((n) => n.id === edge.source);
- const targetNode = nodes.find((n) => n.id === edge.target);
-
- if (!sourceNode || !targetNode) return true;
-
- // Remove edges connected to the category being changed
- if (targetNode.data.category === category) return false;
-
- // Remove edges that connect from the category being changed
- if (sourceNode.data.category === category) return false;
-
- // For database changes, also remove ORM connections
- if (category === "database" && targetNode.data.category === "orm")
- return false;
-
- return true;
- });
- });
- },
- [nodes, setEdges],
- );
-
- const handleTechSelect = useCallback(
- (category: string, techId: string) => {
- // Update active nodes state
- setActiveNodes((prev) => ({
- ...prev,
- [category]: techId,
- ...(category === "addons" && {
- addons: {
- ...prev.addons,
- [techId]: !prev.addons[techId as keyof typeof prev.addons],
- },
- }),
- }));
-
- // Update node active states
- setNodes((nds) =>
- nds.map((node) => ({
- ...node,
- data: {
- ...node.data,
- isActive: node.data.isStatic
- ? true
- : node.data.category === category
- ? node.id === techId
- : node.data.isActive,
- },
- })),
- );
-
- // Remove old connections for this category
- removeConnectionsByCategory(category);
-
- // Create new connections based on the selected tech
- if (category === "backend") {
- // Connect backend to database, auth, and other core components
- const database = activeNodes.database;
- const auth = activeNodes.auth;
-
- setEdges((eds) => [
- ...eds,
- {
- id: `bun-${techId}`,
- source: "bun",
- target: techId,
- animated: true,
- },
- {
- id: `${techId}-${database}`,
- source: techId,
- target: database,
- animated: true,
- },
- {
- id: `${techId}-${auth}`,
- source: techId,
- target: auth,
- animated: true,
- },
- {
- id: `${techId}-tanstack`,
- source: techId,
- target: "tanstack",
- animated: true,
- },
- ]);
- } else if (category === "database") {
- // Connect backend to database and database to ORM
- const orm = activeNodes.orm;
-
- setEdges((eds) => [
- ...eds,
- {
- id: `hono-${techId}`,
- source: "hono",
- target: techId,
- animated: true,
- },
- // Only add ORM connection if database is not "no-database"
- ...(techId !== "no-database"
- ? [
- {
- id: `${techId}-${orm}`,
- source: techId,
- target: orm,
- animated: true,
- },
- ]
- : []),
- ]);
- } else if (category === "orm") {
- // Connect database to ORM
- const database = activeNodes.database;
-
- // Only add connection if database is not "no-database"
- if (database !== "no-database") {
- setEdges((eds) => [
- ...eds,
- {
- id: `${database}-${techId}`,
- source: database,
- target: techId,
- animated: true,
- },
- ]);
- }
- } else if (category === "auth") {
- // Connect backend to auth
- setEdges((eds) => [
- ...eds,
- {
- id: `hono-${techId}`,
- source: "hono",
- target: techId,
- animated: true,
- },
- ]);
- }
- },
- [activeNodes, setNodes, setEdges, removeConnectionsByCategory],
- );
-
- const isValidConnection = useCallback(
- (connection: Connection) => {
- const sourceNode = nodes.find((n) => n.id === connection.source);
- const targetNode = nodes.find((n) => n.id === connection.target);
-
- if (!sourceNode || !targetNode) return false;
-
- // Define valid connection patterns
- if (sourceNode.id === "hono" && targetNode.data.category === "database") {
- return ["postgres", "sqlite", "no-database"].includes(targetNode.id);
- }
-
- if (sourceNode.id === "hono" && targetNode.data.category === "auth") {
- return ["better-auth", "no-auth"].includes(targetNode.id);
- }
-
- if (
- ["postgres", "sqlite"].includes(sourceNode.id) &&
- targetNode.data.category === "orm"
- ) {
- return ["drizzle", "prisma"].includes(targetNode.id);
- }
-
- return false;
- },
- [nodes],
- );
-
- const onConnect = useCallback(
- (connection: Connection) => {
- if (!isValidConnection(connection)) return;
-
- const targetNode = nodes.find((n) => n.id === connection.target);
- if (!targetNode || !targetNode.data.category) return;
-
- // Remove existing connections for the category we're connecting to
- removeConnectionsByCategory(targetNode.data.category);
-
- // Update active nodes state
- setActiveNodes((prev) => ({
- ...prev,
- [targetNode.data.category]: connection.target,
- }));
-
- // Update node active states
- setNodes((nds) =>
- nds.map((node) => ({
- ...node,
- data: {
- ...node.data,
- isActive: node.data.isStatic
- ? true
- : node.data.category === targetNode.data.category
- ? node.id === connection.target
- : node.data.isActive,
- },
- })),
- );
-
- // Add the new connection
- setEdges((eds) => [
- ...eds,
- {
- id: `${connection.source}-${connection.target}`,
- source: connection.source,
- target: connection.target,
- animated: true,
- },
- ]);
-
- // If connecting to database, also connect to the active ORM
- if (
- targetNode.data.category === "database" &&
- targetNode.id !== "no-database"
- ) {
- const activeOrm = nodes.find(
- (n) => n.data.category === "orm" && n.data.isActive,
- );
- if (activeOrm) {
- setEdges((eds) => [
- ...eds,
- {
- id: `${connection.target}-${activeOrm.id}`,
- source: connection.target,
- target: activeOrm.id,
- animated: true,
- },
- ]);
- }
- }
- },
- [nodes, setEdges, setNodes, removeConnectionsByCategory, isValidConnection],
- );
-
- const generateCommand = useCallback(() => {
- // Start with the base command
- const command = "npx create-better-t-stack my-app";
- const flags: string[] = [];
-
- const isAllDefaults =
- activeNodes.database === "sqlite" &&
- activeNodes.auth === "better-auth" &&
- activeNodes.orm === "drizzle" &&
- activeNodes.packageManager === "npm" &&
- activeNodes.addons.git === true &&
- !activeNodes.addons.docker &&
- !activeNodes.addons.githubActions &&
- !activeNodes.addons.seo;
-
- // If using all defaults, just use -y flag
- if (isAllDefaults) {
- return `${command} -y`;
- }
-
- // Database options
- if (activeNodes.database === "postgres") {
- flags.push("--postgres");
- } else if (activeNodes.database === "sqlite") {
- flags.push("--sqlite");
- } else if (activeNodes.database === "no-database") {
- flags.push("--no-database");
- }
-
- // Authentication options
- if (activeNodes.auth === "better-auth") {
- flags.push("--auth");
- } else if (activeNodes.auth === "no-auth") {
- flags.push("--no-auth");
- }
-
- // ORM options
- if (activeNodes.orm === "drizzle") {
- flags.push("--drizzle");
- } else if (activeNodes.orm === "prisma") {
- flags.push("--prisma");
- }
-
- // Package manager options
- if (activeNodes.packageManager !== "npm") {
- flags.push(`--${activeNodes.packageManager}`);
- }
-
- // Feature flags
- if (activeNodes.addons.docker) {
- flags.push("--docker");
- }
-
- if (activeNodes.addons.githubActions) {
- flags.push("--github-actions");
- }
-
- if (activeNodes.addons.seo) {
- flags.push("--seo");
- }
-
- if (!activeNodes.addons.git) {
- flags.push("--no-git");
- }
-
- return flags.length > 0 ? `${command} ${flags.join(" ")}` : command;
- }, [activeNodes]);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Select technologies from the left panel to customize your stack. The
- graph will automatically update connections.
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default CustomizableStack;
diff --git a/apps/web/src/app/(home)/_components/Spotlight.tsx b/apps/web/src/app/(home)/_components/Spotlight.tsx
deleted file mode 100644
index cada65f..0000000
--- a/apps/web/src/app/(home)/_components/Spotlight.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from "react";
-
-const Spotlight = () => {
- return (
-
- );
-};
-
-export default Spotlight;
diff --git a/apps/web/src/app/(home)/_components/StackArchitech.tsx b/apps/web/src/app/(home)/_components/StackArchitech.tsx
new file mode 100644
index 0000000..078036e
--- /dev/null
+++ b/apps/web/src/app/(home)/_components/StackArchitech.tsx
@@ -0,0 +1,690 @@
+"use client";
+
+import { motion } from "framer-motion";
+import {
+ Check,
+ Circle,
+ CircleCheck,
+ ClipboardCopy,
+ Terminal,
+} from "lucide-react";
+import { useCallback, useEffect, useState } from "react";
+
+const triggerConfetti = () => {
+ const createConfettiElement = (color: string) => {
+ const confetti = document.createElement("div");
+ confetti.style.position = "fixed";
+ confetti.style.width = `${Math.random() * 10 + 5}px`;
+ confetti.style.height = `${Math.random() * 10 + 5}px`;
+ confetti.style.backgroundColor = color;
+ confetti.style.borderRadius = "50%";
+ confetti.style.zIndex = "9999";
+
+ const startX = window.innerWidth / 2 + (Math.random() - 0.5) * 200;
+ const startY = window.innerHeight / 2;
+
+ confetti.style.left = `${startX}px`;
+ confetti.style.top = `${startY}px`;
+
+ document.body.appendChild(confetti);
+
+ const angle = Math.random() * Math.PI * 2;
+ const velocity = Math.random() * 5 + 3;
+ const vx = Math.cos(angle) * velocity;
+ let vy = Math.sin(angle) * velocity - 2;
+
+ let posX = startX;
+ let posY = startY;
+ let opacity = 1;
+ let rotation = 0;
+
+ const animate = () => {
+ posX += vx;
+ posY += vy;
+ vy += 0.1; // Gravity
+ opacity -= 0.01;
+ rotation += 5;
+
+ confetti.style.left = `${posX}px`;
+ confetti.style.top = `${posY}px`;
+ confetti.style.opacity = `${opacity}`;
+ confetti.style.transform = `rotate(${rotation}deg)`;
+
+ if (opacity > 0 && posY < window.innerHeight) {
+ requestAnimationFrame(animate);
+ } else {
+ confetti.remove();
+ }
+ };
+
+ requestAnimationFrame(animate);
+ };
+
+ const colors = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#ffd166", "#f8a5c2"];
+ for (let i = 0; i < 30; i++) {
+ setTimeout(() => {
+ createConfettiElement(colors[Math.floor(Math.random() * colors.length)]);
+ }, Math.random() * 500);
+ }
+};
+
+const TECH_OPTIONS = {
+ runtime: [
+ {
+ id: "bun",
+ name: "Bun",
+ description: "Fast JavaScript runtime & toolkit",
+ icon: "🥟",
+ color: "from-amber-400 to-amber-600",
+ default: true,
+ },
+ {
+ id: "node",
+ name: "Node.js",
+ description: "JavaScript runtime environment",
+ icon: "🟩",
+ color: "from-green-400 to-green-600",
+ },
+ ],
+ backendFramework: [
+ {
+ id: "hono",
+ name: "Hono",
+ description: "Ultrafast web framework",
+ icon: "⚡",
+ color: "from-blue-500 to-blue-700",
+ default: true,
+ },
+ {
+ id: "elysia",
+ name: "Elysia",
+ description: "TypeScript web framework",
+ icon: "🦊",
+ color: "from-purple-500 to-purple-700",
+ },
+ ],
+ database: [
+ {
+ id: "sqlite",
+ name: "SQLite",
+ description: "File-based SQL database",
+ icon: "🗃️",
+ color: "from-blue-400 to-cyan-500",
+ default: true,
+ },
+ {
+ id: "postgres",
+ name: "PostgreSQL",
+ description: "Advanced SQL database",
+ icon: "🐘",
+ color: "from-indigo-400 to-indigo-600",
+ },
+ {
+ id: "none",
+ name: "No Database",
+ description: "Skip database integration",
+ icon: "🚫",
+ color: "from-gray-400 to-gray-600",
+ },
+ ],
+ orm: [
+ {
+ id: "drizzle",
+ name: "Drizzle",
+ description: "TypeScript ORM",
+ icon: "💧",
+ color: "from-cyan-400 to-cyan-600",
+ default: true,
+ },
+ {
+ id: "prisma",
+ name: "Prisma",
+ description: "Next-gen ORM",
+ icon: "◮",
+ color: "from-purple-400 to-purple-600",
+ },
+ ],
+ auth: [
+ {
+ id: "true",
+ name: "Better Auth",
+ description: "Simple authentication",
+ icon: "🔐",
+ color: "from-green-400 to-green-600",
+ default: true,
+ },
+ {
+ id: "false",
+ name: "No Auth",
+ description: "Skip authentication",
+ icon: "🔓",
+ color: "from-red-400 to-red-600",
+ },
+ ],
+ turso: [
+ {
+ id: "true",
+ name: "Turso",
+ description: "SQLite cloud database",
+ icon: "☁️",
+ color: "from-pink-400 to-pink-600",
+ default: false,
+ },
+ {
+ id: "false",
+ name: "No Turso",
+ description: "Skip Turso integration",
+ icon: "🚫",
+ color: "from-gray-400 to-gray-600",
+ default: true,
+ },
+ ],
+ packageManager: [
+ {
+ id: "npm",
+ name: "npm",
+ description: "Default package manager",
+ icon: "📦",
+ color: "from-red-500 to-red-700",
+ },
+ {
+ id: "pnpm",
+ name: "pnpm",
+ description: "Fast, disk space efficient",
+ icon: "🚀",
+ color: "from-orange-500 to-orange-700",
+ },
+ {
+ id: "bun",
+ name: "bun",
+ description: "All-in-one toolkit",
+ icon: "🥟",
+ color: "from-amber-500 to-amber-700",
+ default: true,
+ },
+ ],
+ addons: [
+ {
+ id: "pwa",
+ name: "PWA",
+ description: "Progressive Web App",
+ icon: "📱",
+ color: "from-blue-500 to-blue-700",
+ default: false,
+ },
+ {
+ id: "tauri",
+ name: "Tauri",
+ description: "Desktop app support",
+ icon: "🖥️",
+ color: "from-amber-500 to-amber-700",
+ default: false,
+ },
+ {
+ id: "biome",
+ name: "Biome",
+ description: "Linting & formatting",
+ icon: "🌿",
+ color: "from-green-500 to-green-700",
+ default: false,
+ },
+ {
+ id: "husky",
+ name: "Husky",
+ description: "Git hooks & lint-staged",
+ icon: "🐶",
+ color: "from-purple-500 to-purple-700",
+ default: false,
+ },
+ ],
+ examples: [
+ {
+ id: "todo",
+ name: "Todo Example",
+ description: "Simple todo application",
+ icon: "✅",
+ color: "from-indigo-500 to-indigo-700",
+ default: false,
+ },
+ ],
+ git: [
+ {
+ id: "true",
+ name: "Git",
+ description: "Initialize Git repository",
+ icon: "📝",
+ color: "from-gray-500 to-gray-700",
+ default: true,
+ },
+ {
+ id: "false",
+ name: "No Git",
+ description: "Skip Git initialization",
+ icon: "🚫",
+ color: "from-red-400 to-red-600",
+ },
+ ],
+ install: [
+ {
+ id: "true",
+ name: "Install Dependencies",
+ description: "Install packages automatically",
+ icon: "📥",
+ color: "from-green-400 to-green-600",
+ default: true,
+ },
+ {
+ id: "false",
+ name: "Skip Install",
+ description: "Skip dependency installation",
+ icon: "⏭️",
+ color: "from-yellow-400 to-yellow-600",
+ },
+ ],
+};
+
+interface StackState {
+ runtime: string;
+ backendFramework: string;
+ database: string;
+ orm: string | null;
+ auth: string;
+ turso: string;
+ packageManager: string;
+ addons: string[];
+ examples: string[];
+ git: string;
+ install: string;
+}
+
+const DEFAULT_STACK: StackState = {
+ runtime: "bun",
+ backendFramework: "hono",
+ database: "sqlite",
+ orm: "drizzle",
+ auth: "true",
+ turso: "false",
+ packageManager: "bun",
+ addons: [],
+ examples: [],
+ git: "true",
+ install: "true",
+};
+
+const StackArchitect = () => {
+ const [stack, setStack] = useState(DEFAULT_STACK);
+ const [command, setCommand] = useState("npx create-better-t-stack my-app -y");
+ const [activeTab, setActiveTab] = useState("database");
+ const [copied, setCopied] = useState(false);
+
+ useEffect(() => {
+ const cmd = generateCommand(stack);
+ setCommand(cmd);
+ }, [stack]);
+
+ const generateCommand = useCallback((stackState: StackState) => {
+ const base = "npx create-better-t-stack";
+ const projectName = "my-better-t-app";
+ const flags: string[] = ["-y"];
+
+ const isDefault =
+ stackState.runtime === "bun" &&
+ stackState.backendFramework === "hono" &&
+ stackState.database === "sqlite" &&
+ stackState.orm === "drizzle" &&
+ stackState.auth === "true" &&
+ stackState.turso === "false" &&
+ stackState.packageManager === "bun" &&
+ stackState.addons.length === 0 &&
+ stackState.examples.length === 0 &&
+ stackState.git === "true" &&
+ stackState.install === "true";
+
+ if (isDefault) return `${base} ${projectName} -y`;
+
+ if (stackState.runtime === "node") {
+ flags.push("--runtime node");
+ }
+
+ if (stackState.backendFramework === "elysia") {
+ flags.push("--elysia");
+ }
+
+ if (stackState.database === "postgres") {
+ flags.push("--postgres");
+ } else if (stackState.database === "none") {
+ flags.push("--no-database");
+ }
+
+ if (stackState.orm === "prisma") {
+ flags.push("--prisma");
+ }
+
+ if (stackState.auth === "false") {
+ flags.push("--no-auth");
+ }
+
+ if (stackState.turso === "true") {
+ flags.push("--turso");
+ }
+
+ if (stackState.packageManager !== "bun") {
+ flags.push(`--${stackState.packageManager}`);
+ }
+
+ if (stackState.addons.length > 0) {
+ for (const addon of stackState.addons) {
+ flags.push(`--${addon}`);
+ }
+ }
+
+ if (stackState.examples.length > 0) {
+ flags.push(`--examples ${stackState.examples.join(",")}`);
+ }
+
+ if (stackState.git === "false") {
+ flags.push("--no-git");
+ }
+
+ if (stackState.install === "false") {
+ flags.push("--no-install");
+ }
+
+ return flags.length > 0
+ ? `${base} ${projectName} ${flags.join(" ")}`
+ : `${base} ${projectName}`;
+ }, []);
+
+ const handleTechSelect = useCallback(
+ (category: keyof typeof TECH_OPTIONS, techId: string) => {
+ setStack((prev) => {
+ if (category === "addons" || category === "examples") {
+ const currentArray = [...(prev[category] || [])];
+ const index = currentArray.indexOf(techId);
+
+ if (index >= 0) {
+ currentArray.splice(index, 1);
+ } else {
+ currentArray.push(techId);
+ }
+
+ return {
+ ...prev,
+ [category]: currentArray,
+ };
+ }
+
+ if (category === "database") {
+ if (techId === "none") {
+ return {
+ ...prev,
+ database: techId,
+ orm: null,
+ };
+ }
+
+ if (prev.database === "none") {
+ return {
+ ...prev,
+ database: techId,
+ orm: "drizzle",
+ };
+ }
+ }
+
+ if (category === "database" && techId === "sqlite") {
+ return {
+ ...prev,
+ database: techId,
+ turso: prev.turso,
+ };
+ }
+
+ if (category === "database" && techId !== "sqlite") {
+ return {
+ ...prev,
+ database: techId,
+ turso: "false",
+ };
+ }
+
+ return {
+ ...prev,
+ [category]: techId,
+ };
+ });
+
+ triggerConfetti();
+ },
+ [],
+ );
+
+ const copyToClipboard = useCallback(() => {
+ navigator.clipboard.writeText(command);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ }, [command]);
+
+ return (
+
+
+
+
+
+ Stack Architect Terminal
+
+
+
+
+
+
+
+
+
+ $
+
+ {command}
+
+
+
+
+
+
+
+
+ Configure{" "}
+ {activeTab.charAt(0).toUpperCase() + activeTab.slice(1)}
+
+
+
+
+ {TECH_OPTIONS[activeTab as keyof typeof TECH_OPTIONS].map(
+ (tech) => {
+ let isSelected = false;
+ if (activeTab === "addons" || activeTab === "examples") {
+ isSelected = stack[activeTab].includes(tech.id);
+ } else {
+ isSelected =
+ stack[activeTab as keyof StackState] === tech.id;
+ }
+
+ const isDisabled =
+ (activeTab === "orm" && stack.database === "none") ||
+ (activeTab === "turso" && stack.database !== "sqlite");
+
+ return (
+
+ !isDisabled &&
+ handleTechSelect(
+ activeTab as keyof typeof TECH_OPTIONS,
+ tech.id,
+ )
+ }
+ >
+
+
+ {isSelected ? (
+
+ ) : (
+
+ )}
+
+
+
+ {tech.icon}
+
+ {tech.name}
+
+
+
+ {tech.description}
+
+
+ {tech.default && !isSelected && (
+
+ Default
+
+ )}
+
+
+ );
+ },
+ )}
+
+
+
+
+ Selected Stack
+
+
+
+ {
+ TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime)
+ ?.icon
+ }{" "}
+ {
+ TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime)
+ ?.name
+ }
+
+
+
+ {
+ TECH_OPTIONS.backendFramework.find(
+ (t) => t.id === stack.backendFramework,
+ )?.icon
+ }{" "}
+ {
+ TECH_OPTIONS.backendFramework.find(
+ (t) => t.id === stack.backendFramework,
+ )?.name
+ }
+
+
+
+ {
+ TECH_OPTIONS.database.find((t) => t.id === stack.database)
+ ?.icon
+ }{" "}
+ {
+ TECH_OPTIONS.database.find((t) => t.id === stack.database)
+ ?.name
+ }
+
+
+ {stack.orm && (
+
+ {TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.icon}{" "}
+ {TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.name}
+
+ )}
+
+
+ {TECH_OPTIONS.auth.find((t) => t.id === stack.auth)?.icon}{" "}
+ {TECH_OPTIONS.auth.find((t) => t.id === stack.auth)?.name}
+
+
+ {stack.turso === "true" && stack.database === "sqlite" && (
+
+ {TECH_OPTIONS.turso.find((t) => t.id === stack.turso)?.icon}{" "}
+ {TECH_OPTIONS.turso.find((t) => t.id === stack.turso)?.name}
+
+ )}
+
+ {stack.addons.map((addonId) => {
+ const addon = TECH_OPTIONS.addons.find(
+ (a) => a.id === addonId,
+ );
+ return addon ? (
+
+ {addon.icon} {addon.name}
+
+ ) : null;
+ })}
+
+
+
+
+
+
+ {Object.keys(TECH_OPTIONS).map((category) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default StackArchitect;
diff --git a/apps/web/src/app/(home)/_components/TechConstellation.tsx b/apps/web/src/app/(home)/_components/TechConstellation.tsx
deleted file mode 100644
index ba0ee4e..0000000
--- a/apps/web/src/app/(home)/_components/TechConstellation.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-"use client";
-
-import { technologies } from "@/lib/constant";
-import { type JSX, useEffect, useRef, useState } from "react";
-
-type TechConstellationProp = {
- fromRef: React.RefObject;
- toRef: React.RefObject;
- containerRef: React.RefObject;
- delay?: number;
- curveDirection?: number;
-};
-
-const AnimatedBeam = ({
- fromRef,
- toRef,
- containerRef,
- delay = 0,
- curveDirection = 50,
-}: TechConstellationProp) => {
- const [path, setPath] = useState("");
-
- useEffect(() => {
- const updatePath = () => {
- if (!fromRef.current || !toRef.current || !containerRef.current) return;
-
- const containerRect = containerRef.current.getBoundingClientRect();
- const fromRect = fromRef.current.getBoundingClientRect();
- const toRect = toRef.current.getBoundingClientRect();
-
- const fromX = fromRect.left - containerRect.left + fromRect.width / 2;
- const fromY = fromRect.top - containerRect.top + fromRect.height / 2;
- const toX = toRect.left - containerRect.left + toRect.width / 2;
- const toY = toRect.top - containerRect.top + toRect.height / 2;
-
- setPath(
- `M ${fromX},${fromY} Q ${(fromX + toX) / 2},${(fromY + toY) / 2 - curveDirection} ${toX},${toY}`,
- );
- };
-
- updatePath();
- window.addEventListener("resize", updatePath);
- return () => window.removeEventListener("resize", updatePath);
- }, [fromRef, toRef, containerRef, curveDirection]);
-
- return (
-
- );
-};
-
-const TechConstellation = () => {
- const containerRef = useRef(null);
- const centerRef = useRef(null);
- const techRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
- const [isVisible, setIsVisible] = useState(false);
- const [stars, setStars] = useState<
- Array<{
- left: string;
- top: string;
- delay: string;
- }>
- >([]);
-
- useEffect(() => {
- const newStars = Array.from({ length: 20 }, () => ({
- left: `${Math.random() * 100}%`,
- top: `${Math.random() * 100}%`,
- delay: `${Math.random() * 5}s`,
- }));
- setStars(newStars);
- }, []);
-
- const calculateRadius = (category: string) => {
- switch (category) {
- case "core":
- return 160;
- case "frontend":
- case "backend":
- return 240;
- default:
- return 200;
- }
- };
-
- const renderCategoryBeams = (category: string) => {
- const categoryTechs = technologies.filter(
- (tech) => tech.category === category,
- );
- const beams: JSX.Element[] = [];
-
- if (category !== "core") {
- categoryTechs.forEach((tech, index) => {
- const curveDirection = tech.category === "frontend" ? 50 : -50;
- beams.push(
- }
- toRef={{ current: techRefs.current[tech.name] as HTMLElement }}
- containerRef={containerRef as React.RefObject}
- delay={index * 0.2}
- curveDirection={curveDirection}
- />,
- );
- });
- }
-
- for (let i = 0; i < categoryTechs.length - 1; i++) {
- const curveDirection = category === "frontend" ? 30 : -30;
- beams.push(
- }
- delay={(i + categoryTechs.length) * 0.2}
- curveDirection={curveDirection}
- />,
- );
- }
-
- if (category === "core") {
- beams.push(
- }
- delay={0}
- curveDirection={0}
- />,
- );
- }
-
- return beams;
- };
-
- useEffect(() => {
- setIsVisible(true);
- }, []);
-
- return (
-
-
- TS
-
-
- {technologies.map((tech, index) => {
- const radius = calculateRadius(tech.category);
- const x = Math.cos((tech.angle * Math.PI) / 180) * radius;
- const y = Math.sin((tech.angle * Math.PI) / 180) * radius;
-
- return (
-
{
- techRefs.current[tech.name] = el;
- }}
- className={`absolute z-20 transform -translate-x-1/2 -translate-y-1/2 transition-all duration-1000
- ${isVisible ? "scale-100 opacity-100" : "scale-0 opacity-0"}`}
- style={{
- left: `calc(50% + ${x}px)`,
- top: `calc(50% + ${y}px)`,
- transitionDelay: `${index * 100}ms`,
- }}
- >
-
-
-
-
-
-
{tech.name}
-
{tech.description}
-
-
-
-
- {tech.name}
-
-
{tech.description}
-
-
-
- );
- })}
-
- {isVisible && (
- <>
- {renderCategoryBeams("core")}
- {renderCategoryBeams("frontend")}
- {renderCategoryBeams("backend")}
- >
- )}
-
-
- {stars.map((star) => (
-
- ))}
-
-
- );
-};
-
-export default TechConstellation;
diff --git a/apps/web/src/app/(home)/_components/TechMatrix.tsx b/apps/web/src/app/(home)/_components/TechMatrix.tsx
deleted file mode 100644
index c72dd9d..0000000
--- a/apps/web/src/app/(home)/_components/TechMatrix.tsx
+++ /dev/null
@@ -1,387 +0,0 @@
-import { AnimatePresence, motion } from "framer-motion";
-import { useEffect, useState } from "react";
-
-interface TechItem {
- name: string;
- description: string;
- category: "frontend" | "backend" | "database" | "tooling" | "deployment";
- logo?: string;
- color?: string;
-}
-
-const techStack: TechItem[] = [
- {
- name: "Next.js",
- description: "React framework for production",
- category: "frontend",
- logo: "/tech/nextjs.svg",
- color: "#ffffff",
- },
- {
- name: "TypeScript",
- description: "Strongly typed programming language",
- category: "frontend",
- logo: "/tech/typescript.svg",
- color: "#3178c6",
- },
- {
- name: "tRPC",
- description: "End-to-end typesafe APIs",
- category: "backend",
- logo: "/tech/trpc.svg",
- color: "#398CCB",
- },
- {
- name: "Tailwind CSS",
- description: "Utility-first CSS framework",
- category: "frontend",
- logo: "/tech/tailwind.svg",
- color: "#38bdf8",
- },
- {
- name: "Prisma",
- description: "Next-generation ORM",
- category: "database",
- logo: "/tech/prisma.svg",
- color: "#5a67d8",
- },
- {
- name: "PostgreSQL",
- description: "Advanced open source database",
- category: "database",
- logo: "/tech/postgresql.svg",
- color: "#336791",
- },
- {
- name: "Zod",
- description: "TypeScript-first schema validation",
- category: "backend",
- logo: "/tech/zod.svg",
- color: "#3E67B1",
- },
- {
- name: "Auth.js",
- description: "Authentication for the web",
- category: "backend",
- logo: "/tech/authjs.svg",
- color: "#32383E",
- },
- {
- name: "Turborepo",
- description: "High-performance build system",
- category: "tooling",
- logo: "/tech/turborepo.svg",
- color: "#EF4444",
- },
- {
- name: "Docker",
- description: "Containerization platform",
- category: "deployment",
- logo: "/tech/docker.svg",
- color: "#2496ED",
- },
- {
- name: "ESLint",
- description: "Pluggable JavaScript linter",
- category: "tooling",
- logo: "/tech/eslint.svg",
- color: "#4B32C3",
- },
- {
- name: "Prettier",
- description: "Opinionated code formatter",
- category: "tooling",
- logo: "/tech/prettier.svg",
- color: "#F7B93E",
- },
-];
-
-export default function TechMatrix() {
- const [selectedCategory, setSelectedCategory] = useState(null);
- const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });
- const [typedCommand, setTypedCommand] = useState("");
- const [isTyping, setIsTyping] = useState(false);
- const fullCommand = "show tech-stack --category all";
-
- useEffect(() => {
- if (isTyping) return;
-
- setIsTyping(true);
- let index = 0;
-
- const typeInterval = setInterval(() => {
- if (index < fullCommand.length) {
- setTypedCommand(fullCommand.substring(0, index + 1));
- index++;
- } else {
- clearInterval(typeInterval);
- }
- }, 80);
-
- return () => clearInterval(typeInterval);
- }, [isTyping]);
-
- const categories = Array.from(
- new Set(techStack.map((item) => item.category)),
- );
-
- const filteredTech = selectedCategory
- ? techStack.filter((tech) => tech.category === selectedCategory)
- : techStack;
-
- const handleMouseMove = (e: React.MouseEvent) => {
- const rect = e.currentTarget.getBoundingClientRect();
- setCursorPosition({
- x: e.clientX - rect.left,
- y: e.clientY - rect.top,
- });
- };
-
- return (
-
- {/* Floating particles effect */}
-
- {[...Array(6)].map((_, i) => (
-
- key={i}
- className="absolute rounded-full bg-blue-500/10 dark:bg-blue-500/10 blur-xl"
- style={{
- width: `${Math.random() * 200 + 50}px`,
- height: `${Math.random() * 200 + 50}px`,
- top: `${Math.random() * 100}%`,
- left: `${Math.random() * 100}%`,
- animation: `float ${Math.random() * 10 + 10}s linear infinite`,
- opacity: Math.random() * 0.5,
- }}
- />
- ))}
-
-
- {/* Main container */}
-
- {/* Gradient border effect */}
-
-
- {/* Terminal header */}
-
-
-
-
- user@better-t-stack
-
- :
-
- ~/tech-matrix
-
- $
-
-
- v1.0.0
-
-
-
-
- {/* Command line interface effect */}
-
- $
- {typedCommand}
-
-
-
- {/* Category filters */}
-
-
-
-
- {categories.map((category) => (
-
- ))}
-
-
- {/* Tech stack display */}
-
-
-
-
- {"// Better-T Stack Tech Matrix"}
-
-
- {"const techStack = {"}
-
-
-
-
- {filteredTech.map((tech, index) => (
-
-
-
-
- {tech.logo && (
-
- )}
-
-
-
- {tech.name}
-
-
- :{" "}
-
-
- "{tech.description}"
-
- {index < filteredTech.length - 1 && (
-
- ,
-
- )}
-
-
-
- {`// ${tech.category}`}
-
- [installed]
-
-
-
-
-
-
- ))}
-
-
-
-
{"};"}
-
- {/* Terminal footer */}
-
-
- ${" "}
- run better-t-stack --with-typesafety
-
-
- Ready for deployment...
-
-
-
-
-
-
- {/* Add animated style tag for custom animations */}
-
-
- );
-}
diff --git a/apps/web/src/app/(home)/_components/TechNodeComponent.tsx b/apps/web/src/app/(home)/_components/TechNodeComponent.tsx
deleted file mode 100644
index d73c246..0000000
--- a/apps/web/src/app/(home)/_components/TechNodeComponent.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Handle, Position } from "@xyflow/react";
-
-const categoryIndicators = {
- core: "before:bg-amber-500",
- frontend: "before:bg-blue-500",
- backend: "before:bg-emerald-500",
- database: "before:bg-purple-500",
- auth: "before:bg-red-500",
- orm: "before:bg-orange-500",
-};
-
-interface TechNodeData {
- category: keyof typeof categoryIndicators;
- isActive: boolean;
- label: string;
- description: string;
- isDefault?: boolean;
- isStatic?: boolean;
-}
-
-export function TechNodeComponent({ data }: { data: TechNodeData }) {
- const baseStyles = `
- relative lg:px-5 lg:py-4 px-3 py-1 rounded-lg
- transition-all duration-300
- dark:border-white/20 border-gray-300/30
- before:content-[''] before:absolute before:left-0 before:top-0 before:w-1.5 before:h-full
- before:rounded-l-xl ${categoryIndicators[data.category]}
- `;
-
- const activeStyles = data.isActive
- ? "opacity-100 dark:bg-gradient-to-br dark:from-indigo-900/30 dark:to-violet-900/30 bg-gradient-to-br from-indigo-50 to-violet-50"
- : "opacity-80 hover:opacity-95 dark:bg-slate-800 bg-slate-100";
-
- return (
-
- {data.label !== "Bun" && (
-
- )}
-
-
-
- {data.label}
-
-
- {data.description}
-
- {!data.isDefault && !data.isStatic && (
-
- Alternative Option
-
- )}
-
-
-
-
- );
-}
diff --git a/apps/web/src/app/(home)/_components/TechSelector.tsx b/apps/web/src/app/(home)/_components/TechSelector.tsx
deleted file mode 100644
index 3865cb0..0000000
--- a/apps/web/src/app/(home)/_components/TechSelector.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-interface ActiveNodes {
- backend: string;
- database: string;
- orm: string;
- auth: string;
- packageManager: string;
- addons: {
- docker: boolean;
- githubActions: boolean;
- seo: boolean;
- git: boolean;
- };
-}
-
-type TechOption = {
- id: string;
- label: string;
- category: string;
-};
-
-const techOptions: Record
= {
- database: [
- { id: "sqlite", label: "SQLite", category: "database" },
- { id: "postgres", label: "PostgreSQL", category: "database" },
- { id: "no-database", label: "No DB", category: "database" },
- ],
- orm: [
- { id: "drizzle", label: "Drizzle", category: "orm" },
- { id: "prisma", label: "Prisma", category: "orm" },
- ],
- auth: [
- { id: "better-auth", label: "Auth", category: "auth" },
- { id: "no-auth", label: "No Auth", category: "auth" },
- ],
- packageManager: [
- { id: "npm", label: "NPM", category: "packageManager" },
- { id: "pnpm", label: "PNPM", category: "packageManager" },
- { id: "bun", label: "Bun", category: "packageManager" },
- ],
- addons: [
- { id: "docker", label: "Docker", category: "addons" },
- { id: "githubActions", label: "GitHub Actions", category: "addons" },
- { id: "seo", label: "SEO", category: "addons" },
- { id: "git", label: "Git", category: "addons" },
- ],
-};
-
-interface TechSelectorProps {
- onSelect: (category: string, techId: string) => void;
- activeNodes: ActiveNodes;
-}
-export function TechSelector({ onSelect, activeNodes }: TechSelectorProps) {
- return (
-
-
- Options
-
-
- {Object.entries(techOptions)
- .filter(([category]) => category !== "addons")
- .map(([category, options]) => (
-
-
- {category}
-
-
- {options.map((option) => (
-
- ] === option.id && "bg-blue-600 dark:text-white text-white"
- }`}
- onClick={() => onSelect(category, option.id)}
- >
- {option.label}
-
- ))}
-
-
- ))}
-
-
-
Addons
-
- {techOptions.addons.map((option) => (
- onSelect("addons", option.id)}
- >
- {option.label}
-
- ))}
-
-
-
- );
-}
-
-const Badge = ({
- children,
- className,
- ...props
-}: {
- children: React.ReactNode;
- variant: "primary" | "secondary";
- className?: string;
- onClick?: () => void;
-}) => {
- return (
-
- {children}
-
- );
-};
diff --git a/apps/web/src/app/(home)/_components/Terminal.tsx b/apps/web/src/app/(home)/_components/Terminal.tsx
deleted file mode 100644
index 6991d3c..0000000
--- a/apps/web/src/app/(home)/_components/Terminal.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from "react";
-
-const TerminalDisplay = () => {
- const TITLE_TEXT = `
-╔════════════════════════════════════════════════════════════╗
-║ ║
-║ ██████╗ ███████╗████████╗████████╗███████╗██████╗ ║
-║ ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗ ║
-║ ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝ ║
-║ ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗ ║
-║ ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║ ║
-║ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ║
-║ ║
-║ ████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗ ║
-║ ╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ ║
-║ ██║ ███████╗ ██║ ███████║██║ █████╔╝ ║
-║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗ ║
-║ ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗ ║
-║ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ║
-║ ║
-╚════════════════════════════════════════════════════════════╝
- `;
-
- return (
-
-
-
-
-
-
- ➜
- ~
- $
-
- npx create-better-t-stack@latest
-
-
-
-
- {TITLE_TEXT}
-
-
-
-
- );
-};
-
-export default TerminalDisplay;
diff --git a/apps/web/src/app/(home)/_components/Testimonials.tsx b/apps/web/src/app/(home)/_components/Testimonials.tsx
index 6c38bad..627fcad 100644
--- a/apps/web/src/app/(home)/_components/Testimonials.tsx
+++ b/apps/web/src/app/(home)/_components/Testimonials.tsx
@@ -30,11 +30,7 @@ const testimonials = [
const Testimonials = () => {
const scrollContainerRef = useRef(null);
- const duplicatedTestimonials = [
- ...testimonials,
- ...testimonials,
- ...testimonials,
- ];
+ const duplicatedTestimonials = [...testimonials];
return (
diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx
index 87531dd..2a9ca55 100644
--- a/apps/web/src/app/(home)/page.tsx
+++ b/apps/web/src/app/(home)/page.tsx
@@ -4,8 +4,7 @@ import React from "react";
import CodeContainer from "./_components/CodeContainer";
import CustomizableSection from "./_components/CustomizableSection";
import NpmPackage from "./_components/NpmPackage";
-import TechShowcase from "./_components/TechShowcase";
-import Testimonials from "./_components/Testimonials";
+// import Testimonials from "./_components/Testimonials";
export default function HomePage() {
return (
@@ -46,46 +45,6 @@ export default function HomePage() {
- {/*