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}
+
-
+
Semantic Intelligence for
-
Large-Scale Engineering.
+
Large-Scale Engineering.
-
-
- {toolGroups.map(({ name, color, layout, tools }) => (
-
-
- {name}
-
-
- {tools.map(({ color: toolColor, 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"
>
-
{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 (
+ <>
+
+
+ {toolGroups.map(({ name, color, layout, tools }) => (
+
+
+ {name}
+
+
+ {tools.map(({ color: toolColor, label }) => (
+
+ ))}
+
+
+ ))}
+
+ >
+ );
+}
|