created
This commit is contained in:
parent
35fe099dd1
commit
8fd6e06f19
2
site/.gitignore
vendored
Normal file
2
site/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
26
site/index.html
Normal file
26
site/index.html
Normal 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
1648
site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
site/package.json
Normal file
31
site/package.json
Normal 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
5
site/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
},
|
||||||
|
};
|
||||||
BIN
site/public/favicon.png
Normal file
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
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
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
39
site/src/App.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
site/src/components/BuiltWithSection.tsx
Normal file
63
site/src/components/BuiltWithSection.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
site/src/components/Footer.tsx
Normal file
53
site/src/components/Footer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
102
site/src/components/GadgetCodeSection.tsx
Normal file
102
site/src/components/GadgetCodeSection.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
444
site/src/components/GadgetGrid.tsx
Normal file
444
site/src/components/GadgetGrid.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
131
site/src/components/GadgetSection.tsx
Normal file
131
site/src/components/GadgetSection.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
137
site/src/components/GetStartedSection.tsx
Normal file
137
site/src/components/GetStartedSection.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
132
site/src/components/Header.tsx
Normal file
132
site/src/components/Header.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
106
site/src/components/Hero.tsx
Normal file
106
site/src/components/Hero.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
site/src/components/ProblemSection.tsx
Normal file
112
site/src/components/ProblemSection.tsx
Normal 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
154
site/src/index.css
Normal 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);
|
||||||
|
}
|
||||||
13
site/src/lib/webgl-detect.ts
Normal file
13
site/src/lib/webgl-detect.ts
Normal 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
10
site/src/main.tsx
Normal 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
61
site/tailwind.config.js
Normal 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
17
site/tsconfig.json
Normal 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
31
site/vite.config.ts
Normal 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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user