This commit is contained in:
Rob Colbert 2026-05-13 22:10:28 -04:00
parent 35fe099dd1
commit 8fd6e06f19
24 changed files with 3317 additions and 0 deletions

2
site/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist
node_modules

26
site/index.html Normal file
View File

@ -0,0 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0a0a0a" />
<meta name="description" content="Gadget Code — Self-hosted Agentic Engineering Platform. Build software with AI agents on your infrastructure, under your control." />
<meta property="og:title" content="Gadget Code — Self-Hosted Agentic Engineering" />
<meta property="og:description" content="The platform where AI agents and humans build software together — on your infrastructure, under your control." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://g4dge7.com" />
<meta property="og:image" content="/web-app.png" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Gadget Code — Self-Hosted Agentic Engineering" />
<meta name="twitter:description" content="The platform where AI agents and humans build software together — on your infrastructure, under your control." />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>Gadget Code — Self-Hosted Agentic Engineering Platform</title>
</head>
<body>
<a href="#main-content" class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:px-4 focus:py-2 focus:bg-brand focus:text-white">
Skip to content
</a>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

1648
site/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
site/package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "gadget-landing-site",
"version": "1.0.0",
"description": "Gadget Code and Gadget — Landing Page",
"type": "module",
"scripts": {
"dev": "vite",
"typecheck": "tsc --noEmit",
"build": "tsc --noEmit && vite build",
"preview": "vite preview"
},
"author": "Robert Colbert <rob.colbert@openplatform.us>",
"license": "Apache-2.0",
"dependencies": {
"@react-three/fiber": "^9.6.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"three": "^0.184.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.184.0",
"@vitejs/plugin-react": "^6.0.1",
"postcss": "^8.5.10",
"tailwindcss": "^4.2.4",
"typescript": "^5.8.3",
"vite": "^8.0.10"
}
}

5
site/postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};

BIN
site/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
site/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
site/public/web-app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

39
site/src/App.tsx Normal file
View File

@ -0,0 +1,39 @@
import { useState, useEffect } from 'react';
import Header from './components/Header';
import Hero from './components/Hero';
import ProblemSection from './components/ProblemSection';
import GadgetCodeSection from './components/GadgetCodeSection';
import GadgetSection from './components/GadgetSection';
import BuiltWithSection from './components/BuiltWithSection';
import GetStartedSection from './components/GetStartedSection';
import Footer from './components/Footer';
export default function App() {
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 50);
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
<div className="min-h-screen flex flex-col bg-bg-primary text-text-primary">
<Header scrolled={scrolled} />
<main className="flex-1">
<Hero />
<div className="section-divider" />
<ProblemSection />
<div className="section-divider" />
<GadgetCodeSection />
<div className="section-divider" />
<GadgetSection />
<div className="section-divider" />
<BuiltWithSection />
<div className="section-divider" />
<GetStartedSection />
</main>
<Footer />
</div>
);
}

View File

@ -0,0 +1,63 @@
export default function BuiltWithSection() {
return (
<section id="built-with" className="py-20 px-6">
<div className="max-w-4xl mx-auto">
{/* Section Header */}
<div className="text-center mb-12">
<span className="text-xs font-mono tracking-widest text-brand mb-3 block">
CREDIBILITY
</span>
<h2 className="text-3xl md:text-4xl font-mono font-bold tracking-tight text-text-primary">
We Ship Our Own Dogfood
</h2>
</div>
{/* Narrative */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-8">
{/* Status bar accent */}
<div className="flex items-center gap-2 mb-6 pb-4 border-b border-border-default">
<div className="w-1.5 h-1.5 rounded-full bg-green-glow status-indicator" />
<span className="text-xs font-mono tracking-widest text-green-glow/80">
PRODUCTION VERIFIED
</span>
</div>
<p className="text-text-secondary leading-relaxed mb-4">
Gadget the browser extension and web app was built using Gadget Code.
Every feature, every line, every agent-assisted commit. The AI agent
participated in development across all five workspace modes: Inspect,
Station, Update, Agent, and Agent+.
</p>
<p className="text-text-secondary leading-relaxed mb-4">
This isn't a demo project or a proof-of-concept. It's a production
platform that ships production software. The same guardrails, observability,
and agent-first architecture that we're offering to you are what we use
every day to build our own products.
</p>
<p className="text-text-muted leading-relaxed text-sm italic">
We don't ship anything we wouldn't use ourselves. Gadget Code built Gadget.
Gadget proves Gadget Code works.
</p>
{/* Visual data strip */}
<div className="mt-6 pt-4 border-t border-border-default flex flex-wrap gap-6">
<div className="flex items-center gap-2">
<span className="font-mono text-lg text-brand font-bold">5</span>
<span className="text-xs font-mono text-text-muted tracking-wide">WORKSPACE MODES</span>
</div>
<div className="flex items-center gap-2">
<span className="font-mono text-lg text-brand font-bold">100%</span>
<span className="text-xs font-mono text-text-muted tracking-wide">AGENT PARTICIPATION</span>
</div>
<div className="flex items-center gap-2">
<span className="font-mono text-lg text-brand font-bold">0</span>
<span className="text-xs font-mono text-text-muted tracking-wide">THIRD-PARTY TRUST REQ</span>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,53 @@
export default function Footer() {
return (
<footer className="border-t border-border-default bg-bg-secondary">
<div className="max-w-6xl mx-auto px-6 py-6">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
{/* Left: Copyright + Version */}
<div className="flex items-center gap-4">
<span className="text-xs font-mono text-text-muted">
© 2026 DTP Technologies, LLC
</span>
<span className="text-xs font-mono text-text-muted/50">
Gadget Code v1.0.0
</span>
</div>
{/* Center: Links */}
<div className="flex items-center gap-4">
<a
href="/terms"
className="text-xs font-mono text-text-muted hover:text-text-secondary transition-colors"
>
TERMS OF SERVICE
</a>
<span className="text-text-muted/30"></span>
<a
href="/privacy"
className="text-xs font-mono text-text-muted hover:text-text-secondary transition-colors"
>
PRIVACY POLICY
</a>
</div>
{/* Right: Domain */}
<a
href="https://g4dge7.com"
className="text-xs font-mono text-text-muted hover:text-brand transition-colors tracking-wider"
>
G4DGE7.COM
</a>
</div>
{/* Bottom accent */}
<div className="mt-4 pt-3 border-t border-border-subtle flex items-center justify-center gap-2">
<div className="w-1 h-1 rounded-full bg-green-glow/40" />
<span className="text-xs font-mono text-text-muted/40 tracking-widest">
SELF-HOSTED · OPEN SOURCE · AGENTIC
</span>
<div className="w-1 h-1 rounded-full bg-green-glow/40" />
</div>
</div>
</footer>
);
}

View File

@ -0,0 +1,102 @@
const FEATURES = [
{
title: 'Agents Are Native',
description:
'The agent is built into the platform, not duct-taped onto a text editor. It participates in defined modes with explicit boundaries — not as an afterthought extension.',
icon: '⬡',
},
{
title: 'Full Observability',
description:
'Every tool call, file operation, and reasoning step is visible in real-time. No black boxes. No surprises. You see what the agent is doing, always.',
icon: '◉',
},
{
title: 'Workspace Guardrails',
description:
'Mode-based workspace locking prevents destructive concurrent operations. The agent can\'t edit files you\'re editing. You can\'t delete directories the agent is working in.',
icon: '◧',
},
{
title: 'Your Infrastructure',
description:
'Self-hosted. No telemetry. No data harvesting. Your code never leaves your network. Zero third-party trust required. Deploy on your terms.',
icon: '⬢',
},
{
title: 'Open Source',
description:
'Apache 2.0 license. Fork it. Own it. Extend it. No vendor lock-in, no seat licenses, no enterprise tiers. The code is yours.',
icon: '⟐',
},
{
title: 'Choose Your Compute',
description:
'Ollama, OpenAI, Gab AI, or any OpenAI-compatible API. Route different models to different tasks. Your agents, your compute, your choice.',
icon: '⬡',
},
];
export default function GadgetCodeSection() {
return (
<section id="gadget-code" className="py-20 px-6">
<div className="max-w-6xl mx-auto">
{/* Section Header */}
<div className="text-center mb-16">
<span className="text-xs font-mono tracking-widest text-brand mb-3 block">
SOLUTION
</span>
<h2 className="text-3xl md:text-4xl font-mono font-bold tracking-tight text-text-primary">
Engineering, Not Vibes
</h2>
<p className="mt-4 text-text-secondary max-w-2xl mx-auto">
Gadget Code is a self-hosted, open source Agentic Engineering Platform.
The AI agent isn't a bolt-on extension — it's a first-class participant
with guardrails, audit trails, and full observability.
</p>
</div>
{/* Feature Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{FEATURES.map((feature) => (
<div
key={feature.title}
className="feature-card bg-bg-secondary border border-border-default rounded-lg p-5"
>
{/* Icon + Title */}
<div className="flex items-center gap-3 mb-3">
<span className="text-brand text-lg font-mono">{feature.icon}</span>
<h3 className="font-mono text-sm font-bold tracking-wide text-text-primary">
{feature.title.toUpperCase()}
</h3>
</div>
{/* Description */}
<p className="text-sm text-text-secondary leading-relaxed">
{feature.description}
</p>
</div>
))}
</div>
{/* CTA */}
<div className="mt-12 text-center">
<a
href="https://git.g4dge7.com"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-8 py-3 text-sm font-mono tracking-wider border border-brand bg-brand hover:bg-brand-light text-white rounded transition-all shadow-lg shadow-brand/20"
>
GET THE SOURCE CODE
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="ml-1">
<path d="M1 7h12M8 3l4 4-4 4" stroke="currentColor" strokeWidth="1.5" />
</svg>
</a>
<p className="mt-3 text-xs font-mono text-text-muted">
Free & Open Source · Apache 2.0 · No account required
</p>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,444 @@
// @ts-nocheck
import { useRef, useMemo, useEffect } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three';
const BOARD_SIZE = 200;
const GRID_DIVISIONS = 16;
const JUNCTION_SPACING = BOARD_SIZE / GRID_DIVISIONS;
const HALF_BOARD = BOARD_SIZE / 2;
const ORB_COUNT = 20;
const PULSE_COUNT = 10;
interface Junction {
x: number;
z: number;
connections: number[];
}
interface Trace {
start: number;
end: number;
}
interface ParticleData {
pos: THREE.Vector3;
target: THREE.Vector3;
junctionIndex: number;
targetJunctionIndex: number;
progress: number;
speed: number;
type: 'orb' | 'pulse';
}
function seededRandom(seed: number): () => number {
let s = seed;
return () => {
s = (s * 9301 + 49297) % 233280;
return s / 233280;
};
}
function createGlowTexture(color: string): THREE.CanvasTexture {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d')!;
const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
gradient.addColorStop(0, color);
gradient.addColorStop(0.3, color.replace(')', ', 0.8)').replace('rgb', 'rgba'));
gradient.addColorStop(0.6, color.replace(')', ', 0.3)').replace('rgb', 'rgba'));
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 64, 64);
return new THREE.CanvasTexture(canvas);
}
function generateBoardData() {
const rand = seededRandom(42);
const junctions: Junction[] = [];
const indexMap: Map<string, number> = new Map();
for (let gx = 0; gx <= GRID_DIVISIONS; gx++) {
for (let gz = 0; gz <= GRID_DIVISIONS; gz++) {
const x = -HALF_BOARD + gx * JUNCTION_SPACING;
const z = -HALF_BOARD + gz * JUNCTION_SPACING;
const idx = junctions.length;
junctions.push({ x, z, connections: [] });
indexMap.set(`${gx},${gz}`, idx);
}
}
for (let gx = 0; gx <= GRID_DIVISIONS; gx++) {
for (let gz = 0; gz <= GRID_DIVISIONS; gz++) {
const idx = indexMap.get(`${gx},${gz}`);
if (idx === undefined) continue;
if (gx < GRID_DIVISIONS) {
const rightIdx = indexMap.get(`${gx + 1},${gz}`);
if (rightIdx !== undefined) {
junctions[idx].connections.push(rightIdx);
junctions[rightIdx].connections.push(idx);
}
}
if (gz < GRID_DIVISIONS) {
const downIdx = indexMap.get(`${gx},${gz + 1}`);
if (downIdx !== undefined) {
junctions[idx].connections.push(downIdx);
junctions[downIdx].connections.push(idx);
}
}
if (gx < GRID_DIVISIONS && gz < GRID_DIVISIONS && rand() > 0.85) {
const diagIdx = indexMap.get(`${gx + 1},${gz + 1}`);
if (diagIdx !== undefined) {
junctions[idx].connections.push(diagIdx);
junctions[diagIdx].connections.push(idx);
}
}
}
}
const traceSet = new Set<string>();
const traces: Trace[] = [];
for (const j of junctions) {
const jIdx = junctions.indexOf(j);
for (const c of j.connections) {
const key = j.x < junctions[c].x || (j.x === junctions[c].x && j.z <= junctions[c].z)
? `${jIdx},${c}`
: `${c},${jIdx}`;
if (!traceSet.has(key)) {
traceSet.add(key);
traces.push({ start: jIdx, end: c });
}
}
}
const chips: Array<{ x: number; z: number; width: number; depth: number }> = [];
const usedIndices = new Set<number>();
for (let i = 0; i < 22; i++) {
let attempts = 0;
while (attempts < 50) {
const idx = Math.floor(rand() * junctions.length);
if (!usedIndices.has(idx) && junctions[idx].connections.length >= 2) {
usedIndices.add(idx);
chips.push({
x: junctions[idx].x,
z: junctions[idx].z,
width: 5 + rand() * 7,
depth: 5 + rand() * 7,
});
break;
}
attempts++;
}
}
const capacitors: Array<{ x: number; z: number; radius: number; height: number }> = [];
for (let i = 0; i < 45; i++) {
const idx = Math.floor(rand() * junctions.length);
capacitors.push({
x: junctions[idx].x + (rand() - 0.5) * 3,
z: junctions[idx].z + (rand() - 0.5) * 3,
radius: 0.35 + rand() * 0.45,
height: 1.2 + rand() * 2.2,
});
}
const ics: Array<{ x: number; z: number; width: number; depth: number }> = [];
const icCandidates = junctions.filter(j => j.connections.length > 2);
for (let i = 0; i < Math.min(16, icCandidates.length); i++) {
const j = icCandidates[i];
ics.push({
x: j.x,
z: j.z,
width: 10 + rand() * 6,
depth: 6 + rand() * 4,
});
}
return { junctions, traces, chips, capacitors, ics };
}
function TraceLines({ traces, junctions }: { traces: Trace[]; junctions: Junction[] }) {
const geometry = useMemo(() => {
const positions: number[] = [];
for (const t of traces) {
const start = junctions[t.start];
const end = junctions[t.end];
positions.push(start.x, 0.1, start.z);
positions.push(end.x, 0.1, end.z);
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
return geo;
}, [traces, junctions]);
return (
<lineSegments geometry={geometry}>
<lineBasicMaterial color="#3d5a3a" />
</lineSegments>
);
}
function Chips({ chips }: { chips: Array<{ x: number; z: number; width: number; depth: number }> }) {
return (
<group>
{chips.map((chip, i) => (
<group key={i} position={[chip.x, 0, chip.z]}>
<mesh position={[0, 0.6, 0]} castShadow>
<boxGeometry args={[chip.width, 1.2, chip.depth]} />
<meshStandardMaterial color="#1c1c1c" roughness={0.4} metalness={0.5} />
</mesh>
<mesh position={[0, 1.25, 0]}>
<boxGeometry args={[chip.width * 0.4, 0.25, chip.depth * 0.15]} />
<meshStandardMaterial color="#3a3a3a" roughness={0.3} metalness={0.6} emissive="#0a1a0a" emissiveIntensity={0.2} />
</mesh>
{[-1, 1].map((side) =>
Array.from({ length: Math.floor(chip.width / 1.2) }).map((_, pi) => (
<mesh key={`pin-${side}-${pi}`} position={[side * (chip.width / 2 + 0.25), 0.25, -chip.depth / 2 + 1.0 + pi * 1.2]} castShadow>
<boxGeometry args={[0.25, 0.5, 0.25]} />
<meshStandardMaterial color="#909090" roughness={0.25} metalness={0.85} />
</mesh>
))
)}
{[-1, 1].map((side) =>
Array.from({ length: Math.floor(chip.depth / 1.2) }).map((_, pi) => (
<mesh key={`pin-z-${side}-${pi}`} position={[-chip.width / 2 + 1.0 + pi * 1.2, 0.25, side * (chip.depth / 2 + 0.25)]} castShadow>
<boxGeometry args={[0.25, 0.5, 0.25]} />
<meshStandardMaterial color="#909090" roughness={0.25} metalness={0.85} />
</mesh>
))
)}
</group>
))}
</group>
);
}
function Capacitors({ capacitors }: { capacitors: Array<{ x: number; z: number; radius: number; height: number }> }) {
return (
<group>
{capacitors.map((cap, i) => (
<mesh key={i} position={[cap.x, cap.height / 2, cap.z]} castShadow>
<cylinderGeometry args={[cap.radius, cap.radius, cap.height, 8]} />
<meshStandardMaterial color="#2d2d2d" roughness={0.35} metalness={0.5} />
</mesh>
))}
</group>
);
}
function ICs({ ics }: { ics: Array<{ x: number; z: number; width: number; depth: number }> }) {
return (
<group>
{ics.map((ic, i) => (
<group key={i} position={[ic.x, 0, ic.z]}>
<mesh position={[0, 0.8, 0]} castShadow>
<boxGeometry args={[ic.width, 1.6, ic.depth]} />
<meshStandardMaterial color="#181818" roughness={0.5} metalness={0.3} />
</mesh>
<mesh position={[0, 1.65, 0]}>
<boxGeometry args={[ic.width * 0.2, 0.15, ic.depth * 0.1]} />
<meshStandardMaterial color="#2a2a2a" roughness={0.4} metalness={0.5} />
</mesh>
</group>
))}
</group>
);
}
function ParticleSystem({ junctions }: { junctions: Junction[] }) {
const orbRefs = useRef<(THREE.Mesh | null)[]>([]);
const pulseRefs = useRef<(THREE.Mesh | null)[]>([]);
const particles = useRef<ParticleData[]>([]);
const initialized = useRef(false);
const glowTexture = useMemo(() => createGlowTexture('rgb(0, 255, 68)'), []);
const pulseGlowTexture = useMemo(() => createGlowTexture('rgb(0, 255, 255)'), []);
useEffect(() => {
if (initialized.current) return;
initialized.current = true;
const rand = seededRandom(Date.now());
const initial: ParticleData[] = [];
for (let i = 0; i < ORB_COUNT; i++) {
const idx = Math.floor(rand() * junctions.length);
const conn = junctions[idx].connections;
const target = conn.length > 0 ? conn[Math.floor(rand() * conn.length)] : idx;
initial.push({
pos: new THREE.Vector3(junctions[idx].x, 0.4, junctions[idx].z),
target: new THREE.Vector3(junctions[target].x, 0.4, junctions[target].z),
junctionIndex: idx,
targetJunctionIndex: target,
progress: rand(),
speed: 0.002 + rand() * 0.003,
type: 'orb',
});
}
for (let i = 0; i < PULSE_COUNT; i++) {
const idx = Math.floor(rand() * junctions.length);
const conn = junctions[idx].connections;
const target = conn.length > 0 ? conn[Math.floor(rand() * conn.length)] : idx;
initial.push({
pos: new THREE.Vector3(junctions[idx].x, 0.4, junctions[idx].z),
target: new THREE.Vector3(junctions[target].x, 0.4, junctions[target].z),
junctionIndex: idx,
targetJunctionIndex: target,
progress: rand(),
speed: 0.01 + rand() * 0.015,
type: 'pulse',
});
}
particles.current = initial;
}, [junctions]);
useFrame(() => {
const p = particles.current;
if (!p.length) return;
for (let i = 0; i < p.length; i++) {
const particle = p[i];
particle.progress += particle.speed;
if (particle.progress >= 1) {
const conn = junctions[particle.junctionIndex].connections;
if (conn.length > 0) {
particle.junctionIndex = particle.targetJunctionIndex;
const nextConn = junctions[particle.targetJunctionIndex].connections.filter(c => c !== particle.junctionIndex);
particle.targetJunctionIndex = nextConn.length > 0
? nextConn[Math.floor(Math.random() * nextConn.length)]
: conn[Math.floor(Math.random() * conn.length)];
particle.progress = 0;
particle.target.set(
junctions[particle.targetJunctionIndex].x,
0.4,
junctions[particle.targetJunctionIndex].z
);
} else {
particle.progress = 0;
}
}
const startJ = junctions[particle.junctionIndex];
particle.pos.x = startJ.x + (particle.target.x - startJ.x) * particle.progress;
particle.pos.z = startJ.z + (particle.target.z - startJ.z) * particle.progress;
particle.pos.y = 0.4 + Math.sin(particle.progress * Math.PI) * 0.6;
if (particle.type === 'orb') {
const mesh = orbRefs.current[i];
if (mesh) {
mesh.position.copy(particle.pos);
}
} else {
const mesh = pulseRefs.current[i - ORB_COUNT];
if (mesh) {
mesh.position.copy(particle.pos);
const scale = 1 - Math.abs(particle.progress - 0.5) * 2;
mesh.scale.setScalar(0.4 + scale * 0.6);
(mesh.material as THREE.MeshStandardMaterial).emissiveIntensity = 2 + scale * 4;
}
}
}
});
return (
<group>
{Array.from({ length: ORB_COUNT }).map((_, i) => (
<mesh
key={`orb-${i}`}
ref={(el) => { orbRefs.current[i] = el; }}
>
<planeGeometry args={[1.2, 1.2]} />
<meshBasicMaterial
map={glowTexture}
transparent
opacity={0.95}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</mesh>
))}
{Array.from({ length: PULSE_COUNT }).map((_, i) => (
<mesh
key={`pulse-${i}`}
ref={(el) => { pulseRefs.current[i] = el; }}
rotation={[-Math.PI / 2, 0, 0]}
>
<planeGeometry args={[0.8, 0.8]} />
<meshBasicMaterial
map={pulseGlowTexture}
transparent
opacity={0.95}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</mesh>
))}
</group>
);
}
function CameraDrift() {
const { camera } = useThree();
const timeRef = useRef(0);
useFrame((_, delta) => {
timeRef.current += delta;
const t = timeRef.current;
camera.position.x = Math.sin(t * 0.05) * 3;
camera.position.y = 80 + Math.sin(t * 0.07) * 2.5;
camera.position.z = Math.cos(t * 0.04) * 5;
camera.lookAt(0, 0, 0);
});
return null;
}
function Scene() {
const boardData = useMemo(() => generateBoardData(), []);
return (
<>
<ambientLight intensity={0.3} />
<directionalLight position={[60, 120, 60]} intensity={0.7} castShadow />
<directionalLight position={[-40, 80, -40]} intensity={0.4} />
<pointLight position={[0, 40, 0]} intensity={0.6} color="#00ff44" distance={120} />
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]} receiveShadow>
<planeGeometry args={[BOARD_SIZE, BOARD_SIZE]} />
<meshStandardMaterial color="#080808" roughness={0.85} metalness={0.15} />
</mesh>
<TraceLines traces={boardData.traces} junctions={boardData.junctions} />
<Chips chips={boardData.chips} />
<Capacitors capacitors={boardData.capacitors} />
<ICs ics={boardData.ics} />
<ParticleSystem junctions={boardData.junctions} />
<CameraDrift />
</>
);
}
export default function GadgetGrid() {
return (
<div className="absolute inset-0">
<Canvas
camera={{ position: [0, 80, 0], fov: 45, near: 0.1, far: 500 }}
gl={{ antialias: true, alpha: true }}
dpr={[1, 1.5]}
>
<Scene />
</Canvas>
</div>
);
}

View File

@ -0,0 +1,131 @@
export default function GadgetSection() {
return (
<section id="gadget" className="py-20 px-6">
<div className="max-w-6xl mx-auto">
{/* Section Header */}
<div className="text-center mb-16">
<span className="text-xs font-mono tracking-widest text-brand mb-3 block">
PRODUCT
</span>
<h2 className="text-3xl md:text-4xl font-mono font-bold tracking-tight text-text-primary">
Gadget: AI Where You Work
</h2>
<p className="mt-4 text-text-secondary max-w-2xl mx-auto">
An agentic sidebar for your browser and a personal knowledge engine.
Gadget understands context, performs actions, and keeps your information
connected and actionable.
</p>
</div>
{/* Two Sub-Sections */}
<div className="grid md:grid-cols-2 gap-6 mb-8">
{/* Browser Extension */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-8 rounded bg-bg-tertiary border border-border-default flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="3" width="14" height="10" rx="1.5" stroke="#c20600" strokeWidth="1.5" />
<path d="M1 6h14" stroke="#c20600" strokeWidth="1" />
<circle cx="3" cy="4.5" r="0.5" fill="#c20600" />
<circle cx="5" cy="4.5" r="0.5" fill="#c20600" />
</svg>
</div>
<h3 className="font-mono text-base font-bold tracking-wide text-text-primary">
BROWSER EXTENSION
</h3>
</div>
<p className="text-sm text-text-secondary leading-relaxed mb-4">
An agentic sidebar integrated into your browsing. Gadget understands
the page you're on and can perform actions on your behalf research,
form-filling, summarization, and more. It's AI that works where you
already are.
</p>
<div className="space-y-2 text-sm text-text-muted">
<div className="flex items-center gap-2">
<span className="text-brand font-mono text-xs"></span>
<span>Context-aware: understands the page you're on</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand font-mono text-xs"></span>
<span>Agentic: performs actions on your behalf</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand font-mono text-xs"></span>
<span>Works in Chrome, Firefox, and Edge</span>
</div>
</div>
{/* Installation Instructions */}
<div className="mt-5 pt-4 border-t border-border-default">
<h4 className="font-mono text-xs font-bold tracking-wide text-text-muted mb-3">
INSTALLATION
</h4>
<div className="space-y-3 text-xs text-text-muted font-mono">
<div>
<span className="text-text-secondary">Chrome / Chromium:</span>
<p className="mt-1">Settings Extensions Enable Developer Mode Load Unpacked Select extension directory</p>
</div>
<div>
<span className="text-text-secondary">Firefox:</span>
<p className="mt-1">about:debugging This Firefox Load Temporary Add-on Select manifest.json from extension directory</p>
</div>
<div>
<span className="text-text-secondary">Microsoft Edge:</span>
<p className="mt-1">edge://extensions → Enable Developer Mode → Load Unpacked → Select extension directory</p>
</div>
</div>
</div>
</div>
{/* Web App */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-8 rounded bg-bg-tertiary border border-border-default flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M2 4h12M2 8h8M2 12h10" stroke="#c20600" strokeWidth="1.5" strokeLinecap="round" />
<circle cx="14" cy="9" r="2" stroke="#c20600" strokeWidth="1.5" />
</svg>
</div>
<h3 className="font-mono text-base font-bold tracking-wide text-text-primary">
WEB APP
</h3>
</div>
<p className="text-sm text-text-secondary leading-relaxed mb-4">
Your personal knowledge engine. Chat with your information, meetings,
documents, and notes. Persistent, searchable, and actionable. Gadget
connects what you know to what you need.
</p>
<div className="space-y-2 text-sm text-text-muted">
<div className="flex items-center gap-2">
<span className="text-brand font-mono text-xs"></span>
<span>Chat with your documents and meetings</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand font-mono text-xs"></span>
<span>Persistent, searchable knowledge base</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand font-mono text-xs"></span>
<span>Actions, not just answers</span>
</div>
</div>
{/* "Built with Gadget Code" badge */}
<div className="mt-6 pt-4 border-t border-border-default">
<div className="inline-flex items-center gap-2 px-3 py-1.5 bg-bg-tertiary border border-border-default rounded">
<div className="w-1.5 h-1.5 rounded-full bg-green-glow animate-pulse-glow" />
<span className="text-xs font-mono tracking-wide text-text-muted">
BUILT WITH GADGET CODE
</span>
</div>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,137 @@
export default function GetStartedSection() {
return (
<section id="get-started" className="py-20 px-6">
<div className="max-w-6xl mx-auto">
{/* Section Header */}
<div className="text-center mb-16">
<span className="text-xs font-mono tracking-widest text-brand mb-3 block">
ACCESS
</span>
<h2 className="text-3xl md:text-4xl font-mono font-bold tracking-tight text-text-primary">
Get Started
</h2>
</div>
{/* Two Columns */}
<div className="grid md:grid-cols-2 gap-6">
{/* Gadget Code (Free) */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-6">
{/* Status header */}
<div className="flex items-center gap-2 mb-4">
<div className="w-1.5 h-1.5 rounded-full bg-green-glow animate-pulse-glow" />
<span className="text-xs font-mono tracking-widest text-green-glow/80">
FREE & OPEN SOURCE
</span>
</div>
<h3 className="text-2xl font-mono font-bold text-text-primary mb-2">
Gadget Code
</h3>
<p className="text-text-secondary text-sm mb-1">
Apache 2.0 License
</p>
<p className="text-text-muted text-xs mb-6">
No account. No payment. No telemetry. No vendor lock-in.
</p>
{/* Requirements */}
<div className="mb-6 text-xs text-text-muted font-mono space-y-1">
<div className="flex items-center gap-2">
<span className="text-brand"></span>
<span>Self-hosted on your server</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand"></span>
<span>MongoDB + Redis + Node.js 22+</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand"></span>
<span>Deploy in minutes</span>
</div>
</div>
{/* CTAs */}
<a
href="https://git.g4dge7.com"
target="_blank"
rel="noopener noreferrer"
className="block w-full text-center px-6 py-3 text-sm font-mono tracking-wider border border-brand bg-brand hover:bg-brand-light text-white rounded transition-all mb-3"
>
GET THE SOURCE CODE
</a>
<a
href="https://g4dge7.com/docs/install/"
className="block w-full text-center px-6 py-3 text-sm font-mono tracking-wider border border-border-default hover:border-border-highlight text-text-secondary hover:text-text-primary rounded transition-all"
>
INSTALLATION GUIDE
</a>
</div>
{/* Gadget (Subscription) */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-6 relative overflow-hidden">
{/* Animated accent line */}
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand to-transparent animate-pulse-glow" />
{/* Status header */}
<div className="flex items-center gap-2 mb-4">
<div className="w-1.5 h-1.5 rounded-full bg-cyan-glow animate-pulse-glow" />
<span className="text-xs font-mono tracking-widest text-cyan-glow/80">
SUBSCRIPTION
</span>
</div>
<h3 className="text-2xl font-mono font-bold text-text-primary mb-2">
Gadget
</h3>
<p className="text-text-secondary text-sm mb-1">
AI in Your Browser
</p>
<p className="text-text-muted text-xs mb-6">
Subscription-based. Cancel anytime. Pay-as-you-go metered use also available.
</p>
{/* Features */}
<div className="mb-6 text-xs text-text-muted font-mono space-y-1">
<div className="flex items-center gap-2">
<span className="text-brand"></span>
<span>Agentic browser sidebar</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand"></span>
<span>Personal knowledge engine web app</span>
</div>
<div className="flex items-center gap-2">
<span className="text-brand"></span>
<span>Chrome, Firefox, and Edge support</span>
</div>
</div>
{/* CTAs */}
<a
href="#subscribe"
className="block w-full text-center px-6 py-3 text-sm font-mono tracking-wider border border-cyan-glow/50 bg-cyan-glow/10 hover:bg-cyan-glow/20 text-cyan-glow rounded transition-all mb-3"
>
SUBSCRIBE NOW
</a>
<a
href="#gadget"
className="block w-full text-center px-6 py-3 text-sm font-mono tracking-wider border border-border-default hover:border-border-highlight text-text-secondary hover:text-text-primary rounded transition-all"
>
LEARN MORE
</a>
{/* "Built with" badge */}
<div className="mt-4 flex items-center justify-center gap-2">
<div className="w-1 h-1 rounded-full bg-green-glow" />
<span className="text-xs font-mono text-text-muted tracking-wide">
BUILT WITH GADGET CODE
</span>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,132 @@
import { useState, useEffect } from 'react';
interface HeaderProps {
scrolled: boolean;
}
const NAV_LINKS = [
{ label: 'Why Gadget Code', href: '#why' },
{ label: 'Gadget Code', href: '#gadget-code' },
{ label: 'Gadget', href: '#gadget' },
{ label: 'Get Started', href: '#get-started' },
];
export default function Header({ scrolled }: HeaderProps) {
const [mobileOpen, setMobileOpen] = useState(false);
// Close mobile menu on resize
useEffect(() => {
const onResize = () => {
if (window.innerWidth >= 768) setMobileOpen(false);
};
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return (
<header
className={`fixed top-0 left-0 right-0 z-50 h-header flex items-center border-b transition-all duration-300 ${
scrolled
? 'bg-bg-secondary/90 backdrop-blur-md border-border-default'
: 'bg-transparent border-transparent'
}`}
>
<div className="w-full max-w-7xl mx-auto px-6 flex items-center justify-between">
{/* Logo / Brand */}
<a href="#" className="flex items-center gap-3 group">
<img
src="/favicon.png"
alt="Gadget Code"
className="w-6 h-6"
/>
<span className="font-mono text-sm font-bold tracking-wider text-text-primary group-hover:text-brand transition-colors">
GADGET CODE
</span>
</a>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-6">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className="text-xs font-mono tracking-wide text-text-secondary hover:text-text-primary transition-colors"
>
{link.label.toUpperCase()}
</a>
))}
</nav>
{/* Desktop CTAs */}
<div className="hidden md:flex items-center gap-3">
<a
href="https://git.g4dge7.com"
target="_blank"
rel="noopener noreferrer"
className="px-3 py-1.5 text-xs font-mono tracking-wide border border-border-default rounded hover:border-border-highlight hover:text-text-primary text-text-secondary transition-all"
>
GET SOURCE
</a>
<a
href="#get-started"
className="px-3 py-1.5 text-xs font-mono tracking-wide bg-brand hover:bg-brand-light text-white rounded transition-colors"
>
SUBSCRIBE
</a>
</div>
{/* Mobile menu toggle */}
<button
className="md:hidden p-2 text-text-secondary hover:text-text-primary"
onClick={() => setMobileOpen(!mobileOpen)}
aria-label="Toggle menu"
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
{mobileOpen ? (
<path d="M5 5l10 10M15 5L5 15" stroke="currentColor" strokeWidth="1.5" />
) : (
<>
<path d="M3 5h14M3 10h14M3 15h14" stroke="currentColor" strokeWidth="1.5" />
</>
)}
</svg>
</button>
</div>
{/* Mobile Navigation */}
{mobileOpen && (
<div className="md:hidden absolute top-header left-0 right-0 bg-bg-secondary/95 backdrop-blur-md border-b border-border-default">
<nav className="flex flex-col p-4 gap-3">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
onClick={() => setMobileOpen(false)}
className="text-xs font-mono tracking-wide text-text-secondary hover:text-text-primary transition-colors py-1"
>
{link.label.toUpperCase()}
</a>
))}
<div className="flex gap-3 pt-2 border-t border-border-default">
<a
href="https://git.g4dge7.com"
target="_blank"
rel="noopener noreferrer"
className="px-3 py-1.5 text-xs font-mono tracking-wide border border-border-default rounded text-text-secondary hover:text-text-primary"
>
GET SOURCE
</a>
<a
href="#get-started"
onClick={() => setMobileOpen(false)}
className="px-3 py-1.5 text-xs font-mono tracking-wide bg-brand text-white rounded"
>
SUBSCRIBE
</a>
</div>
</nav>
</div>
)}
</header>
);
}

View File

@ -0,0 +1,106 @@
import { useState, useEffect, useRef } from 'react';
import GadgetGrid from './GadgetGrid';
import { isWebGLAvailable } from '../lib/webgl-detect';
export default function Hero() {
const [webgl, setWebgl] = useState(true);
const [visible, setVisible] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setWebgl(isWebGLAvailable());
// Trigger entrance animation after mount
requestAnimationFrame(() => setVisible(true));
}, []);
return (
<section
ref={containerRef}
id="hero"
className="relative min-h-screen flex items-center justify-center overflow-hidden"
>
{/* ThreeJS Background */}
{webgl ? (
<GadgetGrid />
) : (
<div
className="absolute inset-0"
style={{
background: `radial-gradient(ellipse at 50% 50%, #1a0500 0%, #0a0a0a 70%)`,
}}
/>
)}
{/* Scan line overlay for fallback and subtle depth */}
{!webgl && (
<div
className="absolute inset-0 opacity-5 pointer-events-none"
style={{
backgroundImage:
'repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,68,0.03) 2px, rgba(0,255,68,0.03) 4px)',
}}
/>
)}
{/* Dark overlay for text readability */}
<div className="absolute inset-0 bg-bg-primary/50 pointer-events-none" />
{/* Content */}
<div
className={`relative z-10 text-center px-6 max-w-4xl mx-auto transition-all duration-1000 ${
visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'
}`}
>
{/* Status indicator */}
<div className="flex items-center justify-center gap-2 mb-6">
<div className="w-1.5 h-1.5 rounded-full bg-green-glow status-indicator" />
<span className="text-xs font-mono tracking-widest text-green-glow/80">
SYSTEMS ONLINE
</span>
</div>
{/* App icon */}
<img
src="/web-app.png"
alt="Gadget Code"
className="w-16 h-16 mx-auto mb-6 rounded-lg shadow-lg shadow-brand/20"
/>
{/* Tagline */}
<h1 className="text-5xl md:text-6xl font-mono font-bold tracking-tight text-text-primary mb-4">
<span className="text-brand">Self-Hosted</span> Agentic Engineering
</h1>
{/* Subtext */}
<p className="text-lg md:text-xl text-text-secondary max-w-2xl mx-auto mb-10 leading-relaxed">
The platform where AI agents and humans build software together
on your infrastructure, under your control.
</p>
{/* CTAs */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<a
href="#gadget-code"
className="px-8 py-3 text-sm font-mono tracking-wider border border-brand bg-brand hover:bg-brand-light text-white rounded transition-all shadow-lg shadow-brand/20"
>
GET GADGET CODE
</a>
<a
href="#gadget"
className="px-8 py-3 text-sm font-mono tracking-wider border border-border-default hover:border-border-highlight text-text-secondary hover:text-text-primary rounded transition-all"
>
GET GADGET
</a>
</div>
{/* Scroll indicator */}
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-1 opacity-40">
<span className="text-xs font-mono tracking-widest text-text-muted">SCROLL</span>
<svg width="12" height="20" viewBox="0 0 12 20" fill="none">
<path d="M6 2v16M1 13l5 5 5-5" stroke="currentColor" strokeWidth="1.5" />
</svg>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,112 @@
export default function ProblemSection() {
return (
<section id="why" className="py-20 px-6">
<div className="max-w-6xl mx-auto">
{/* Section Header */}
<div className="text-center mb-16">
<span className="text-xs font-mono tracking-widest text-brand mb-3 block">
ASSESSMENT
</span>
<h2 className="text-3xl md:text-4xl font-mono font-bold tracking-tight text-text-primary">
The Platforms Are Failing
</h2>
<p className="mt-4 text-text-secondary max-w-2xl mx-auto">
The tools you depend on to build software are breaking and the
companies behind them chose AI hype over platform reliability.
</p>
</div>
{/* Problem Cards */}
<div className="grid md:grid-cols-2 gap-6">
{/* GitHub */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-6 border-l-2 border-l-brand">
<div className="flex items-center gap-3 mb-4">
<div className="w-2 h-2 rounded-full bg-red-500 status-indicator" />
<h3 className="font-mono text-lg font-bold text-text-primary">
The Platform You Can't Trust
</h3>
</div>
<p className="text-text-secondary leading-relaxed mb-4">
GitHub's uptime has plummeted to <span className="text-brand font-mono font-bold">~90%</span>.
A silent merge queue bug deleted committed code across 2,092 pull requests.
CVE-2026-3854 let anyone execute arbitrary code on GitHub's servers via a
standard <code className="font-mono text-xs bg-bg-tertiary px-1.5 py-0.5 rounded border border-border-default text-yellow-400">git push</code>.
</p>
<ul className="space-y-2 text-sm text-text-muted mb-5">
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>12-hour cascading failure, 5 separate outages (Feb 9)</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Redis misconfiguration: 95% of Actions workflows delayed</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Codespaces 90% auth failure across EU, Asia, AU</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Elasticsearch collapse: global search and PRs down for hours</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Ghostty, Zig, tldraw, curl tier-one projects have fled</span>
</li>
</ul>
<p className="text-xs font-mono text-text-muted italic">
Your source code deserves better.
</p>
</div>
{/* VS Code */}
<div className="bg-bg-secondary border border-border-default rounded-lg p-6 border-l-2 border-l-brand">
<div className="flex items-center gap-3 mb-4">
<div className="w-2 h-2 rounded-full bg-red-500 status-indicator" />
<h3 className="font-mono text-lg font-bold text-text-primary">
The IDE That Forgot Developers
</h3>
</div>
<p className="text-text-secondary leading-relaxed mb-4">
VS Code is leaking <span className="text-brand font-mono font-bold">40GB+</span> of RAM on
idle. Microsoft forced <span className="font-mono text-xs bg-bg-tertiary px-1.5 py-0.5 rounded border border-border-default text-yellow-400">"Co-authored-by: Copilot"</span> into
your commits even for code you wrote yourself. RCE vulnerabilities
hit 125 million extension installs.
</p>
<ul className="space-y-2 text-sm text-text-muted mb-5">
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Copilot metadata hijack: forced AI attribution on human code</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Language server leak: 40GB+ RAM on idle configurations</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>Claude Code extension: 23.2GB RAM out of the box</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>March 2026 marketplace update broke core extension abstractions</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand mt-1 font-mono text-xs"></span>
<span>125M-install RCE: Live Server, Code Runner, and more</span>
</li>
</ul>
<p className="text-xs font-mono text-text-muted italic">
The IDE chose AI hype over your workflow.
</p>
</div>
</div>
</div>
</section>
);
}

154
site/src/index.css Normal file
View File

@ -0,0 +1,154 @@
@import "tailwindcss";
@theme {
--color-brand: #c20600;
--color-brand-light: #e01000;
--color-bg-primary: #0a0a0a;
--color-bg-secondary: #121212;
--color-bg-tertiary: #1a1a1a;
--color-bg-elevated: #202020;
--color-text-primary: #d4d4d4;
--color-text-secondary: #a3a3a3;
--color-text-muted: #737373;
--color-border-subtle: #1a1a1a;
--color-border-default: #2a2a2a;
--color-border-highlight: #3a3a3a;
--color-green-glow: #00ff44;
--color-cyan-glow: #00ffff;
--font-mono: "Fira Code", "Courier New", Courier, monospace;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--spacing-header: 48px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 64px;
}
body {
margin: 0;
font-family: var(--font-sans);
background-color: var(--color-bg-primary);
color: var(--color-text-primary);
line-height: 1.5;
font-size: 14px;
overflow-x: hidden;
}
#root {
min-height: 100vh;
display: flex;
flex-direction: column;
}
a {
text-decoration: none;
color: inherit;
}
button {
cursor: pointer;
font-family: inherit;
font-size: inherit;
}
/* ── Scrollbar ─────────────────────────────────────────────── */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--color-border-default);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-border-highlight);
}
/* ── Animations ─────────────────────────────────────────────── */
@keyframes pulse-glow {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
@keyframes scan-line {
0% { transform: translateY(-100%); }
100% { transform: translateY(100vh); }
}
@keyframes data-stream {
0% { background-position: 0% 0%; }
100% { background-position: 0% 100%; }
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes status-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.animate-pulse-glow {
animation: pulse-glow 3s ease-in-out infinite;
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out forwards;
}
.status-indicator {
animation: status-blink 2s ease-in-out infinite;
}
/* Reduce motion for accessibility */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* ── Section Dividers ──────────────────────────────────────── */
.section-divider {
height: 1px;
background: linear-gradient(
90deg,
transparent 0%,
var(--color-border-default) 20%,
var(--color-brand) 50%,
var(--color-border-default) 80%,
transparent 100%
);
}
/* ── Feature Card Hover ────────────────────────────────────── */
.feature-card {
transition: border-color 0.2s ease, background-color 0.2s ease;
}
.feature-card:hover {
border-color: var(--color-border-highlight);
background-color: var(--color-bg-tertiary);
}

View File

@ -0,0 +1,13 @@
export function isWebGLAvailable(): boolean {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
return gl !== null && gl !== undefined;
} catch {
return false;
}
}
export function prefersReducedMotion(): boolean {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}

10
site/src/main.tsx Normal file
View File

@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

61
site/tailwind.config.js Normal file
View File

@ -0,0 +1,61 @@
import type { Config } from 'tailwindcss';
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
brand: '#c20600',
'bg-primary': '#0a0a0a',
'bg-secondary': '#121212',
'bg-tertiary': '#1a1a1a',
'bg-elevated': '#202020',
'text-primary': '#d4d4d4',
'text-secondary': '#a3a3a3',
'text-muted': '#737373',
'border-subtle': '#1a1a1a',
'border-default': '#2a2a2a',
'border-highlight': '#3a3a3a',
},
fontFamily: {
mono: ['Courier New', 'Courier', 'monospace'],
sans: ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
},
spacing: {
'header': '48px',
'status': '32px',
},
animation: {
'pulse-slow': 'pulse-slow 3s ease-in-out infinite',
'fade-in': 'fade-in 0.6s ease-out forwards',
'fade-in-up': 'fade-in-up 0.6s ease-out forwards',
'glow': 'glow 2s ease-in-out infinite',
'data-flow': 'data-flow 1.5s ease-in-out infinite',
},
keyframes: {
'pulse-slow': {
'0%, 100%': { opacity: '0.4' },
'50%': { opacity: '1' },
},
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
'fade-in-up': {
'0%': { opacity: '0', transform: 'translateY(12px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
'glow': {
'0%, 100%': { boxShadow: '0 0 4px #c2060060' },
'50%': { boxShadow: '0 0 12px #c20600a0' },
},
'data-flow': {
'0%': { transform: 'translateY(-4px)', opacity: '0' },
'50%': { opacity: '1' },
'100%': { transform: 'translateY(4px)', opacity: '0' },
},
},
},
},
plugins: [],
} satisfies Config;

17
site/tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"esModuleInterop": true
},
"include": ["src"]
}

31
site/vite.config.ts Normal file
View File

@ -0,0 +1,31 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
root: '.',
publicDir: 'public',
server: {
port: 5180,
host: '0.0.0.0',
},
build: {
outDir: 'dist',
emptyOutDir: true,
rolldownOptions: {
output: {
codeSplitting: {
minSize: 20000,
groups: [
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 10,
maxSize: 250000,
},
],
},
},
},
},
});