From 3b55fa25d1640b1404d07e0cc34acd03539f8537 Mon Sep 17 00:00:00 2001 From: Kshitij Varma Date: Sun, 1 Mar 2026 13:30:08 +0530 Subject: [PATCH] feat(landing): add dark mode support --- landing/src/app/globals.css | 109 +++++- landing/src/app/layout.tsx | 5 +- landing/src/app/page.tsx | 361 ++---------------- landing/src/components/Background.tsx | 56 ++- landing/src/components/DarkModeToggle.tsx | 60 +++ landing/src/components/Header.tsx | 71 ++++ landing/src/components/IdeSetup.tsx | 67 ++-- .../src/components/InstructionsSection.tsx | 10 +- landing/src/components/IsometricDiagram.tsx | 43 ++- landing/src/components/LetterGlitch.tsx | 10 +- landing/src/components/ThemeProvider.tsx | 52 +++ landing/src/components/ToolDiagram.tsx | 319 ++++++++++++++++ 12 files changed, 756 insertions(+), 407 deletions(-) create mode 100644 landing/src/components/DarkModeToggle.tsx create mode 100644 landing/src/components/Header.tsx create mode 100644 landing/src/components/ThemeProvider.tsx create mode 100644 landing/src/components/ToolDiagram.tsx diff --git a/landing/src/app/globals.css b/landing/src/app/globals.css index 42a4810..4b59354 100644 --- a/landing/src/app/globals.css +++ b/landing/src/app/globals.css @@ -1,5 +1,43 @@ @import "tailwindcss"; +:root { + --bg-primary: #efefef; + --bg-secondary: #ffffff; + --bg-tertiary: #f5f5f5; + --text-primary: #000000; + --text-secondary: #666666; + --text-muted: rgba(0, 0, 0, 0.5); + --border-color: rgba(0, 0, 0, 0.1); + --nav-bg: rgba(239, 239, 239, 0.6); + --toggle-bg: rgba(255, 255, 255, 0.8); + --shadow-color: rgba(0, 0, 0, 0.07); + --scrollbar-thumb: rgba(0, 0, 0, 0.12); + --scrollbar-thumb-hover: rgba(0, 0, 0, 0.2); + --code-bg: #f8f8f8; + --code-border: rgba(0, 0, 0, 0.08); + --table-border: rgba(0, 0, 0, 0.06); + --icon-color: #1e1e1e; +} + +[data-theme="dark"] { + --bg-primary: #0a0a0a; + --bg-secondary: #141414; + --bg-tertiary: #1a1a1a; + --text-primary: #f0f0f0; + --text-secondary: #a0a0a0; + --text-muted: rgba(255, 255, 255, 0.5); + --border-color: rgba(255, 255, 255, 0.1); + --nav-bg: rgba(10, 10, 10, 0.6); + --toggle-bg: rgba(30, 30, 30, 0.8); + --shadow-color: rgba(0, 0, 0, 0.3); + --scrollbar-thumb: rgba(255, 255, 255, 0.12); + --scrollbar-thumb-hover: rgba(255, 255, 255, 0.2); + --code-bg: #1a1a1a; + --code-border: rgba(255, 255, 255, 0.08); + --table-border: rgba(255, 255, 255, 0.06); + --icon-color: #e0e0e0; +} + * { margin: 0; padding: 0; @@ -7,10 +45,11 @@ } body { - background: #efefef; - color: #000; + background: var(--bg-primary); + color: var(--text-primary); font-weight: 300; -webkit-font-smoothing: antialiased; + transition: background 0.3s ease, color 0.3s ease; } h1, @@ -28,27 +67,30 @@ label, .site-footer, .hero-title, .hero-text { - text-shadow: 0 4px 12px rgba(0, 0, 0, 0.07); + text-shadow: 0 4px 12px var(--shadow-color); } ::-webkit-scrollbar { width: 6px; height: 6px; } + ::-webkit-scrollbar-track { background: transparent; } + ::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.12); + background: var(--scrollbar-thumb); border-radius: 3px; } + ::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.2); + background: var(--scrollbar-thumb-hover); } * { scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.12) transparent; + scrollbar-color: var(--scrollbar-thumb) transparent; } @media (min-width: 1600px) { @@ -57,30 +99,37 @@ label, padding-right: 200px !important; min-height: 70vh !important; } + .iso-diagram { + padding-left: 200px !important; padding-right: 200px !important; } } + @media (max-width: 1000px) { .hero-diagram-row { min-height: 60vh !important; } } + @media (min-width: 1000px) and (max-width: 1400px) { .hero-diagram-row { min-height: 50vh !important; } } + @media (min-width: 1400px) and (max-width: 1600px) { .hero-diagram-row { min-height: 60vh !important; } } + @media (min-width: 1440px) { .tool-square { width: 180px !important; height: 180px !important; } + .diagram-groups { gap: 24px !important; } @@ -91,6 +140,7 @@ label, width: 220px !important; height: 220px !important; } + .diagram-groups { gap: 32px !important; } @@ -107,62 +157,77 @@ label, .hero-diagram-row { padding: 48px 60px 20px !important; } + .nav-bar { padding: 40px 60px 30px !important; } + .hero-section { padding: 48px 60px !important; } + .instructions-section { padding: 0 60px 40px !important; } + .hero-title { font-size: 44px !important; line-height: 56px !important; } + .hero-text { font-size: 16px !important; line-height: 26px !important; } + .diagram-outer { margin-left: 60px !important; margin-right: 60px !important; width: auto !important; } + .diagram-groups { flex-direction: column !important; width: 100% !important; } - .diagram-groups > div { + + .diagram-groups>div { width: 100% !important; } + .group-inner-col { flex-direction: row !important; flex-wrap: wrap !important; width: 100% !important; } - .group-inner-col > div { + + .group-inner-col>div { flex: 1 1 0 !important; min-width: 0 !important; } + .discovery-grid { grid-template-columns: 1fr 1fr !important; width: 100% !important; } + .tool-square { width: 100% !important; max-width: none !important; height: 80px !important; aspect-ratio: auto !important; } + .quote-section { padding: 60px 60px !important; min-height: 70vh !important; } + .quote-section p { font-size: 32px !important; line-height: 48px !important; } + .site-footer { padding: 40px 60px !important; } @@ -173,71 +238,90 @@ label, padding: 32px 20px 16px !important; flex-direction: column !important; } + .nav-bar { padding: 20px 20px 16px !important; } + .hero-section { padding: 32px 20px !important; } + .hero-title { font-size: 26px !important; line-height: 40px !important; } + .hero-text { font-size: 15px !important; line-height: 24px !important; } + .diagram-outer { margin-left: 20px !important; margin-right: 20px !important; } + .tool-square { max-width: none !important; height: 70px !important; } + .tools-ref { padding: 40px 20px !important; } + .ide-setup { padding: 40px 20px !important; } + .instructions-section { padding: 0 20px 40px !important; } + .ide-tab-bar { flex-direction: column !important; align-items: flex-start !important; } + .ide-tab-bar button { padding: 8px 12px !important; font-size: 12px !important; } + .tools-ref td { display: block !important; padding: 8px 0 !important; } + .tools-ref tr { display: block !important; padding: 16px 0 !important; border-bottom: 1px solid rgba(0, 0, 0, 0.06) !important; } + .tools-ref td:first-child { border-bottom: none !important; } + .tools-ref td:last-child { border-bottom: none !important; } + .tools-ref .code-pair { flex-direction: column !important; } + .quote-section { padding: 40px 20px !important; min-height: 70vh !important; } + .quote-section p { font-size: 24px !important; line-height: 36px !important; } + .site-footer { padding: 60px 20px !important; flex-direction: column !important; @@ -249,10 +333,12 @@ label, .tool-square { height: 60px !important; } + .discovery-grid { gap: 8px !important; padding: 12px !important; } + .group-inner-col { padding: 12px !important; } @@ -262,19 +348,24 @@ label, .ide-inner-row { gap: 0 !important; } + .ide-dashed-square { display: none !important; } + .ide-setup { justify-content: center !important; } + .instr-inner-row { gap: 0 !important; } + .instr-dashed-square { display: none !important; } + .instructions-section { justify-content: center !important; } -} +} \ No newline at end of file diff --git a/landing/src/app/layout.tsx b/landing/src/app/layout.tsx index 83b416d..12f2b5d 100644 --- a/landing/src/app/layout.tsx +++ b/landing/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { GeistSans } from "geist/font/sans"; import { GeistMono } from "geist/font/mono"; import { GeistPixelSquare, GeistPixelLine } from "geist/font/pixel"; +import { ThemeProvider } from "../components/ThemeProvider"; import "./globals.css"; export const metadata: Metadata = { @@ -19,11 +20,11 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + {children} ); diff --git a/landing/src/app/page.tsx b/landing/src/app/page.tsx index 32366d9..98cbc3c 100644 --- a/landing/src/app/page.tsx +++ b/landing/src/app/page.tsx @@ -1,51 +1,12 @@ import Background from "../components/Background"; +import Header from "../components/Header"; import IdeSetup from "../components/IdeSetup"; import InstructionsSection from "../components/InstructionsSection"; import IsometricDiagram from "../components/IsometricDiagram"; +import ToolDiagram from "../components/ToolDiagram"; export const dynamic = "force-dynamic"; -const toolGroups = [ - { - name: "Discovery", - color: "#000000", - layout: "grid" as const, - tools: [ - { color: "#000000", label: "Context Tree" }, - { color: "#111111", label: "File Skeleton" }, - { color: "#222222", label: "Semantic Search" }, - { color: "#333333", label: "Semantic Identifiers" }, - ], - }, - { - name: "Analysis", - color: "#444444", - layout: "column" as const, - tools: [ - { color: "#444444", label: "Blast Radius" }, - { color: "#555555", label: "Static Analysis" }, - ], - }, - { - name: "Code Ops", - color: "#666666", - layout: "column" as const, - tools: [ - { color: "#666666", label: "Propose Commit" }, - { color: "#777777", label: "Feature Hub" }, - ], - }, - { - name: "Version Control", - color: "#888888", - layout: "column" as const, - tools: [ - { color: "#888888", label: "Restore Points" }, - { color: "#999999", label: "Undo Change" }, - ], - }, -]; - const toolRefRows = [ { name: "get_context_tree", @@ -150,63 +111,7 @@ export default async function Home() { return (
- +
Semantic Intelligence for
- Large-Scale Engineering. + Large-Scale Engineering.

-

-
- - - - - - - - -
- - Context+ MCP - -
- - - - - - - - -
-
-
- {toolGroups.map(({ name, color, layout, tools }) => ( -
- - {name} - -
- {tools.map(({ color: toolColor, label }) => ( -
- - {label} - -
- - - - - - - - -
-
- ))} -
-
- ))} -
+
@@ -505,7 +202,7 @@ export default async function Home() { lineHeight: "28px", fontFamily: "var(--font-geist-pixel-square)", letterSpacing: "-0.02em", - background: "linear-gradient(180deg, #000000 0%, #666666 100%)", + background: "linear-gradient(180deg, var(--text-primary) 0%, var(--text-secondary) 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", backgroundClip: "text" as const, @@ -534,7 +231,7 @@ export default async function Home() { @@ -543,7 +240,7 @@ export default async function Home() { fontFamily: "var(--font-geist-pixel-square)", fontSize: 14, fontWeight: 500, - color: "#000", + color: "var(--text-primary)", letterSpacing: "-0.02em", }} > @@ -553,7 +250,7 @@ export default async function Home() {

Context+ @@ -725,7 +422,7 @@ export default async function Home() { rel="noopener noreferrer" className="flex items-center" > - + @@ -741,7 +438,7 @@ export default async function Home() { height="18" viewBox="0 0 24 24" fill="none" - stroke="#1E1E1E" + stroke="var(--icon-color)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" @@ -749,8 +446,8 @@ export default async function Home() { {stars} diff --git a/landing/src/components/Background.tsx b/landing/src/components/Background.tsx index 73e9b3a..6b9fe99 100644 --- a/landing/src/components/Background.tsx +++ b/landing/src/components/Background.tsx @@ -1,12 +1,46 @@ "use client"; import dynamic from "next/dynamic"; +import { createContext, useContext, useEffect, useState } from "react"; const LetterGlitch = dynamic(() => import("./LetterGlitch"), { ssr: false }); export default function Background() { + const [isDark, setIsDark] = useState(false); + + useEffect(() => { + const checkTheme = () => { + const theme = document.documentElement.getAttribute("data-theme"); + setIsDark(theme === "dark"); + }; + + checkTheme(); + + const observer = new MutationObserver(checkTheme); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }); + + return () => observer.disconnect(); + }, []); + return ( <> + {/* Base background layer */} +

+ {/* Matrix characters layer */}
+ {/* Vignette overlay for text readability */}
diff --git a/landing/src/components/DarkModeToggle.tsx b/landing/src/components/DarkModeToggle.tsx new file mode 100644 index 0000000..48c173e --- /dev/null +++ b/landing/src/components/DarkModeToggle.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { useTheme } from "./ThemeProvider"; + +export default function DarkModeToggle() { + const { theme, toggleTheme } = useTheme(); + + return ( + + ); +} diff --git a/landing/src/components/Header.tsx b/landing/src/components/Header.tsx new file mode 100644 index 0000000..4941423 --- /dev/null +++ b/landing/src/components/Header.tsx @@ -0,0 +1,71 @@ +"use client"; + +import DarkModeToggle from "./DarkModeToggle"; + +interface HeaderProps { + stars: number; +} + +export default function Header({ stars }: HeaderProps) { + return ( + + ); +} diff --git a/landing/src/components/IdeSetup.tsx b/landing/src/components/IdeSetup.tsx index 2783567..e5f40b8 100644 --- a/landing/src/components/IdeSetup.tsx +++ b/landing/src/components/IdeSetup.tsx @@ -66,7 +66,7 @@ function buildInitCommand(runner: string, agent: string): string { function highlightJson(json: string): ReactNode[] { const tokenRegex = - /("(?:[^"\\]|\\.)*")\s*:|("(?:[^"\\]|\\.)*")|(\btrue\b|\bfalse\b|\bnull\b)|(-?\d+(?:\.\d+)?)|([{}[\]:,])/g; + /"(?:[^"\\]|\\.)*"\s*:|"(?:[^"\\]|\\.)*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?|[{}[\]:,]/g; const parts: ReactNode[] = []; let lastIndex = 0; @@ -77,40 +77,35 @@ function highlightJson(json: string): ReactNode[] { parts.push(json.slice(lastIndex, match.index)); } - if (match[1]) { + const token = match[0]; + if (token.endsWith(":")) { parts.push( - - {match[1]} + + {token} , ); + } else if (token.startsWith('"')) { parts.push( - json.slice( - match.index + match[1].length, - match.index + match[0].length, - ), - ); - } else if (match[2]) { - parts.push( - - {match[2]} + + {token} , ); - } else if (match[3]) { + } else if (token === "true" || token === "false" || token === "null") { parts.push( - - {match[3]} + + {token} , ); - } else if (match[4]) { + } else if (/^-?\d/.test(token)) { parts.push( - - {match[4]} + + {token} , ); - } else if (match[5]) { + } else { parts.push( - - {match[5]} + + {token} , ); } @@ -179,7 +174,7 @@ export default function IdeSetup() { lineHeight: "28px", fontFamily: "var(--font-geist-pixel-square)", letterSpacing: "-0.02em", - background: "linear-gradient(180deg, #000000 0%, #666666 100%)", + background: "linear-gradient(180deg, var(--text-primary) 0%, var(--text-secondary) 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", backgroundClip: "text", @@ -215,9 +210,9 @@ export default function IdeSetup() { fontWeight: 300, fontFamily: "var(--font-geist-mono)", letterSpacing: "-0.02em", - color: activeIde === i.id ? "#000" : "#888", + color: activeIde === i.id ? "var(--text-primary)" : "var(--text-secondary)", background: - activeIde === i.id ? "rgba(0,0,0,0.04)" : "none", + activeIde === i.id ? "var(--code-bg)" : "none", backdropFilter: activeIde === i.id ? "blur(8px)" : "none", WebkitBackdropFilter: activeIde === i.id ? "blur(8px)" : "none", @@ -241,9 +236,9 @@ export default function IdeSetup() { fontSize: 13, fontWeight: 300, fontFamily: "var(--font-geist-mono)", - color: activeRunner === r.id ? "#000" : "#888", + color: activeRunner === r.id ? "var(--text-primary)" : "var(--text-secondary)", background: - activeRunner === r.id ? "rgba(0,0,0,0.04)" : "none", + activeRunner === r.id ? "var(--code-bg)" : "none", backdropFilter: activeRunner === r.id ? "blur(8px)" : "none", WebkitBackdropFilter: @@ -262,7 +257,7 @@ export default function IdeSetup() {
@@ -297,7 +292,7 @@ export default function IdeSetup() { padding: "4px 8px", fontSize: 13, fontWeight: 300, - color: copied ? "#000" : "#888", + color: copied ? "var(--text-primary)" : "var(--text-secondary)", fontFamily: "var(--font-geist-mono)", transition: "color 0.15s", }} @@ -311,7 +306,7 @@ export default function IdeSetup() { fontSize: 13, fontWeight: 300, lineHeight: "20px", - color: "#333", + color: "var(--text-secondary)", padding: "12px 24px 20px", overflow: "auto", whiteSpace: "pre", @@ -324,7 +319,7 @@ export default function IdeSetup() {
@@ -359,7 +354,7 @@ export default function IdeSetup() { padding: "4px 8px", fontSize: 13, fontWeight: 300, - color: copiedInit ? "#000" : "#888", + color: copiedInit ? "var(--text-primary)" : "var(--text-secondary)", fontFamily: "var(--font-geist-mono)", transition: "color 0.15s", }} @@ -373,7 +368,7 @@ export default function IdeSetup() { fontSize: 13, fontWeight: 300, lineHeight: "20px", - color: "#333", + color: "var(--text-secondary)", padding: "12px 24px 20px", overflow: "auto", whiteSpace: "pre", @@ -396,7 +391,7 @@ export default function IdeSetup() { fontSize: 13, fontWeight: 300, fontFamily: "var(--font-geist-pixel-square)", - color: "#888", + color: "var(--text-secondary)", textDecoration: "none", transition: "color 0.15s", }} diff --git a/landing/src/components/InstructionsSection.tsx b/landing/src/components/InstructionsSection.tsx index b094bd2..c7b50c3 100644 --- a/landing/src/components/InstructionsSection.tsx +++ b/landing/src/components/InstructionsSection.tsx @@ -239,7 +239,7 @@ export default function InstructionsSection() { lineHeight: "28px", fontFamily: "var(--font-geist-pixel-square)", letterSpacing: "-0.02em", - background: "linear-gradient(180deg, #000000 0%, #666666 100%)", + background: "linear-gradient(180deg, var(--text-primary) 0%, var(--text-secondary) 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", backgroundClip: "text" as const, @@ -256,7 +256,7 @@ export default function InstructionsSection() {
@@ -290,7 +290,7 @@ export default function InstructionsSection() { padding: "4px 8px", fontSize: 13, fontWeight: 300, - color: copied ? "#000" : "#888", + color: copied ? "var(--text-primary)" : "var(--text-secondary)", fontFamily: "var(--font-geist-mono)", transition: "color 0.15s", }} @@ -304,7 +304,7 @@ export default function InstructionsSection() { fontSize: 13, fontWeight: 300, lineHeight: "20px", - color: "#333", + color: "var(--text-secondary)", padding: "12px 24px 20px", overflow: "auto", whiteSpace: "pre-wrap", diff --git a/landing/src/components/IsometricDiagram.tsx b/landing/src/components/IsometricDiagram.tsx index 07ca169..2f866f8 100644 --- a/landing/src/components/IsometricDiagram.tsx +++ b/landing/src/components/IsometricDiagram.tsx @@ -2,6 +2,26 @@ import { useRef, useState, useEffect, useCallback } from "react"; +function useTheme() { + const [isDark, setIsDark] = useState(false); + + useEffect(() => { + const checkTheme = () => { + const theme = document.documentElement.getAttribute("data-theme"); + setIsDark(theme === "dark"); + }; + checkTheme(); + const observer = new MutationObserver(checkTheme); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }); + return () => observer.disconnect(); + }, []); + + return isDark; +} + const functions = [ { id: "context-tree", @@ -86,6 +106,7 @@ export default function IsometricDiagram() { const [animating, setAnimating] = useState(false); const [animCardId, setAnimCardId] = useState(null); const [windowWidth, setWindowWidth] = useState(1200); + const isDark = useTheme(); useEffect(() => { const update = () => setWindowWidth(window.innerWidth); @@ -171,7 +192,7 @@ export default function IsometricDiagram() { aspectRatio: isMobile ? "1 / 1" : undefined, justifyContent: isMobile ? "center" : "flex-end", marginBottom: `${isMobile ? 20 : isVerySmallDesktop ? 60 : isSmallDesktop ? 120 : 300}px !important`, - padding: isMobile ? "0 20px 30px" : "0 60px 40px 100px", + padding: isMobile ? "0 20px 30px" : "0 100px 40px 100px", overflow: isMobile ? "hidden" : undefined, }} > @@ -199,9 +220,19 @@ export default function IsometricDiagram() { const yPos = visualIdx * STACK_DY; const t = visualIdx / (functions.length - 1); - const gray = Math.round(t * 210); + // Dark mode: brighter borders (white to gray), Light mode: darker borders (black to gray) + const grayLight = Math.round(t * 210); + const grayDark = Math.round(255 - t * 180); + const gray = isDark ? grayDark : grayLight; const borderColor = `rgb(${gray},${gray},${gray})`; + // Theme-aware backgrounds + const cardBg = isDark ? "rgba(20,20,20,0.85)" : "rgba(239,239,239,0.8)"; + const labelBg = isDark ? "rgba(20,20,20,0.8)" : "rgba(239,239,239,0.7)"; + const cardShadow = isHovered + ? isDark ? "0 16px 40px rgba(0,0,0,0.4)" : "0 16px 40px rgba(0,0,0,0.18)" + : isDark ? "0 2px 8px rgba(0,0,0,0.2)" : "0 2px 8px rgba(0,0,0,0.04)"; + const isFadingOut = animPhase === "fade-out" && isAnimCard; const isFadingIn = animPhase === "fade-in" && isAnimCard; @@ -236,10 +267,8 @@ export default function IsometricDiagram() { : "translateZ(0)", opacity: isFadingOut ? 0 : isFadingIn ? 0.5 : 1, filter: isFadingOut ? "blur(8px)" : "blur(0px)", - boxShadow: isHovered - ? "0 16px 40px rgba(0,0,0,0.18)" - : "0 2px 8px rgba(0,0,0,0.04)", - background: "rgba(239,239,239,0.8)", + boxShadow: cardShadow, + background: cardBg, backdropFilter: "blur(6px)", WebkitBackdropFilter: "blur(6px)", zIndex: isHovered ? 10 : functions.length - visualIdx, @@ -293,7 +322,7 @@ export default function IsometricDiagram() { letterSpacing: "-0.01em", whiteSpace: "nowrap", pointerEvents: "none", - background: "rgba(239,239,239,0.7)", + background: labelBg, backdropFilter: "blur(4px)", WebkitBackdropFilter: "blur(4px)", padding: "2px 6px", diff --git a/landing/src/components/LetterGlitch.tsx b/landing/src/components/LetterGlitch.tsx index 656ba45..ea97286 100644 --- a/landing/src/components/LetterGlitch.tsx +++ b/landing/src/components/LetterGlitch.tsx @@ -60,10 +60,10 @@ const LetterGlitch = ({ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16), - } + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } : null; }; @@ -234,7 +234,7 @@ const LetterGlitch = ({ position: "relative", width: "100%", height: "100%", - backgroundColor: "#EFEFEF", + backgroundColor: "transparent", overflow: "hidden", }; diff --git a/landing/src/components/ThemeProvider.tsx b/landing/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..e5c9b81 --- /dev/null +++ b/landing/src/components/ThemeProvider.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "light" | "dark"; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + +export function ThemeProvider({ children }: { children: React.ReactNode }) { + const [theme, setTheme] = useState("light"); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + const stored = localStorage.getItem("theme") as Theme | null; + if (stored) { + setTheme(stored); + } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + setTheme("dark"); + } + }, []); + + useEffect(() => { + if (mounted) { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("theme", theme); + } + }, [theme, mounted]); + + const toggleTheme = () => { + setTheme((prev: Theme) => (prev === "light" ? "dark" : "light")); + }; + + return ( + + {children} + + ); +} + +export function useTheme() { + const context = useContext(ThemeContext); + if (context === undefined) { + return { theme: "light" as Theme, toggleTheme: () => { } }; + } + return context; +} diff --git a/landing/src/components/ToolDiagram.tsx b/landing/src/components/ToolDiagram.tsx new file mode 100644 index 0000000..6438d05 --- /dev/null +++ b/landing/src/components/ToolDiagram.tsx @@ -0,0 +1,319 @@ +"use client"; + +import { useEffect, useState } from "react"; + +const toolGroupsLight = [ + { + name: "Discovery", + color: "#000000", + layout: "grid" as const, + tools: [ + { color: "#000000", label: "Context Tree" }, + { color: "#111111", label: "File Skeleton" }, + { color: "#222222", label: "Semantic Search" }, + { color: "#333333", label: "Semantic Identifiers" }, + ], + }, + { + name: "Analysis", + color: "#444444", + layout: "column" as const, + tools: [ + { color: "#444444", label: "Blast Radius" }, + { color: "#555555", label: "Static Analysis" }, + ], + }, + { + name: "Code Ops", + color: "#666666", + layout: "column" as const, + tools: [ + { color: "#666666", label: "Propose Commit" }, + { color: "#777777", label: "Feature Hub" }, + ], + }, + { + name: "Version Control", + color: "#888888", + layout: "column" as const, + tools: [ + { color: "#888888", label: "Restore Points" }, + { color: "#999999", label: "Undo Change" }, + ], + }, +]; + +const toolGroupsDark = [ + { + name: "Discovery", + color: "#ffffff", + layout: "grid" as const, + tools: [ + { color: "#ffffff", label: "Context Tree" }, + { color: "#eeeeee", label: "File Skeleton" }, + { color: "#dddddd", label: "Semantic Search" }, + { color: "#cccccc", label: "Semantic Identifiers" }, + ], + }, + { + name: "Analysis", + color: "#bbbbbb", + layout: "column" as const, + tools: [ + { color: "#bbbbbb", label: "Blast Radius" }, + { color: "#aaaaaa", label: "Static Analysis" }, + ], + }, + { + name: "Code Ops", + color: "#999999", + layout: "column" as const, + tools: [ + { color: "#999999", label: "Propose Commit" }, + { color: "#888888", label: "Feature Hub" }, + ], + }, + { + name: "Version Control", + color: "#777777", + layout: "column" as const, + tools: [ + { color: "#777777", label: "Restore Points" }, + { color: "#666666", label: "Undo Change" }, + ], + }, +]; + +export default function ToolDiagram() { + const [isDark, setIsDark] = useState(false); + + useEffect(() => { + const checkTheme = () => { + const theme = document.documentElement.getAttribute("data-theme"); + setIsDark(theme === "dark"); + }; + checkTheme(); + const observer = new MutationObserver(checkTheme); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }); + return () => observer.disconnect(); + }, []); + + const toolGroups = isDark ? toolGroupsDark : toolGroupsLight; + + return ( + <> +
+
+ + + + + + + + +
+ + Context+ MCP + +
+ + + + + + + + +
+
+
+ {toolGroups.map(({ name, color, layout, tools }) => ( +
+ + {name} + +
+ {tools.map(({ color: toolColor, label }) => ( +
+ + {label} + +
+ + + + + + + + +
+
+ ))} +
+
+ ))} +
+ + ); +}