+
+
-
Completing sign in…
+
Completing sign in…
);
diff --git a/client/src/app/auth/callback/page.tsx b/client/src/app/(auth)/auth/callback/page.tsx
similarity index 100%
rename from client/src/app/auth/callback/page.tsx
rename to client/src/app/(auth)/auth/callback/page.tsx
diff --git a/client/src/app/(auth)/layout.tsx b/client/src/app/(auth)/layout.tsx
new file mode 100644
index 0000000..eadb692
--- /dev/null
+++ b/client/src/app/(auth)/layout.tsx
@@ -0,0 +1,37 @@
+import Link from "next/link";
+
+export default function AuthLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+ {/* Header */}
+
+
+
+
+
ConfigFlow
+
+
+
+ Back to Home
+ arrow_forward
+
+
+
+ {/* Main Content Area */}
+
+ {children}
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/(auth)/login/page.tsx b/client/src/app/(auth)/login/page.tsx
new file mode 100644
index 0000000..902e89a
--- /dev/null
+++ b/client/src/app/(auth)/login/page.tsx
@@ -0,0 +1,125 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import toast from "react-hot-toast";
+import { useAuth } from "@/lib/auth-context";
+
+export default function LoginPage() {
+ const router = useRouter();
+
+ const { login } = useAuth();
+
+ // State for our form inputs
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+
+ // The function that talks to your Express backend
+ const handleLogin = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+
+ try {
+ await login(email, password);
+
+ toast.success("Welcome back!");
+ router.push("/dashboard"); // Send them to the IDE!
+
+ } catch (error: any) {
+ toast.error(error.message || "Invalid email or password.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000";
+
+ const handleGoogleLogin = () => {
+ window.location.href = `${API_BASE}/api/auth/google/start`;
+ };
+
+ const handleGithubLogin = () => {
+ window.location.href = `${API_BASE}/api/auth/github/start`;
+ };
+
+ return (
+ <>
+
+
+
Welcome back.
+
Sign in to continue to your workspace.
+
+
+ {/* OAuth Buttons */}
+
+
+
+
+
+ {/* Divider */}
+
+
+ {/* The Wired-Up Form */}
+
+
+
+ Don't have an account?
+ Sign up.
+
+
+
+
+ lock
+ Secure 256-bit encryption. We never share your data.
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/(auth)/register/page.tsx b/client/src/app/(auth)/register/page.tsx
new file mode 100644
index 0000000..7f6cb54
--- /dev/null
+++ b/client/src/app/(auth)/register/page.tsx
@@ -0,0 +1,136 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import toast from "react-hot-toast";
+import { useAuth } from "@/lib/auth-context";
+
+export default function RegisterPage() {
+ const router = useRouter();
+
+ const { register } = useAuth();
+ const [name, setName] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleRegister = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+
+ try {
+ await register(email, password, name);
+ toast.success("Check your email to verify your account!");
+ router.push("/login");
+ } catch (error: any) {
+ toast.error(error.message || "Failed to create account.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000";
+
+ const handleGoogleLogin = () => {
+ window.location.href = `${API_BASE}/api/auth/google/start`;
+ };
+
+ const handleGithubLogin = () => {
+ window.location.href = `${API_BASE}/api/auth/github/start`;
+ };
+
+ return (
+ <>
+
+
+ {/* Subtle accent top line */}
+
+
+
+
Start generating software.
+
Create your workspace to begin.
+
+
+ {/* OAuth Actions */}
+
+
+
+
+
+ {/* Divider */}
+
+
+ {/* The Wired-Up Form */}
+
+
+ {/* Login Link */}
+
+
+ Already have an account? Log in.
+
+
+
+
+ {/* Trust Indicator */}
+
+ lock
+ Secure 256-bit encryption. We never share your data.
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/(marketing)/layout.tsx b/client/src/app/(marketing)/layout.tsx
new file mode 100644
index 0000000..e8d4583
--- /dev/null
+++ b/client/src/app/(marketing)/layout.tsx
@@ -0,0 +1,18 @@
+import Navbar from "@/components/layout/Navbar";
+import Footer from "@/components/layout/Footer";
+
+export default function MarketingLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/(marketing)/mission/page.tsx b/client/src/app/(marketing)/mission/page.tsx
new file mode 100644
index 0000000..cca5528
--- /dev/null
+++ b/client/src/app/(marketing)/mission/page.tsx
@@ -0,0 +1,71 @@
+import Link from "next/link";
+
+export default function MissionPage() {
+ return (
+
+
+
+ {/* Header */}
+
+
+
+ Our Mission
+
+
+ Software should be generated, not written.
+
+
+
+ {/* Content */}
+
+
+ For the past two decades, the software industry has been trapped in a cycle of writing the same boilerplate code over and over again. Every new project requires setting up a database, configuring authentication, wiring up API routes, and building basic CRUD interfaces.
+
+
+ We believe that human engineers are too valuable to spend their time writing boilerplate.
+
+
+
+
The ConfigFlow Manifesto
+
+ -
+ 01.
+ Architecture should be defined declaratively, not imperatively.
+
+ -
+ 02.
+ Machines should write the boilerplate. Humans should write the business logic.
+
+ -
+ 03.
+ Generated code must be clean, standard, and vendor-agnostic. No lock-in.
+
+
+
+
+
+ ConfigFlow was built to bridge the gap between idea and production. By defining your architecture in a single, strictly-typed JSON configuration, our engine can orchestrate the entire full-stack application in minutes.
+
+
+ We are building the operating system for software generation. We invite you to build the future with us.
+
+
+
+ {/* CTA */}
+
+
+
account_tree
+
+
THE CONFIGFLOW TEAM
+
Rajasthan, India
+
+
+
+ Join the Platform
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/(marketing)/page.tsx b/client/src/app/(marketing)/page.tsx
new file mode 100644
index 0000000..2fd7288
--- /dev/null
+++ b/client/src/app/(marketing)/page.tsx
@@ -0,0 +1,369 @@
+"use client";
+import Link from "next/link";
+import { useEffect, useRef } from "react";
+
+export default function Home() {
+ const heroRef = useRef
(null);
+ const glowRef = useRef(null);
+
+ useEffect(() => {
+ const heroSection = heroRef.current;
+ const mouseGlow = glowRef.current;
+
+ if (heroSection && mouseGlow) {
+ const handleMouseMove = (e: MouseEvent) => {
+ const rect = heroSection.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ mouseGlow.style.left = `${x}px`;
+ mouseGlow.style.top = `${y}px`;
+ };
+
+ heroSection.addEventListener('mousemove', handleMouseMove);
+ heroSection.addEventListener('mouseleave', () => {
+ mouseGlow.style.left = '50%';
+ mouseGlow.style.top = '50%';
+ });
+
+ return () => {
+ heroSection.removeEventListener('mousemove', handleMouseMove);
+ };
+ }
+ }, []);
+
+ return (
+
+ {/* Hero Section */}
+
+
+
+
+
+ Configuration is the new compiler
+
+
+
Define it.
+ Generate it.
+ Ship it.
+
+
+ Describe your software architecture in JSON. ConfigFlow's generation engine orchestrates the architecture and generates a production-ready Next.js system in minutes.
+
+
+
+ Start Building Free
+ arrow_forward
+
+
+ play_circle
+ See How It Works
+
+
+
+
+ memory
+ Config-Driven Architecture
+
+
+ verified
+ Production Ready
+
+
+
+
+ {/* Abstract Isometric Diagram */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ chat_bubble_outline
+
+
DEFINE
+
+
+
+
+
+
+ layers
+
+
PRODUCTION
+
+
+
+
+
+ {/* Tech Stack Band */}
+
+
+
+ language
+ NEXT.JS
+
+
+ code
+ REACT
+
+
+ api
+ EXPRESS
+
+
+ database
+ POSTGRESQL
+
+
+ schema
+ PRISMA
+
+
+ palette
+ TAILWIND CSS
+
+
+
+
+ {/* Problem / Workflow Comparison */}
+
+
+
THE PROBLEM
+ Stop writing boilerplate.
+
+
+
+ {/* Traditional */}
+
+
+ warning
+ Traditional Development
+
+
+
+ Timeline
+ Weeks to Months
+
+
+ APIs
+ Manual implementation
+
+
+ Database
+ Manual schemas & migrations
+
+
+ UI Components
+ Manual wiring & state
+
+
+
+
+ {/* ConfigFlow */}
+
+
+ bolt
+
+
+ check_circle
+ ConfigFlow
+
+
+
+ Timeline
+ Minutes
+
+
+ APIs
+ Auto-generated CRUD
+
+
+ Database
+ Auto-generated schemas
+
+
+ UI Components
+ Auto-generated & wired
+
+
+
+
+
+
+
+ {/* Generate Section (Showpiece) */}
+
+
+
THE ENGINE
+
A complete, production-ready architecture.
+
Every layer. Every file. Every configuration. Generated, connected, and ready to scale.
+
+
+
+
+ {/* JSON Input Left */}
+
+
+
+ {`{
+ "`}project{`": "`}SaaS CRM{`",
+ "`}models{`": [
+ {
+ "`}name{`": "`}Customer{`",
+ "`}fields{`": {
+ "`}email{`": "`}String @unique{`",
+ "`}status{`": "`}Enum{`"
+ }
+ }
+ ],
+ "`}features{`": [
+ "`}auth{`",
+ "`}api_routes{`",
+ "`}dashboard_ui{`"
+ ]
+}`}
+
+
+
+ {/* Engine Core Middle */}
+
+
+
+ settings_b_roll
+
+
+ GENERATION ENGINE
+ RESOLVING DEPENDENCIES
+
+
+
+ {/* Outputs Right */}
+
+
+
database
+
+
DATABASE
+
schema.prisma generated
+
+
+
+
api
+
+
BACKEND API
+
Next.js Route Handlers
+
+
+
+
web
+
+
FRONTEND
+
React Server Components
+
+
+
+
+
+
+
+ {/* Features Grid */}
+
+
+
FEATURES
+ Everything you need. Out of the box.
+
+
+
+
table_chart
+
Dynamic Data Tables
+
Auto-generated tables with sorting, filtering, and pagination built-in.
+
+
+
format_align_left
+
Smart Forms
+
Client and server-side validation derived directly from your schema.
+
+
+
monitoring
+
Dashboards & Charts
+
Visual components wired to aggregate your data automatically.
+
+
+
admin_panel_settings
+
Built-in Auth
+
Secure user management, roles, and session handling ready to go.
+
+
+
offline_bolt
+
PWA Support
+
Offline caching and installability configured by default.
+
+
+
file_download
+
Standalone Export
+
Export clean, standard Next.js code. No vendor lock-in.
+
+
+
+
+ {/* Use Cases Section */}
+
+
+
USE CASES
+ Built for any domain.
+
+
+
+ Internal Tools
+
+
+ Admin Panels
+
+
+ CRMs
+
+
+ Inventory Management
+
+
+ MVPs
+
+
+
+
+ {/* Final CTA */}
+
+ Ready to build your next app in minutes?
+ Join developers who are shipping faster with ConfigFlow.
+
+ Start Building Now
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/(marketing)/support/page.tsx b/client/src/app/(marketing)/support/page.tsx
new file mode 100644
index 0000000..0bc4743
--- /dev/null
+++ b/client/src/app/(marketing)/support/page.tsx
@@ -0,0 +1,62 @@
+import Link from "next/link";
+
+export default function SupportPage() {
+ return (
+
+
+ {/* Hero Section */}
+
+ How can we help?
+
+ Need help with JSON schemas, want to report a bug, or have a feature request? Select the appropriate channel below.
+
+
+
+ {/* Bento Grid */}
+
+
+ {/* Card 1: GitHub */}
+
+
+ bug_report
+
+
Report a Bug or Contribute
+
Help us improve the core engine. Submit issues or PRs directly to our repository.
+
Open GitHub Issue
+
+
+ {/* Card 2: Roadmap */}
+
+
+ tips_and_updates
+
+
Request a Feature
+
Have an idea for a new component or configuration option? Let us know.
+
View Roadmap
+
+
+ {/* Card 3: Discord */}
+
+
+ forum
+
+
Developer Discord
+
Join the community. Get real-time help, discuss schemas, and connect with other engineers.
+
Join Discord
+
+
+ {/* Card 4: Direct Support */}
+
+
+ mail
+
+
Direct Support
+
For private inquiries, billing issues, or enterprise support SLAs.
+
Contact Us
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/app/apple-icon.png b/client/src/app/apple-icon.png
new file mode 100644
index 0000000..4ffb8c8
Binary files /dev/null and b/client/src/app/apple-icon.png differ
diff --git a/client/src/app/dashboard/page.tsx b/client/src/app/dashboard/page.tsx
deleted file mode 100644
index 2fd7216..0000000
--- a/client/src/app/dashboard/page.tsx
+++ /dev/null
@@ -1,298 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { useRouter } from "next/navigation";
-import { useAuth } from "@/lib/auth-context";
-import api from "@/lib/api";
-import { ArrowRight, Trash2, Clock, AppWindow, Plus, Bell } from "lucide-react";
-import Image from "next/image";
-
-import { useTranslation } from "@/i18n/useTranslation";
-import { toast } from "react-hot-toast";
-import LanguageSwitcher from "@/components/LanguageSwitcher";
-import NotificationSidebar from "@/components/NotificationSidebar";
-import ConfirmDialog from "@/components/ConfirmDialog";
-
-interface AppRecord {
- id: string;
- name: string;
- createdAt: string;
- updatedAt: string;
-}
-
-interface NotificationRecord {
- id: string;
- title: string;
- message: string;
- type: string;
- isRead: boolean;
- createdAt: string;
-}
-
-export default function DashboardPage() {
- const { isAuthenticated, loading, logout, user, token } = useAuth();
- const router = useRouter();
- const [apps, setApps] = useState([]);
- const [isLoadingApps, setIsLoadingApps] = useState(true);
- const [notifications, setNotifications] = useState([]);
- const [isLoadingNotifications, setIsLoadingNotifications] = useState(true);
- const [isNotificationSidebarOpen, setIsNotificationSidebarOpen] = useState(false);
- const [deleteAppId, setDeleteAppId] = useState(null);
-
- const { t } = useTranslation();
-
- const unreadCount = notifications.filter(n => !n.isRead).length;
-
- const deleteApp = async (id: string, e: React.MouseEvent) => {
- e.stopPropagation();
- setDeleteAppId(id);
- };
-
- const confirmDeleteApp = async (id: string) => {
- try {
- await api.delete(`/apps/${id}`);
- setApps(prev => prev.filter(app => app.id !== id));
- toast.success("Application deleted");
- setDeleteAppId(null);
- } catch {
- toast.error(t('common.error'));
- setDeleteAppId(null);
- }
- };
-
- useEffect(() => {
- if (!loading && !isAuthenticated) {
- router.push("/login");
- }
- }, [loading, isAuthenticated, router]);
-
- useEffect(() => {
- if (isAuthenticated) {
- const loadApps = async () => {
- try {
- const res = await api.get("/apps");
- if (res.data.success) {
- setApps(res.data.data);
- }
- } catch {
- console.error("Failed to load apps");
- } finally {
- setIsLoadingApps(false);
- }
- };
- loadApps();
- }
- }, [isAuthenticated]);
-
- useEffect(() => {
- if (isAuthenticated) {
- const loadNotifications = async () => {
- try {
- const res = await api.get("/notifications");
- if (res.data.success) {
- setNotifications(res.data.data.notifications || []);
- }
- } catch {
- console.error("Failed to load notifications");
- } finally {
- setIsLoadingNotifications(false);
- }
- };
-
- loadNotifications();
- }
- }, [isAuthenticated, user?.email]);
-
- // Real-time updates via Server-Sent Events (SSE)
- useEffect(() => {
- if (!isAuthenticated || !token) return;
-
- const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000";
- const sseUrl = `${API_BASE}/api/notifications/stream?token=${encodeURIComponent(token)}`;
-
- const es = new EventSource(sseUrl);
-
- const onNotification = (ev: MessageEvent) => {
- try {
- const payload = JSON.parse(ev.data);
- // Prepend new notification if not already present
- setNotifications((cur) => {
- if (cur.some((n) => n.id === payload.id)) return cur;
- return [payload, ...cur];
- });
- } catch (e) {
- console.error("Failed to parse SSE notification", e);
- }
- };
-
- es.addEventListener("notification", onNotification as EventListener);
-
- es.addEventListener("error", (err) => {
- // reconnect handled by EventSource in the browser; log for debugging
- console.warn("SSE error", err);
- });
-
- return () => {
- es.close();
- };
- }, [isAuthenticated, token]);
-
- const markNotificationRead = async (notificationId: string) => {
- try {
- await api.patch(`/notifications/${notificationId}/read`);
- setNotifications((current) => current.map((item) => (item.id === notificationId ? { ...item, isRead: true } : item)));
- } catch {
- toast.error(t('common.error'));
- }
- };
-
- const markAllAsRead = async () => {
- try {
- const res = await api.post(`/notifications/mark-all-read`);
- if (res.data.success) {
- setNotifications((cur) => cur.map((n) => ({ ...n, isRead: true })));
- toast.success("All notifications marked as read");
- }
- } catch (e) {
- console.error("Failed to mark all read", e);
- toast.error(t('common.error'));
- }
- };
-
- if (loading || !isAuthenticated) {
- return ;
- }
-
- return (
-
- {/* Top Nav */}
-
-
-
-
-
-
{t('dashboard.title')}
-
{t('dashboard.welcome')}
-
-
-
-
-
- {isLoadingApps ? (
-
- {[1, 2, 3].map(i => (
-
- ))}
-
- ) : apps.length === 0 ? (
-
-
-
{t('common.noData')}
-
{t('dashboard.recentTasks')}
-
-
- ) : (
-
- {apps.map(app => (
-
router.push(`/builder/${app.id}`)}
- className="group relative bg-[#111111] border border-white/10 rounded-2xl p-6 hover:border-indigo-500/50 hover:bg-[#161616] transition-all cursor-pointer overflow-hidden"
- >
-
-
-
-
-
-
-
-
{app.name}
-
-
-
- {t('common.created', 'Created')} {new Date(app.createdAt).toLocaleDateString()}
-
-
-
-
- ))}
-
- )}
-
-
-
- {/* Notification Sidebar */}
-
setIsNotificationSidebarOpen(false)}
- onMarkRead={markNotificationRead}
- onMarkAllRead={markAllAsRead}
- isLoading={isLoadingNotifications}
- />
-
- {/* Delete App Confirmation Dialog */}
- deleteAppId && confirmDeleteApp(deleteAppId)}
- onCancel={() => setDeleteAppId(null)}
- />
-
- );
-}
diff --git a/client/src/app/favicon.ico b/client/src/app/favicon.ico
index 718d6fe..f9c81ca 100644
Binary files a/client/src/app/favicon.ico and b/client/src/app/favicon.ico differ
diff --git a/client/src/app/globals.css b/client/src/app/globals.css
index 5462503..4d26b1f 100644
--- a/client/src/app/globals.css
+++ b/client/src/app/globals.css
@@ -2,6 +2,10 @@
@tailwind components;
@tailwind utilities;
+html {
+ scroll-behavior: smooth;
+}
+
:root {
--font-inter: "Inter", sans-serif;
}
@@ -46,3 +50,143 @@ input[type="date"],
input[type="datetime-local"] {
color-scheme: dark;
}
+
+/* Custom ConfigFlow Styles & Animations */
+.bg-grid {
+ background-size: 32px 32px;
+ background-image: linear-gradient(to right, rgba(255, 255, 255, 0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
+}
+.glass-panel {
+ background: rgba(17, 17, 17, 0.6);
+ backdrop-filter: blur(12px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+.hide-scroll::-webkit-scrollbar { display: none; }
+.hide-scroll { -ms-overflow-style: none; scrollbar-width: none; }
+
+@keyframes slideUpFade {
+ from { opacity: 0; transform: translateY(30px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+@keyframes bloom {
+ 0% { text-shadow: 0 0 0 rgba(255, 107, 0, 0); }
+ 50% { text-shadow: 0 0 30px rgba(255, 107, 0, 0.8), 0 0 60px rgba(255, 107, 0, 0.5); }
+ 100% { text-shadow: 0 0 15px rgba(255, 107, 0, 0.5); }
+}
+@keyframes float {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-15px); }
+}
+@keyframes enginePulse {
+ 0%, 100% { transform: scale(1); box-shadow: 0 0 30px rgba(255,107,0,0.15); }
+ 50% { transform: scale(1.08); box-shadow: 0 0 60px rgba(255,107,0,0.4); }
+}
+@keyframes circuitPulse1 {
+ 0% { left: -20px; opacity: 0; }
+ 5% { opacity: 1; }
+ 40% { left: 100%; opacity: 1; }
+ 45%, 100% { left: 100%; opacity: 0; }
+}
+@keyframes circuitPulse2 {
+ 0%, 50% { left: -20px; opacity: 0; }
+ 55% { opacity: 1; }
+ 90% { left: 100%; opacity: 1; }
+ 95%, 100% { left: 100%; opacity: 0; }
+}
+@keyframes circuitEngine {
+ 0%, 35%, 65%, 100% { box-shadow: 0 0 20px rgba(255,107,0,0.1); border-color: rgba(255,107,0,0.3); transform: scale(1); }
+ 45%, 55% { box-shadow: 0 0 50px rgba(255,107,0,0.7); border-color: rgba(255,107,0,1); transform: scale(1.05); }
+}
+
+.reveal-text-1 { animation: slideUpFade 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; opacity: 0; }
+.reveal-text-2 { animation: slideUpFade 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.2s forwards; opacity: 0; }
+.reveal-text-3 { animation: slideUpFade 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.4s forwards, bloom 2s ease-out 0.8s forwards; opacity: 0; }
+.float-diagram { animation: float 6s ease-in-out infinite; }
+.hover-lift { transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.3s ease, border-color 0.3s ease; }
+.hover-lift:hover { transform: translateY(-8px) scale(1.05); box-shadow: 0 15px 30px -5px rgba(255,107,0,0.4); border-color: rgba(255,107,0,0.6); z-index: 20; }
+.shine-effect { position: relative; overflow: hidden; }
+.shine-effect::after {
+ content: ''; position: absolute; top: 0; left: -100%; width: 50%; height: 100%;
+ background: linear-gradient(to right, transparent, rgba(255,255,255,0.4), transparent);
+ transform: skewX(-20deg); transition: all 0.6s;
+}
+.shine-effect:hover::after { left: 150%; }
+#mouse-glow {
+ position: absolute; width: 800px; height: 800px;
+ background: radial-gradient(circle, rgba(255,107,0,0.06) 0%, transparent 60%);
+ border-radius: 50%; pointer-events: none; transform: translate(-50%, -50%);
+ z-index: 0; transition: left 0.2s cubic-bezier(0.2, 0, 0, 1), top 0.2s cubic-bezier(0.2, 0, 0, 1);
+}
+
+.glass-card {
+ background-color: rgba(17, 17, 17, 0.6);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+}
+.glow-button {
+ box-shadow: 0 0 20px rgba(255, 107, 0, 0.4);
+ transition: all 0.2s ease-in-out;
+}
+.glow-button:hover {
+ box-shadow: 0 0 30px rgba(255, 107, 0, 0.6);
+ transform: translateY(-1px);
+}
+.input-field {
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+.input-field:focus {
+ box-shadow: 0 1px 0 0 #ff6b00;
+}
+
+.input-glow:focus {
+ box-shadow: 0 0 0 2px rgba(255, 107, 0, 0.5);
+ border-color: #ff6b00;
+}
+
+.bento-card {
+ background-color: #0F1115;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+}
+.bento-card::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at top right, rgba(255, 107, 0, 0.1), transparent 50%);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+.bento-card:hover {
+ border-color: rgba(255, 107, 0, 0.5);
+ box-shadow: 0 0 20px rgba(255, 107, 0, 0.1);
+}
+.bento-card:hover::before {
+ opacity: 1;
+}
+.glow-icon {
+ filter: drop-shadow(0 0 8px rgba(255, 107, 0, 0.5));
+}
+
+.glow-orange {
+ box-shadow: 0 0 12px rgba(255, 107, 0, 0.4);
+}
+.glow-orange-text {
+ text-shadow: 0 0 8px rgba(255, 107, 0, 0.6);
+}
+/* Custom scrollbar for editors */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+::-webkit-scrollbar-thumb {
+ background: rgba(255,255,255,0.1);
+ border-radius: 4px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(255,255,255,0.2);
+}
\ No newline at end of file
diff --git a/client/src/app/icon.png b/client/src/app/icon.png
new file mode 100644
index 0000000..a439045
Binary files /dev/null and b/client/src/app/icon.png differ
diff --git a/client/src/app/icon.svg b/client/src/app/icon.svg
new file mode 100644
index 0000000..d610dbd
--- /dev/null
+++ b/client/src/app/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx
index 732cffe..8208d85 100644
--- a/client/src/app/layout.tsx
+++ b/client/src/app/layout.tsx
@@ -60,6 +60,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
+
diff --git a/client/src/app/login/page.tsx b/client/src/app/login/page.tsx
deleted file mode 100644
index 5e21aca..0000000
--- a/client/src/app/login/page.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-"use client";
-
-import React, { useEffect, useState } from "react";
-import Link from "next/link";
-import { useRouter } from "next/navigation";
-import { useAuth } from "@/lib/auth-context";
-import { Blocks, Loader2, AlertCircle, Mail } from "lucide-react";
-
-import { useTranslation } from "@/i18n/useTranslation";
-import LanguageSwitcher from "@/components/LanguageSwitcher";
-
-function GoogleLogo({ className = "w-4 h-4" }: { className?: string }) {
- return (
-
- );
-}
-
-function GitHubLogo({ className = "w-4 h-4" }: { className?: string }) {
- return (
-
- );
-}
-
-export default function LoginPage() {
- const { login } = useAuth();
- const router = useRouter();
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(false);
- const [oauthLoading, setOauthLoading] = useState<"google" | "github" | null>(null);
- const [unverifiedEmail, setUnverifiedEmail] = useState(null);
- const [resendLoading, setResendLoading] = useState(false);
- const [resendSuccess, setResendSuccess] = useState(false);
-
- const { t } = useTranslation();
- const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000";
-
- const startOAuth = async (provider: "google" | "github") => {
- setError(null);
- setOauthLoading(provider);
-
- try {
- const res = await fetch(`${API_BASE}/api/auth/${provider}/start?mode=json`);
- const data = await res.json();
-
- if (!res.ok || !data?.success || !data?.data?.url) {
- throw new Error(data?.error || `${provider} sign in is not configured`);
- }
-
- window.location.href = data.data.url as string;
- } catch (err: any) {
- setError(err?.message || `${provider} sign in failed`);
- setOauthLoading(null);
- }
- };
-
- useEffect(() => {
- const resetOAuthLoading = () => setOauthLoading(null);
- const onVisibilityChange = () => {
- if (document.visibilityState === "visible") {
- resetOAuthLoading();
- }
- };
-
- // If user returns from provider page (back/cancel), unlock OAuth buttons.
- window.addEventListener("focus", resetOAuthLoading);
- window.addEventListener("pageshow", resetOAuthLoading);
- document.addEventListener("visibilitychange", onVisibilityChange);
-
- return () => {
- window.removeEventListener("focus", resetOAuthLoading);
- window.removeEventListener("pageshow", resetOAuthLoading);
- document.removeEventListener("visibilitychange", onVisibilityChange);
- };
- }, []);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setError(null);
- setUnverifiedEmail(null);
- setResendSuccess(false);
- setLoading(true);
-
- try {
- await login(email, password);
- router.push("/dashboard");
- } catch (err: any) {
- const serverError: string = err.response?.data?.error || err.message || "Login failed";
- setError(serverError);
- // Detect unverified-account error (server returns 403)
- if (
- err.response?.status === 403 ||
- /verify/i.test(serverError)
- ) {
- setUnverifiedEmail(email);
- }
- } finally {
- setLoading(false);
- }
- };
-
- const resendVerification = async () => {
- if (!unverifiedEmail) return;
- setResendLoading(true);
- setResendSuccess(false);
- setError(null);
- try {
- const res = await fetch(`${API_BASE}/api/auth/resend-verification`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: unverifiedEmail }),
- });
- const data = await res.json();
- if (!res.ok || !data?.success) {
- throw new Error(data?.error || "Unable to resend verification email");
- }
- setResendSuccess(true);
- } catch (err: any) {
- setError(err?.message || "Unable to resend verification email");
- } finally {
- setResendLoading(false);
- }
- };
-
- return (
-
-
- {/* Logo */}
-
-
-
-
-
{t('auth.login')}
-
{t('auth.signInDescription')}
-
-
-
-
-
-
-
-
-
-
{t("auth.orContinueWith", "Or continue with")}
-
-
-
-
-
-
-
-
-
-
- {t('auth.noAccount')}{" "}
-
- {t('auth.signup')}
-
-
-
-
- );
-}
diff --git a/client/src/app/manifest.ts b/client/src/app/manifest.ts
index 20982ac..15e5ae0 100644
--- a/client/src/app/manifest.ts
+++ b/client/src/app/manifest.ts
@@ -10,31 +10,31 @@ export default function manifest(): MetadataRoute.Manifest {
display: "standalone",
orientation: "portrait-primary",
background_color: "#0a0a0a",
- theme_color: "#6366f1",
+ theme_color: "#ff6b00",
categories: ["productivity", "utilities"],
icons: [
{
- src: "/icon-192.svg",
+ src: "/icon-192.png",
sizes: "192x192",
- type: "image/svg+xml",
+ type: "image/png",
purpose: "any",
},
{
- src: "/icon-512.svg",
+ src: "/icon-512.png",
sizes: "512x512",
- type: "image/svg+xml",
+ type: "image/png",
purpose: "any",
},
{
- src: "/icon-maskable-192.svg",
+ src: "/icon-maskable-192.png",
sizes: "192x192",
- type: "image/svg+xml",
+ type: "image/png",
purpose: "maskable",
},
{
- src: "/icon-maskable-512.svg",
+ src: "/icon-maskable-512.png",
sizes: "512x512",
- type: "image/svg+xml",
+ type: "image/png",
purpose: "maskable",
},
],
diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx
deleted file mode 100644
index 6e1c488..0000000
--- a/client/src/app/page.tsx
+++ /dev/null
@@ -1,274 +0,0 @@
-"use client";
-
-import { useState, useEffect, useCallback } from "react";
-import { useRouter } from "next/navigation";
-import Image from "next/image";
-import { useAuth } from "@/lib/auth-context";
-import { ArrowRight, Code2, Database, LayoutTemplate, Zap } from "lucide-react";
-import api from "@/lib/api";
-
-import { useTranslation } from "@/i18n/useTranslation";
-import LanguageSwitcher from "@/components/LanguageSwitcher";
-import PwaRegister from "@/components/PwaRegister";
-import { toast } from "react-hot-toast";
-const SAMPLE_CONFIG = {
- "app": {
- "name": "Task Manager",
- "description": "Personal task tracking app",
- "theme": { "primaryColor": "#6366f1", "mode": "light" },
- "auth": { "enabled": true }
- },
- "entities": {
- "task": {
- "userScoped": true,
- "displayField": "title",
- "fields": {
- "title": { "type": "string", "required": true, "label": "Task Title" },
- "description": { "type": "text", "label": "Description" },
- "status": { "type": "enum", "options": ["todo", "in_progress", "done"], "default": "todo", "label": "Status" },
- "priority": { "type": "number", "min": 1, "max": 5, "default": 3, "label": "Priority" },
- "dueDate": { "type": "date", "label": "Due Date" },
- "isUrgent": { "type": "boolean", "default": false, "label": "Urgent?" }
- }
- }
- },
- "pages": [
- {
- "type": "table", "name": "All Tasks", "path": "/tasks",
- "entity": "task",
- "columns": ["title", "status", "priority", "dueDate", "isUrgent"],
- "actions": ["create", "edit", "delete"],
- "filters": ["status", "priority"],
- "searchable": true,
- "pageSize": 10
- },
- {
- "type": "form", "name": "New Task", "path": "/tasks/new",
- "entity": "task",
- "fields": ["title", "description", "status", "priority", "dueDate", "isUrgent"]
- },
- {
- "type": "dashboard", "name": "Dashboard", "path": "/dashboard",
- "widgets": [
- { "type": "stat", "label": "Total Tasks", "entity": "task", "operation": "count" },
- { "type": "stat", "label": "Avg Priority", "entity": "task", "operation": "avg", "field": "priority" },
- { "type": "chart", "label": "Tasks by Status", "entity": "task", "groupBy": "status", "chartType": "pie" },
- { "type": "list", "label": "Recent Tasks", "entity": "task" }
- ]
- }
- ]
-};
-
-export default function HomePage() {
- const { isAuthenticated, loading } = useAuth();
- const router = useRouter();
- const [jsonInput, setJsonInput] = useState(JSON.stringify(SAMPLE_CONFIG, null, 2));
- const [isGenerating, setIsGenerating] = useState(false);
- const [error, setError] = useState("");
-
- const { t, direction } = useTranslation();
- const handleGenerate = useCallback(async (configString: string) => {
- try {
- setIsGenerating(true);
- setError("");
- const configJson = JSON.parse(configString);
-
- const response = await api.post("/apps", configJson);
- if (response.data.success) {
- toast.success("Application created");
- router.push(`/builder/${response.data.data.id}`);
- }
- } catch (err: unknown) {
- let message = "Invalid JSON or server error";
- if (typeof err === "object" && err !== null) {
- const e = err as { response?: { data?: { error?: unknown } }; message?: unknown };
- if (typeof e.response?.data?.error === "string") {
- message = e.response!.data!.error as string;
- } else if (typeof e.message === "string") {
- message = e.message;
- }
- } else if (typeof err === "string") {
- message = err;
- }
- setError(message);
- } finally {
- setIsGenerating(false);
- }
- }, [router]);
-
- useEffect(() => {
- // If returning from login with a pending config, generate it
- const pendingConfig = localStorage.getItem("pending_app_config");
- if (isAuthenticated && pendingConfig) {
- localStorage.removeItem("pending_app_config");
- // defer to avoid calling setState synchronously during rendering
- setTimeout(() => handleGenerate(pendingConfig), 0);
- }
- }, [isAuthenticated, handleGenerate]);
-
- useEffect(() => {
- if (typeof window !== "undefined") {
- const searchParams = new URLSearchParams(window.location.search);
- const sharedText = searchParams.get("text") || searchParams.get("title");
- const sharedUrl = searchParams.get("url");
-
- let contentToParse = sharedText || "";
- if (sharedUrl && !contentToParse.includes(sharedUrl)) {
- contentToParse = contentToParse ? `${contentToParse}\n${sharedUrl}` : sharedUrl;
- }
-
- if (contentToParse) {
- try {
- const trimmed = contentToParse.trim();
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
- const parsed = JSON.parse(trimmed);
- setJsonInput(JSON.stringify(parsed, null, 2));
- toast.success("Imported shared configuration!");
-
- // Clean up query string from URL
- const cleanUrl = window.location.pathname;
- window.history.replaceState({}, document.title, cleanUrl);
- }
- } catch (e) {
- // not valid JSON
- }
- }
- }
- }, []);
-
- const handleGenerateClick = () => {
- if (!isAuthenticated) {
- localStorage.setItem("pending_app_config", jsonInput);
- router.push("/login?redirect=generate");
- return;
- }
- handleGenerate(jsonInput);
- };
-
- return (
-
-
- {/* Navigation */}
-
-
- {/* Hero Section */}
-
-
-
- {/* Left: Copy */}
-
-
-
- {t('header.tagline')}
-
-
- {t('home.title')}
-
- {t('home.subtitle')}
-
-
-
- {t('header.description')}
-
-
-
- } title={t('home.featureDatabase')} desc={t('home.featureDatabaseDesc')} />
- } title={t('home.featureNext')} desc={t('home.featureNextDesc')} />
- } title={t('home.featureExport')} desc={t('home.featureExportDesc')} />
-
-
-
- {/* Right: The Prompt Box (JSON Editor) */}
-
-
-
-
- {/* Editor Header */}
-
-
- {/* Editor Body */}
-
-
-
- {/* Editor Footer / Action */}
-
- {error &&
{error}
}
-
-
-
-
-
-
-
-
-
- );
-}
-
-function Feature({ rtl, icon, title, desc }: { rtl?: boolean; icon: React.ReactNode; title: string; desc: string }) {
- return (
-
- );
-}
diff --git a/client/src/app/register/page.tsx b/client/src/app/register/page.tsx
deleted file mode 100644
index cfad52c..0000000
--- a/client/src/app/register/page.tsx
+++ /dev/null
@@ -1,377 +0,0 @@
-"use client";
-
-import React, { useEffect, useState } from "react";
-import Link from "next/link";
-import { useRouter } from "next/navigation";
-import { useAuth } from "@/lib/auth-context";
-import { Blocks, Loader2, AlertCircle, CheckCircle2, Mail, ArrowLeft } from "lucide-react";
-
-import { useTranslation } from "@/i18n/useTranslation";
-import LanguageSwitcher from "@/components/LanguageSwitcher";
-
-function GoogleLogo({ className = "w-4 h-4" }: { className?: string }) {
- return (
-
- );
-}
-
-function GitHubLogo({ className = "w-4 h-4" }: { className?: string }) {
- return (
-
- );
-}
-
-function getErrorMessage(error: unknown, fallback: string) {
- // Prefer structured server response (axios-like)
- if (typeof error === "object" && error !== null) {
- const maybe = error as {
- response?: { status?: number; data?: { error?: string } };
- message?: string;
- };
-
- const serverError = maybe.response?.data?.error;
- const status = maybe.response?.status;
-
- if (status === 409) {
- // Conflict — common case: email already exists / needs verification
- if (serverError && /verify/i.test(serverError)) {
- return "An account with that email already exists. Check your inbox for the verification link or click 'Resend verification' to request a new one.";
- }
-
- if (serverError && /already registered/i.test(serverError)) {
- return "This email is already registered. Try signing in or request a verification link if you haven't verified your address.";
- }
-
- return serverError || "This email is already in use. Try signing in or request a new verification link.";
- }
-
- if (serverError) return serverError;
- if (maybe.message) return maybe.message;
- }
-
- if (error instanceof Error) {
- return error.message || fallback;
- }
-
- return fallback;
-}
-
-export default function RegisterPage() {
- const { register } = useAuth();
- const router = useRouter();
- const [name, setName] = useState("");
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [confirmPassword, setConfirmPassword] = useState("");
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(false);
- const [verificationSent, setVerificationSent] = useState(false);
- const [verificationDeliveryProblem, setVerificationDeliveryProblem] = useState(false);
- const [resendLoading, setResendLoading] = useState(false);
- const [oauthLoading, setOauthLoading] = useState<"google" | "github" | null>(null);
-
- const { t } = useTranslation();
- const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000";
-
- const startOAuth = async (provider: "google" | "github") => {
- setError(null);
- setOauthLoading(provider);
-
- try {
- const res = await fetch(`${API_BASE}/api/auth/${provider}/start?mode=json`);
- const data = await res.json();
-
- if (!res.ok || !data?.success || !data?.data?.url) {
- throw new Error(data?.error || `${provider} sign in is not configured`);
- }
-
- window.location.href = data.data.url as string;
- } catch (error: unknown) {
- setError(getErrorMessage(error, `${provider} sign in failed`));
- setOauthLoading(null);
- }
- };
-
- useEffect(() => {
- const resetOAuthLoading = () => setOauthLoading(null);
- const onVisibilityChange = () => {
- if (document.visibilityState === "visible") {
- resetOAuthLoading();
- }
- };
-
- // If user returns from provider page (back/cancel), unlock OAuth buttons.
- window.addEventListener("focus", resetOAuthLoading);
- window.addEventListener("pageshow", resetOAuthLoading);
- document.addEventListener("visibilitychange", onVisibilityChange);
-
- return () => {
- window.removeEventListener("focus", resetOAuthLoading);
- window.removeEventListener("pageshow", resetOAuthLoading);
- document.removeEventListener("visibilitychange", onVisibilityChange);
- };
- }, []);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setError(null);
- setLoading(true);
- setVerificationSent(false);
- setVerificationDeliveryProblem(false);
-
- if (password !== confirmPassword) {
- setError(t('auth.passwordMismatch'));
- setLoading(false);
- return;
- }
-
- try {
- const result = await register(email, password, name || undefined);
- setVerificationSent(true);
- setVerificationDeliveryProblem(result.verificationEmailSent === false);
- setPassword("");
- setConfirmPassword("");
- } catch (error: unknown) {
- setError(getErrorMessage(error, "Registration failed"));
- } finally {
- setLoading(false);
- }
- };
-
- const resendVerification = async () => {
- setError(null);
- setResendLoading(true);
-
- try {
- const res = await fetch(`${API_BASE}/api/auth/resend-verification`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email }),
- });
- const data = await res.json();
-
- if (!res.ok || !data?.success) {
- throw new Error(data?.error || "Unable to resend verification email");
- }
-
- // Show the verification panel so the user has clear next steps
- setVerificationSent(true);
- // If the server indicates delivery failed, surface that state
- setVerificationDeliveryProblem(data?.data?.verificationEmailSent === false);
- } catch (error: unknown) {
- setError(getErrorMessage(error, "Unable to resend verification email"));
- } finally {
- setResendLoading(false);
- }
- };
-
- return (
-
-
-
-
-
-
-
{t('auth.signup')}
-
{t('header.tagline')}
-
-
-
-
-
- {verificationSent ? (
-
-
-
-
-
-
-
-
Check your inbox
-
-
- We sent a verification link to
-
-
- {email}
-
- Open it to finish creating your account and sign in automatically.
-
-
-
- Didn’t receive it? Check spam or resend a fresh verification link below.
-
-
- {verificationDeliveryProblem && (
-
- The email could not be delivered automatically. Use resend to try again.
-
- )}
-
-
-
-
-
-
-
-
-
- ) : (
-
- )}
-
-
-
-
{t("auth.orContinueWith", "Or continue with")}
-
-
-
-
-
-
-
-
-
-
- {t('auth.haveAccount')}{" "}
-
- {t('auth.login')}
-
-
-
-
- );
-}
diff --git a/client/src/components/layout/Footer.tsx b/client/src/components/layout/Footer.tsx
new file mode 100644
index 0000000..e5416fe
--- /dev/null
+++ b/client/src/components/layout/Footer.tsx
@@ -0,0 +1,107 @@
+import Link from "next/link";
+
+export default function Footer() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/components/layout/Navbar.tsx b/client/src/components/layout/Navbar.tsx
new file mode 100644
index 0000000..f7ece39
--- /dev/null
+++ b/client/src/components/layout/Navbar.tsx
@@ -0,0 +1,73 @@
+"use client";
+import Link from "next/link";
+import { useState, useEffect } from "react";
+
+export default function Navbar() {
+ const [scrolled, setScrolled] = useState(false);
+
+ // Listen for scroll events to trigger the glass effect
+ useEffect(() => {
+ const handleScroll = () => {
+ setScrolled(window.scrollY > 20);
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/client/tailwind.config.js b/client/tailwind.config.js
index bcc5a41..b9bf331 100644
--- a/client/tailwind.config.js
+++ b/client/tailwind.config.js
@@ -6,10 +6,86 @@ module.exports = {
darkMode: "class",
theme: {
extend: {
+ colors: {
+ // Semantic Surface Colors
+ 'surface': '#131313',
+ 'surface-dim': '#131313',
+ 'surface-bright': '#3a3939',
+ 'surface-container-lowest': '#0e0e0e',
+ 'surface-container-low': '#1c1b1b',
+ 'surface-container': '#201f1f',
+ 'surface-container-high': '#2a2a2a',
+ 'surface-container-highest': '#353534',
+
+ // Semantic Text/Icon Colors
+ 'on-surface': '#e5e2e1',
+ 'on-surface-variant': '#e2bfb0',
+ 'inverse-surface': '#e5e2e1',
+ 'inverse-on-surface': '#313030',
+
+ // Borders and Outlines
+ 'outline': '#a98a7d',
+ 'outline-variant': '#5a4136',
+ 'outline-hairline': 'rgba(255, 255, 255, 0.1)',
+
+ // Primary (Warm Orange)
+ 'primary': '#ffb693',
+ 'on-primary': '#561f00',
+ 'primary-container': '#ff6b00',
+ 'on-primary-container': '#572000',
+ 'primary-fixed': '#ffdbcc',
+ 'primary-fixed-dim': '#ffb693',
+
+ // Secondary & Tertiary
+ 'secondary': '#c0c1ff',
+ 'secondary-container': '#3131c0',
+ 'tertiary': '#4edea3',
+ 'tertiary-container': '#00ae78',
+
+ // Backgrounds & Utilities
+ 'background': '#131313',
+ 'terminal-bg': '#09090B',
+ 'surface-elevated': '#111111',
+ 'code-indigo': '#C7D2FE',
+ 'error-red': '#EF4444',
+ 'warning-amber': '#F59E0B',
+ },
fontFamily: {
- sans: ["var(--font-inter)", "system-ui", "sans-serif"],
+ 'display-hero': ['Inter', 'sans-serif'],
+ 'headline-lg': ['Inter', 'sans-serif'],
+ 'headline-md': ['Inter', 'sans-serif'],
+ 'body-lg': ['Inter', 'sans-serif'],
+ 'body-md': ['Inter', 'sans-serif'],
+ 'code-base': ['Geist Mono', 'monospace'],
+ 'label-caps': ['Geist Mono', 'monospace'],
+ 'label-tech': ['Geist Mono', 'monospace'],
+ },
+ fontSize: {
+ 'display-hero': ['72px', { lineHeight: '1.1', letterSpacing: '-0.04em', fontWeight: '900' }],
+ 'headline-lg': ['32px', { lineHeight: '1.2', letterSpacing: '-0.02em', fontWeight: '700' }],
+ 'headline-md': ['24px', { lineHeight: '1.3', fontWeight: '700' }],
+ 'body-lg': ['18px', { lineHeight: '1.6', fontWeight: '400' }],
+ 'body-md': ['15px', { lineHeight: '1.5', fontWeight: '400' }],
+ 'code-base': ['14px', { lineHeight: '1.6', fontWeight: '400' }],
+ 'label-caps': ['11px', { lineHeight: '1.0', letterSpacing: '0.15em', fontWeight: '700' }],
+ 'label-tech': ['12px', { lineHeight: '1.2', fontWeight: '500' }],
+ },
+ spacing: {
+ 'base': '4px',
+ 'gutter': '16px',
+ 'margin-safe': '32px',
+ 'panel-gap': '1px',
+ 'container-max': '1440px',
},
+ borderRadius: {
+ 'sm': '0.125rem',
+ DEFAULT: '0.25rem',
+ 'md': '0.375rem',
+ 'lg': '0.5rem',
+ 'xl': '0.75rem',
+ 'full': '9999px',
+ }
},
},
plugins: [],
-};
+};
\ No newline at end of file