From 6fda511c3898dfae91990f1c72940bc2811eb613 Mon Sep 17 00:00:00 2001 From: Solo Shun <88045720+soloshun@users.noreply.github.com> Date: Tue, 27 Jan 2026 06:55:49 +0000 Subject: [PATCH] feat(app): add coming soon and not found page --- apps/app/package.json | 6 +- apps/app/src/app/globals.css | 49 ++- apps/app/src/app/layout.tsx | 37 +- apps/app/src/app/not-found.tsx | 315 ++++++++++++++++ apps/app/src/app/page.tsx | 658 ++++++++++++++++++++++++++++++--- pnpm-lock.yaml | 12 + 6 files changed, 996 insertions(+), 81 deletions(-) create mode 100644 apps/app/src/app/not-found.tsx diff --git a/apps/app/package.json b/apps/app/package.json index 4027230..ddd53f9 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -9,9 +9,13 @@ "lint": "eslint" }, "dependencies": { + "clsx": "^2.1.1", + "framer-motion": "^12.29.0", + "lucide-react": "^0.563.0", "next": "16.1.4", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/apps/app/src/app/globals.css b/apps/app/src/app/globals.css index a2dc41e..a6c56c1 100644 --- a/apps/app/src/app/globals.css +++ b/apps/app/src/app/globals.css @@ -1,26 +1,47 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: 0 0% 3%; + --foreground: 0 0% 98%; + --card: 0 0% 6%; + --card-foreground: 0 0% 98%; + --primary: 142 71% 45%; + --primary-foreground: 0 0% 2%; + --secondary: 0 0% 10%; + --muted: 0 0% 15%; + --muted-foreground: 0 0% 60%; + --border: 0 0% 15%; + --ring: 142 71% 45%; } @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --color-background: hsl(var(--background)); + --color-foreground: hsl(var(--foreground)); + --color-primary: hsl(var(--primary)); + --color-primary-foreground: hsl(var(--primary-foreground)); + --font-sans: var(--font-syne), system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: var(--font-jetbrains), ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } +* { + box-sizing: border-box; + padding: 0; + margin: 0; } +html, body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + min-height: 100vh; + overflow-x: hidden; +} + +/* Font classes */ +.font-sans { + font-family: var(--font-syne), system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.font-mono { + font-family: var(--font-jetbrains), ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; } diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index f7fa87e..de7057e 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -1,20 +1,37 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { JetBrains_Mono, Syne } from "next/font/google"; import "./globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const syne = Syne({ + variable: "--font-syne", subsets: ["latin"], + weight: ["400", "500", "600", "700", "800"], + display: "swap", }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const jetbrainsMono = JetBrains_Mono({ + variable: "--font-jetbrains", subsets: ["latin"], + display: "swap", }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "OpenDSA App - Coming Soon", + description: "The open-source algorithm visualization platform. Interactive visualizations for sorting, searching, graphs, trees, and more.", + keywords: ["algorithms", "data structures", "visualization", "learning", "education", "open source"], + authors: [{ name: "Solomon Eshun", url: "https://github.com/soloshun" }], + openGraph: { + title: "OpenDSA - Coming Soon", + description: "The open-source algorithm visualization platform is under development.", + url: "https://app.opendsa.dev", + siteName: "OpenDSA", + type: "website", + }, + twitter: { + card: "summary_large_image", + title: "OpenDSA - Coming Soon", + description: "The open-source algorithm visualization platform is under development.", + }, }; export default function RootLayout({ @@ -23,10 +40,8 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - + + {children} diff --git a/apps/app/src/app/not-found.tsx b/apps/app/src/app/not-found.tsx new file mode 100644 index 0000000..71172e4 --- /dev/null +++ b/apps/app/src/app/not-found.tsx @@ -0,0 +1,315 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; + +// Glitch text effect +function GlitchText({ text }: { text: string }) { + const [glitchedText, setGlitchedText] = useState(text); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setMounted(true); + }, []); + + useEffect(() => { + if (!mounted) return; + + const glitchChars = "!@#$%^&*()_+-=[]{}|;:,.<>?"; + let interval: NodeJS.Timeout; + + const startGlitch = () => { + let iterations = 0; + interval = setInterval(() => { + setGlitchedText( + text + .split("") + .map((char, index) => { + if (index < iterations) return text[index]; + return glitchChars[Math.floor(Math.random() * glitchChars.length)]; + }) + .join("") + ); + iterations += 1 / 3; + if (iterations >= text.length) { + clearInterval(interval); + setGlitchedText(text); + } + }, 30); + }; + + startGlitch(); + const loopInterval = setInterval(startGlitch, 5000); + + return () => { + clearInterval(interval); + clearInterval(loopInterval); + }; + }, [text, mounted]); + + if (!mounted) return {text}; + + return {glitchedText}; +} + +// ASCII art 404 +const ascii404 = ` + ██╗ ██╗ ██████╗ ██╗ ██╗ + ██║ ██║██╔═████╗██║ ██║ + ███████║██║██╔██║███████║ + ╚════██║████╔╝██║╚════██║ + ██║╚██████╔╝ ██║ + ╚═╝ ╚═════╝ ╚═╝ +`; + +export default function NotFound() { + const [mounted, setMounted] = useState(false); + const [currentPath, setCurrentPath] = useState("/unknown"); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setMounted(true); + setCurrentPath(window.location.pathname); + }, []); + + if (!mounted) { + return ( +
+ ); + } + + return ( +
+ {/* Grid pattern */} +
+ + {/* Scanlines */} +
+ + {/* Gradient orb */} +
+ + {/* Content */} +
+ {/* ASCII Art */} +
+          {ascii404}
+        
+ + {/* Terminal window */} +
+ {/* Terminal header */} +
+
+
+
+
+
+ + error.log + +
+ + {/* Terminal content */} +
+
+ [ERROR] Page not found +
+
+ path: "{currentPath}" +
+
+ status: 404 +
+
+ message: "The requested resource could not be found" +
+
+ $ + suggesting: + go_home + +
+
+
+ + {/* Error message */} +
+

+ +

+

+ Looks like you've ventured into uncharted territory. The page you're looking for doesn't exist or has been moved. +

+
+ + {/* Action buttons */} +
+ + + + + Go Home + + + +
+ + {/* Helpful links */} +
+ + → website + + + → docs + + + → github + +
+
+ + +
+ ); +} diff --git a/apps/app/src/app/page.tsx b/apps/app/src/app/page.tsx index 295f8fd..676f7a0 100644 --- a/apps/app/src/app/page.tsx +++ b/apps/app/src/app/page.tsx @@ -1,65 +1,613 @@ -import Image from "next/image"; +"use client"; + +import { useState, useEffect, useMemo } from "react"; +import Link from "next/link"; + +// Terminal typing effect hook +function useTypingEffect(text: string, speed: number = 50, delay: number = 0) { + const [displayedText, setDisplayedText] = useState(""); + const [isComplete, setIsComplete] = useState(false); + + useEffect(() => { + let timeout: NodeJS.Timeout; + let charIndex = 0; + + const startTyping = () => { + const typeChar = () => { + if (charIndex < text.length) { + setDisplayedText(text.slice(0, charIndex + 1)); + charIndex++; + timeout = setTimeout(typeChar, speed); + } else { + setIsComplete(true); + } + }; + typeChar(); + }; + + timeout = setTimeout(startTyping, delay); + return () => clearTimeout(timeout); + }, [text, speed, delay]); + + return { displayedText, isComplete }; +} + +// Matrix rain character - client-only with stable random values +function MatrixChar({ index }: { index: number }) { + const chars = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン01"; + const [char, setChar] = useState("0"); + const [mounted, setMounted] = useState(false); + + // Generate stable random values based on index (seeded pseudo-random) + const { left, duration, delay, opacity } = useMemo(() => { + const seed = (index * 9301 + 49297) % 233280; + const rnd1 = seed / 233280; + const seed2 = (seed * 9301 + 49297) % 233280; + const rnd2 = seed2 / 233280; + const seed3 = (seed2 * 9301 + 49297) % 233280; + const rnd3 = seed3 / 233280; + const seed4 = (seed3 * 9301 + 49297) % 233280; + const rnd4 = seed4 / 233280; + + return { + left: rnd1 * 100, + duration: 8 + rnd2 * 4, + delay: rnd3 * 5, + opacity: 0.15 + rnd4 * 0.2, + }; + }, [index]); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setMounted(true); + const interval = setInterval(() => { + setChar(chars[Math.floor(Math.random() * chars.length)]); + }, 100); + return () => clearInterval(interval); + }, []); + + if (!mounted) return null; -export default function Home() { return ( -
-
- Next.js logo + {char} +
+ ); +} + +// Progress bar component +function ProgressBar() { + const [progress, setProgress] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setProgress((prev) => { + if (prev >= 100) return 0; + return prev + Math.random() * 3; + }); + }, 200); + return () => clearInterval(interval); + }, []); + + return ( +
+
+ Building visualizers... + {Math.min(Math.floor(progress), 99)}% +
+
+
-
-

- To get started, edit the page.tsx file. +

+
+ ); +} + +// Animated logo +function AnimatedLogo() { + return ( +
+ {/* Glow behind */} +
+ + {/* Logo */} +
+
+ + {"<>"} + +
+
+

+ OPENDSA

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. +

+ v0.1.0-alpha

- +
+ ); +} + +export default function ComingSoon() { + const [mounted, setMounted] = useState(false); + const { displayedText: line1, isComplete: line1Done } = useTypingEffect( + "$ initializing opendsa...", + 40, + 500 + ); + const { displayedText: line2, isComplete: line2Done } = useTypingEffect( + "$ loading algorithm visualizers...", + 40, + 2000 + ); + const { displayedText: line3 } = useTypingEffect( + "$ status: UNDER_DEVELOPMENT", + 40, + 3500 + ); + + const [showContent, setShowContent] = useState(false); + const [currentTime, setCurrentTime] = useState(""); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setMounted(true); + }, []); + + useEffect(() => { + const timer = setTimeout(() => setShowContent(true), 4500); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + const updateTime = () => { + setCurrentTime(new Date().toISOString().replace("T", " ").split(".")[0] + " UTC"); + }; + updateTime(); + const interval = setInterval(updateTime, 1000); + return () => clearInterval(interval); + }, []); + + if (!mounted) { + return ( +
+ ); + } + + return ( +
+ {/* Matrix rain background */} +
+ {[...Array(30)].map((_, i) => ( + + ))} +
+ + {/* Grid pattern */} +
+ + {/* Scanlines */} +
+ + {/* Gradient orbs */} +
+
+ + {/* Main content */} +
+ {/* Logo */} + + + {/* Terminal window */} +
+
+ {/* Terminal header */} +
+
+
+
+
+
+
+ + opendsa@dev ~ bash + +
+ + {currentTime} + +
+ + {/* Terminal content */} +
+ {/* Line 1 */} +
+ {line1} + {!line1Done && ( + + )} +
+ + {/* Line 2 */} + {line1Done && ( +
+ {line2} + {!line2Done && ( + + )} +
+ )} + + {/* Line 3 */} + {line2Done && ( +
+ {line3} +
+ )} + + {/* Status output */} + {showContent && ( +
+
+ Algorithm Engine: building +
+
+ Visualization Core: in progress +
+
+ UI Components: ready +
+
+ Documentation: available +
+
+ )} +
+
+
+ + {/* Coming soon message */} + {showContent && ( +
+
+

+ COMING SOON +

+

+ We're building something amazing. The open-source algorithm visualization platform is under active development. +

+
+ + {/* Progress bar */} + + + {/* Action buttons */} +
+ + + + + Star on GitHub + + + + + + + Visit Website + + + + + + + Read Docs + +
+ + {/* Tech stack hint */} +
+ {["Next.js 14", "TypeScript", "Framer Motion", "D3.js", "Tailwind"].map((tech) => ( + + {tech} + + ))} +
+
+ )} + + {/* Footer */} +
+

+ Made with 💚 by{" "} + + @soloshun + + {" "}• MIT License +

- +
+ +
); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ace5cd6..fbbe3b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,15 @@ importers: apps/app: dependencies: + clsx: + specifier: ^2.1.1 + version: 2.1.1 + framer-motion: + specifier: ^12.29.0 + version: 12.29.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + lucide-react: + specifier: ^0.563.0 + version: 0.563.0(react@19.2.3) next: specifier: 16.1.4 version: 16.1.4(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -35,6 +44,9 @@ importers: react-dom: specifier: 19.2.3 version: 19.2.3(react@19.2.3) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 devDependencies: '@tailwindcss/postcss': specifier: ^4