modified: .replit

This commit is contained in:
MrPiglr 2025-12-05 01:06:47 -07:00
parent bce04f18cd
commit 82de4d9b41
17 changed files with 4918 additions and 430 deletions

View file

@ -52,10 +52,6 @@ externalPort = 80
localPort = 8044 localPort = 8044
externalPort = 3003 externalPort = 3003
[[ports]]
localPort = 36771
externalPort = 3002
[[ports]] [[ports]]
localPort = 38557 localPort = 38557
externalPort = 3000 externalPort = 3000

151
README.md Normal file
View file

@ -0,0 +1,151 @@
# AeThex Forge - Local Development Setup
## Quick Start Guide
This guide will help you set up and run the AeThex platform locally on your machine.
## Prerequisites
1. **Node.js** (v18 or higher)
- Download from: https://nodejs.org/
- This will also install npm (Node Package Manager)
2. **Git** (optional, if you want to clone updates)
- Download from: https://git-scm.com/
## Installation Steps
### 1. Install Node.js
- Visit https://nodejs.org/ and download the LTS version
- Run the installer and follow the setup wizard
- Restart your terminal/PowerShell after installation
### 2. Verify Installation
Open PowerShell or Command Prompt and run:
```powershell
node --version
npm --version
```
You should see version numbers (e.g., v20.x.x and 10.x.x)
### 3. Install Project Dependencies
Navigate to the project folder and install dependencies:
```powershell
cd C:\Users\PCOEM\Downloads\aethex-forge\aethex-forge
npm install
```
This may take a few minutes as it downloads all required packages.
### 4. Set Up Environment Variables
Create a `.env` file in the root directory (`aethex-forge` folder) with the following variables:
**Minimum Required (to run the app):**
```env
# Supabase Configuration (Required)
VITE_SUPABASE_URL=your_supabase_url_here
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
SUPABASE_SERVICE_ROLE=your_service_role_key_here
SUPABASE_URL=your_supabase_url_here
# API Base URL
VITE_API_BASE=http://localhost:5000
```
**Optional (for full functionality):**
```env
# Discord Integration
DISCORD_CLIENT_ID=your_discord_client_id
DISCORD_CLIENT_SECRET=your_discord_client_secret
DISCORD_BOT_TOKEN=your_discord_bot_token
DISCORD_PUBLIC_KEY=your_discord_public_key
DISCORD_REDIRECT_URI=http://localhost:5000/api/discord/oauth/callback
# Foundation OAuth
VITE_FOUNDATION_URL=https://aethex.foundation
FOUNDATION_OAUTH_CLIENT_ID=your_foundation_client_id
FOUNDATION_OAUTH_CLIENT_SECRET=your_foundation_client_secret
# Email Service (SMTP)
SMTP_HOST=your_smtp_host
SMTP_PORT=587
SMTP_USER=your_smtp_user
SMTP_PASSWORD=your_smtp_password
# Other Services
VITE_GHOST_API_URL=your_ghost_api_url
GHOST_ADMIN_API_KEY=your_ghost_admin_key
```
**Note:** You can start with just the Supabase variables to get the app running. Other features will work once you add their respective credentials.
### 5. Run the Development Server
```powershell
npm run dev
```
The application will start on **http://localhost:5000**
Open your browser and navigate to that URL to view the application.
## Available Commands
- `npm run dev` - Start development server (port 5000)
- `npm run build` - Build for production
- `npm start` - Start production server
- `npm run typecheck` - Check TypeScript types
- `npm test` - Run tests
## Project Structure
```
aethex-forge/
├── client/ # React frontend (pages, components)
├── server/ # Express backend API
├── api/ # API route handlers
├── shared/ # Shared types between client/server
├── discord-bot/ # Discord bot integration
└── supabase/ # Database migrations
```
## Getting Supabase Credentials
If you don't have Supabase credentials yet:
1. Go to https://supabase.com/
2. Create a free account
3. Create a new project
4. Go to Project Settings → API
5. Copy:
- Project URL → `VITE_SUPABASE_URL` and `SUPABASE_URL`
- `anon` `public` key → `VITE_SUPABASE_ANON_KEY`
- `service_role` `secret` key → `SUPABASE_SERVICE_ROLE`
## Troubleshooting
### Port Already in Use
If port 5000 is already in use, you can change it in `vite.config.ts`:
```typescript
server: {
port: 5001, // Change to any available port
}
```
### Module Not Found Errors
Try deleting `node_modules` and `package-lock.json`, then run `npm install` again:
```powershell
Remove-Item -Recurse -Force node_modules
Remove-Item package-lock.json
npm install
```
### Environment Variables Not Loading
- Make sure `.env` file is in the root `aethex-forge` directory
- Restart the dev server after adding new environment variables
- Variables starting with `VITE_` are exposed to the client
## Need Help?
- Check the `docs/` folder for detailed documentation
- Review `AGENTS.md` for architecture details
- See `replit.md` for deployment information

42
aethex-forge/.gitignore vendored Normal file
View file

@ -0,0 +1,42 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Testing
coverage/
.nyc_output
# Production
dist/
build/
*.local
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS files
Thumbs.db

View file

@ -164,6 +164,7 @@ import StaffLearningPortal from "./pages/staff/StaffLearningPortal";
import StaffPerformanceReviews from "./pages/staff/StaffPerformanceReviews"; import StaffPerformanceReviews from "./pages/staff/StaffPerformanceReviews";
import StaffProjectTracking from "./pages/staff/StaffProjectTracking"; import StaffProjectTracking from "./pages/staff/StaffProjectTracking";
import StaffTeamHandbook from "./pages/staff/StaffTeamHandbook"; import StaffTeamHandbook from "./pages/staff/StaffTeamHandbook";
import Overlay from "./pages/Overlay";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@ -184,6 +185,7 @@ const App = () => (
<Routes> <Routes>
{/* Subdomain Passport (aethex.me and aethex.space) handles its own redirect if not a subdomain */} {/* Subdomain Passport (aethex.me and aethex.space) handles its own redirect if not a subdomain */}
<Route path="/" element={<SubdomainPassport />} /> <Route path="/" element={<SubdomainPassport />} />
<Route path="/overlay" element={<Overlay />} />
<Route path="/onboarding" element={<Onboarding />} /> <Route path="/onboarding" element={<Onboarding />} />
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard" element={<Dashboard />} />
<Route <Route

View file

@ -0,0 +1,21 @@
import TitleBar from "./TitleBar";
import { ReactNode } from "react";
export default function DesktopShell({ children }: { children: ReactNode }) {
return (
<div
style={{
width: "100vw",
height: "100vh",
background: "#030712",
color: "#e5e7eb",
display: "flex",
flexDirection: "column",
}}
>
<TitleBar />
<div style={{ flex: 1, overflow: "hidden" }}>{children}</div>
</div>
);
}

308
client/components/Scene.tsx Normal file
View file

@ -0,0 +1,308 @@
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import { Grid, OrbitControls, Text } from "@react-three/drei";
import { MathUtils, Vector3 } from "three";
import React, { useMemo, useRef, useState } from "react";
import { motion } from "framer-motion";
import { useNavigate } from "react-router-dom";
type Gateway = {
label: string;
color: string;
route: string;
angle: number; // radians
};
const gateways: Gateway[] = [
{
label: "NEXUS",
color: "#a855f7",
route: "/dashboard/nexus",
angle: MathUtils.degToRad(-50),
},
{
label: "GAMEFORGE",
color: "#22c55e",
route: "/gameforge",
angle: MathUtils.degToRad(-20),
},
{
label: "FOUNDATION",
color: "#ef4444",
route: "/foundation",
angle: MathUtils.degToRad(0),
},
{
label: "LABS",
color: "#eab308",
route: "/dashboard/labs",
angle: MathUtils.degToRad(20),
},
{
label: "CORP",
color: "#3b82f6",
route: "/corp",
angle: MathUtils.degToRad(50),
},
];
function CoreCube() {
const ref = useRef<THREE.Mesh>(null);
useFrame((_, delta) => {
if (!ref.current) return;
ref.current.rotation.x += delta * 0.45;
ref.current.rotation.y += delta * 0.65;
});
return (
<mesh ref={ref}>
<boxGeometry args={[1.4, 1.4, 1.4]} />
<meshStandardMaterial
emissive="#38bdf8"
emissiveIntensity={1.5}
color="#0ea5e9"
metalness={0.6}
roughness={0.2}
/>
</mesh>
);
}
function GatewayMesh({
gateway,
onHover,
onClick,
isActive,
}: {
gateway: Gateway;
onHover: (label: string | null) => void;
onClick: (gw: Gateway) => void;
isActive: boolean;
}) {
const ref = useRef<THREE.Mesh>(null);
const glow = useRef<THREE.Mesh>(null);
const position = useMemo(() => {
const radius = 6;
return new Vector3(
Math.cos(gateway.angle) * radius,
1.5,
Math.sin(gateway.angle) * radius,
);
}, [gateway.angle]);
useFrame((_, delta) => {
if (ref.current) {
const targetScale = isActive ? 1.22 : 1;
ref.current.scale.lerp(
new Vector3(targetScale, targetScale, targetScale),
6 * delta,
);
}
if (glow.current) {
glow.current.rotation.y += delta * 0.8;
}
});
return (
<group position={position}>
<mesh
ref={ref}
onPointerOver={() => onHover(gateway.label)}
onPointerOut={() => onHover(null)}
onClick={() => onClick(gateway)}
>
<torusKnotGeometry args={[0.5, 0.15, 120, 16]} />
<meshStandardMaterial
color={gateway.color}
emissive={gateway.color}
emissiveIntensity={isActive ? 2.4 : 1.2}
roughness={0.25}
metalness={0.7}
/>
</mesh>
<mesh ref={glow} rotation={[Math.PI / 2, 0, 0]}>
<ringGeometry args={[0.9, 1.2, 64]} />
<meshBasicMaterial
color={gateway.color}
opacity={0.35}
transparent
side={2}
/>
</mesh>
<Text
position={[0, 1.5, 0]}
fontSize={0.6}
color={gateway.color}
anchorX="center"
anchorY="middle"
outlineColor="#0f172a"
outlineWidth={0.01}
>
{gateway.label}
</Text>
</group>
);
}
function CameraRig({ target }: { target: Gateway | null }) {
const { camera } = useThree();
const desired = useRef(new Vector3(0, 3, 12));
useFrame((_, delta) => {
if (target) {
const radius = 3.2;
desired.current.set(
Math.cos(target.angle) * radius,
2.5,
Math.sin(target.angle) * radius,
);
camera.lookAt(
Math.cos(target.angle) * 6,
1.5,
Math.sin(target.angle) * 6,
);
} else {
desired.current.set(0, 3, 12);
camera.lookAt(0, 0, 0);
}
camera.position.lerp(desired.current, 3 * delta);
});
return null;
}
function SceneContent() {
const [hovered, setHovered] = useState<string | null>(null);
const [selected, setSelected] = useState<Gateway | null>(null);
const navigate = useNavigate();
const handleClick = (gw: Gateway) => {
setSelected(gw);
setTimeout(() => navigate(gw.route), 550);
};
return (
<>
<color attach="background" args={["#030712"]} />
<fog attach="fog" args={["#030712", 15, 60]} />
<hemisphereLight
intensity={0.35}
color="#4f46e5"
groundColor="#0f172a"
/>
<spotLight
position={[5, 12, 5]}
intensity={1.5}
angle={0.4}
penumbra={0.7}
/>
<pointLight position={[-6, 6, -6]} intensity={1.2} color="#22d3ee" />
<Grid
args={[100, 100]}
sectionSize={2}
sectionThickness={0.2}
sectionColor="#0ea5e9"
cellSize={0.5}
cellThickness={0.1}
cellColor="#1e293b"
fadeDistance={30}
fadeStrength={3}
position={[0, -1, 0]}
infiniteGrid
/>
<CoreCube />
{gateways.map((gw) => (
<GatewayMesh
key={gw.label}
gateway={gw}
onHover={setHovered}
onClick={handleClick}
isActive={hovered === gw.label || selected?.label === gw.label}
/>
))}
<OrbitControls
enablePan={false}
minDistance={8}
maxDistance={18}
maxPolarAngle={Math.PI / 2.2}
minPolarAngle={Math.PI / 3}
enableDamping
dampingFactor={0.08}
/>
<CameraRig target={selected} />
</>
);
}
export default function Scene() {
return (
<div
style={{
width: "100vw",
height: "100vh",
position: "relative",
background: "#030712",
}}
>
{/* HUD Overlay */}
<motion.div
initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
style={{
position: "absolute",
top: 16,
left: 20,
color: "#e5e7eb",
fontFamily: "Inter, sans-serif",
letterSpacing: "0.08em",
fontSize: 14,
zIndex: 10,
userSelect: "none",
}}
>
AeThex OS v5.0
</motion.div>
<motion.button
initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut", delay: 0.05 }}
style={{
position: "absolute",
top: 12,
right: 20,
padding: "10px 16px",
borderRadius: 10,
border: "1px solid #38bdf8",
background: "rgba(14, 165, 233, 0.12)",
color: "#e0f2fe",
fontWeight: 600,
fontFamily: "Inter, sans-serif",
cursor: "pointer",
zIndex: 10,
backdropFilter: "blur(6px)",
}}
onClick={() => alert("Connect Passport")}
>
Connect Passport
</motion.button>
<Canvas
shadows
camera={{ position: [0, 3, 12], fov: 50, near: 0.1, far: 100 }}
gl={{ antialias: true }}
>
<SceneContent />
</Canvas>
</div>
);
}

View file

@ -0,0 +1,82 @@
import { useState } from "react";
export default function TitleBar() {
const [pinned, setPinned] = useState(false);
const call = async (method: string) => {
const api = (window as any)?.aeBridge;
if (!api || !api[method]) return;
const res = await api[method]();
if (method === "togglePin") setPinned(res);
};
return (
<div
style={{
height: 36,
display: "flex",
alignItems: "center",
padding: "0 12px",
background: "#050814",
color: "#9ca3af",
WebkitAppRegion: "drag",
borderBottom: "1px solid #0f172a",
letterSpacing: "0.08em",
fontSize: 12,
}}
>
<div style={{ fontFamily: "Space Mono, monospace" }}>AeThex Terminal</div>
<div
style={{
marginLeft: "auto",
display: "flex",
gap: 8,
WebkitAppRegion: "no-drag",
}}
>
<button
onClick={() => call("togglePin")}
style={btnStyle(pinned ? "#38bdf8" : "#1f2937")}
title="Pin / Unpin"
>
{pinned ? "Pinned" : "Pin"}
</button>
<button
onClick={() => call("minimize")}
style={btnStyle("#1f2937")}
title="Minimize"
>
_
</button>
<button
onClick={() => call("maximize")}
style={btnStyle("#1f2937")}
title="Maximize / Restore"
>
</button>
<button
onClick={() => call("close")}
style={btnStyle("#ef4444")}
title="Close"
>
X
</button>
</div>
</div>
);
}
function btnStyle(bg: string) {
return {
border: "1px solid #111827",
background: bg,
color: "#e5e7eb",
borderRadius: 6,
padding: "4px 8px",
cursor: "pointer",
fontSize: 12,
minWidth: 46,
} as React.CSSProperties;
}

View file

@ -1,6 +1,8 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import App from "./App"; import App from "./App";
import Overlay from "./pages/Overlay";
import DesktopShell from "./components/DesktopShell";
import ErrorBoundary from "./components/ErrorBoundary"; import ErrorBoundary from "./components/ErrorBoundary";
const container = document.getElementById("root"); const container = document.getElementById("root");
@ -9,10 +11,24 @@ if (!container) {
} }
const root = createRoot(container); const root = createRoot(container);
root.render(
<StrictMode> const hash = typeof window !== "undefined" ? window.location.hash : "";
<ErrorBoundary> if (hash.startsWith("#/overlay")) {
<App /> root.render(
</ErrorBoundary> <StrictMode>
</StrictMode>, <ErrorBoundary>
); <Overlay />
</ErrorBoundary>
</StrictMode>,
);
} else {
root.render(
<StrictMode>
<ErrorBoundary>
<DesktopShell>
<App />
</DesktopShell>
</ErrorBoundary>
</StrictMode>,
);
}

View file

@ -1,414 +1,19 @@
import { useState, useEffect } from "react";
import Layout from "@/components/Layout";
import SEO from "@/components/SEO"; import SEO from "@/components/SEO";
import { Button } from "@/components/ui/button"; import Scene from "@/components/Scene";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import LoadingScreen from "@/components/LoadingScreen";
import { Link } from "react-router-dom";
import {
ArrowRight,
Sparkles,
Zap,
Target,
Users,
TrendingUp,
LayoutDashboard,
Microscope,
IdCard,
Briefcase,
Code,
BookOpen,
Network,
} from "lucide-react";
export default function Index() { export default function Index() {
const [isLoading, setIsLoading] = useState(true);
const [activeSection, setActiveSection] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false);
}, 1200);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
const interval = setInterval(() => {
setActiveSection((prev) => (prev + 1) % 4);
}, 4000);
return () => clearInterval(interval);
}, []);
type FeatureCard = {
title: string;
description: string;
external?: boolean;
icon: any;
color: string;
link?: string;
tags?: string[];
};
// Ecosystem Audience Navigation
const audienceNav = [
{
title: "Solutions for Business",
description:
"Custom software development, consulting, and digital transformation",
icon: Briefcase,
color: "from-blue-500 to-cyan-500",
link: "/corp",
audience: "Enterprise clients",
},
{
title: "Explore & Learn",
description:
"Open source projects, educational workshops, and community resources",
icon: BookOpen,
color: "from-red-500 to-pink-500",
link: "https://aethex.foundation",
audience: "Developers & learners",
external: true,
},
{
title: "Join the Network",
description:
"Professional networking, job board, and portfolio showcase for Roblox devs",
icon: Network,
color: "from-cyan-500 to-blue-500",
link: "/dev-link",
audience: "Individual developers",
},
{
title: "Careers & Innovation",
description:
"Join our team and work on cutting-edge R&D and experimental features",
icon: Zap,
color: "from-yellow-500 to-amber-500",
link: "/labs",
audience: "Top-tier talent",
},
];
// Platform Features (Dashboard, Feed, etc.)
const platformFeatures: FeatureCard[] = [
{
title: "Dashboard",
description: "Your projects, applications, and rewards — in one place",
icon: LayoutDashboard,
color: "from-rose-500 to-amber-500",
link: "/dashboard",
tags: ["Overview", "Rewards"],
},
{
title: "Community Feed",
description: "Share progress, discover collaborators, and stay updated",
icon: Users,
color: "from-indigo-500 to-cyan-500",
link: "/feed",
tags: ["Posts", "Collab"],
},
{
title: "Developer Passport",
description: "A public profile with verifiable achievements",
icon: IdCard,
color: "from-fuchsia-500 to-violet-600",
link: "/passport/me",
tags: ["Profile", "Badges"],
},
{
title: "Docs & CLI",
description: "Guides, API reference, and tooling to ship faster",
icon: Microscope,
color: "from-lime-500 to-emerald-600",
link: "/docs",
tags: ["Guides", "API"],
},
];
// Showcase wins from each division
const showcaseWins = [
{
division: "Corp",
title: "Enterprise Transformation",
description: "Helped Fortune 500 company modernize their tech stack",
metric: "$2.5M revenue impact",
color: "bg-blue-500/10 border-blue-400/30",
},
{
division: "Foundation",
title: "Community Education",
description: "Launched Roblox development workshop series",
metric: "500+ developers trained",
color: "bg-red-500/10 border-red-400/30",
},
{
division: "Labs",
title: "AI Innovation",
description: "Breakthrough in procedural game content generation",
metric: "Published research paper",
color: "bg-yellow-500/10 border-yellow-400/30",
},
{
division: "Dev-Link",
title: "Network Growth",
description: "Reached 10K+ Roblox developers on the platform",
metric: "1000+ jobs posted",
color: "bg-cyan-500/10 border-cyan-400/30",
},
];
if (isLoading) {
return (
<LoadingScreen
message="Initializing AeThex OS..."
showProgress={true}
duration={1200}
/>
);
}
return ( return (
<> <>
<SEO <SEO
pageTitle="Home" pageTitle="AeThex | Immersive OS"
description="AeThex: Building the Future of Immersive Digital Experiences. Consulting, Open Source, Developer Network, and Innovation." description="AeThex OS — Cyberpunk Animus command center for Nexus, GameForge, Foundation, Labs, and Corp."
canonical={ canonical={
typeof window !== "undefined" typeof window !== "undefined"
? window.location.href ? window.location.href
: (undefined as any) : (undefined as any)
} }
/> />
<Layout hideFooter> <Scene />
{/* Hero Section */}
<section className="relative min-h-screen flex items-start justify-center overflow-hidden pt-24 sm:pt-36">
{/* Geometric Background */}
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-aethex-900/50 via-background to-aethex-800/50" />
<div className="absolute inset-0">
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<div className="relative w-96 h-96 opacity-5">
<img
src="https://docs.aethex.tech/~gitbook/image?url=https%3A%2F%2F1143808467-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Forganizations%252FDhUg3jal6kdpG645FzIl%252Fsites%252Fsite_HeOmR%252Flogo%252FqxDYz8Oj2SnwUTa8t3UB%252FAeThex%2520Origin%2520logo.png%3Falt%3Dmedia%26token%3D200e8ea2-0129-4cbe-b516-4a53f60c512b&width=512&dpr=1&quality=100&sign=6c7576ce&sv=2"
alt="Background"
className="w-full h-full animate-float"
/>
</div>
</div>
{[...Array(20)].map((_, i) => (
<div
key={i}
className="absolute bg-aethex-400/20 animate-float"
style={{
width: `${10 + Math.random() * 20}px`,
height: `${10 + Math.random() * 20}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 5}s`,
animationDuration: `${4 + Math.random() * 3}s`,
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
}}
/>
))}
</div>
</div>
{/* Main Content */}
<div className="container mx-auto px-4 relative z-10 pb-24 sm:pb-28">
<div className="text-center space-y-12">
{/* Title & Value Prop */}
<div className="space-y-6 animate-scale-in">
<div className="space-y-4">
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold">
<span className="text-gradient-purple">AeThex</span>
</h1>
<h2 className="text-2xl lg:text-3xl text-gradient animate-fade-in">
Building the Future of Immersive Digital Experiences
</h2>
<p className="text-lg text-muted-foreground max-w-3xl mx-auto animate-slide-up">
Software innovation, enterprise consulting, open source
education, and professional networkingall in one ecosystem.
</p>
</div>
</div>
{/* Audience Navigation Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 max-w-6xl mx-auto animate-slide-up">
{audienceNav.map((item, index) => {
const Icon = item.icon;
return (
<Link key={index} to={item.link} className="group">
<Card
className="relative overflow-hidden rounded-xl border border-border/30 hover:border-aethex-400/50 bg-card/60 backdrop-blur-sm hover:translate-y-[-4px] hover:shadow-[0_12px_40px_rgba(80,80,120,0.3)] transition-all duration-300 h-full cursor-pointer"
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-gradient-to-br from-white/6 via-transparent to-white/0" />
<CardContent className="p-6 flex flex-col items-center text-center gap-4 h-full justify-between">
<div
className={`relative w-14 h-14 rounded-lg bg-gradient-to-r ${item.color} grid place-items-center shadow-inner`}
>
<Icon className="h-7 w-7 text-white drop-shadow" />
</div>
<div>
<h3 className="font-bold text-lg mb-2">
{item.title}
</h3>
<p className="text-sm text-muted-foreground mb-3">
{item.description}
</p>
<Badge variant="outline" className="text-xs">
{item.audience}
</Badge>
</div>
<div
className={`h-[2px] w-12 rounded-full bg-gradient-to-r ${item.color} opacity-60 group-hover:opacity-100 transition-opacity`}
/>
</CardContent>
</Card>
</Link>
);
})}
</div>
{/* Division Showcase */}
<div className="max-w-6xl mx-auto animate-slide-up mt-8">
<h3 className="text-2xl font-bold mb-6">Recent Wins</h3>
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
{showcaseWins.map((win, index) => (
<Card
key={index}
className={`border ${win.color} bg-opacity-50`}
>
<CardContent className="pt-6">
<Badge variant="outline" className="mb-3 text-xs">
{win.division}
</Badge>
<h4 className="font-semibold text-sm mb-2">
{win.title}
</h4>
<p className="text-xs text-muted-foreground mb-3">
{win.description}
</p>
<p className="text-xs font-bold text-aethex-300">
{win.metric}
</p>
</CardContent>
</Card>
))}
</div>
</div>
{/* Platform Features Section */}
<div className="max-w-6xl mx-auto animate-slide-up mt-12">
<h3 className="text-2xl font-bold mb-6">Your Platform</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{platformFeatures.map((feature, index) => {
const Icon = feature.icon;
const isActive = activeSection === index;
return (
<Card
key={`platform-${index}`}
className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
isActive
? "border-aethex-500/60 glow-blue"
: "border-border/30 hover:border-aethex-400/50"
} bg-card/60 backdrop-blur-sm hover:translate-y-[-2px] hover:shadow-[0_8px_30px_rgba(80,80,120,0.25)]`}
style={{ animationDelay: `${(index + 4) * 0.08}s` }}
>
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-gradient-to-br from-white/6 via-transparent to-white/0" />
<CardContent className="p-5 sm:p-6 flex flex-col items-center text-center gap-3">
<div
className={`relative w-12 h-12 rounded-lg bg-gradient-to-r ${feature.color} grid place-items-center shadow-inner`}
>
<Icon className="h-6 w-6 text-white drop-shadow" />
</div>
<h3 className="font-semibold text-sm tracking-wide">
{feature.title}
</h3>
<div className="flex flex-wrap justify-center gap-2 min-h-[24px]">
{(feature.tags || []).slice(0, 2).map((tag, i) => (
<Badge
key={i}
variant="outline"
className="border-white/10 text-xs text-foreground/80"
>
{tag}
</Badge>
))}
</div>
<p className="text-xs text-muted-foreground line-clamp-2">
{feature.description}
</p>
<div
className={`mt-1 h-[2px] w-16 rounded-full bg-gradient-to-r ${feature.color} opacity-60 group-hover:opacity-100 transition-opacity`}
/>
{feature.link ? (
<div className="pt-1">
{feature.external ? (
<a
href={feature.link}
target="_blank"
rel="noopener noreferrer"
className="text-xs inline-flex items-center gap-1 text-aethex-300 hover:text-aethex-200"
>
Explore
<ArrowRight className="h-3 w-3" />
</a>
) : (
<Link
to={feature.link}
className="text-xs inline-flex items-center gap-1 text-aethex-300 hover:text-aethex-200"
>
Explore
<ArrowRight className="h-3 w-3" />
</Link>
)}
</div>
) : null}
</CardContent>
</Card>
);
})}
</div>
</div>
{/* CTA Buttons */}
<div className="flex flex-col sm:flex-row justify-center gap-6 animate-slide-up mt-8">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90 glow-blue hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link
to="/onboarding"
className="flex items-center space-x-2 group"
>
<Sparkles className="h-5 w-5" />
<span>Get Started</span>
<ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-2" />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="border-aethex-400/50 hover:border-aethex-400 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link to="/explore">Explore Platform</Link>
</Button>
</div>
</div>
</div>
</section>
</Layout>
</> </>
); );
} }

107
client/pages/Overlay.tsx Normal file
View file

@ -0,0 +1,107 @@
import { useState } from "react";
const defaultPath = "C:/Projects/Roblox"; // change as needed
export default function Overlay() {
const [dir, setDir] = useState(defaultPath);
const [status, setStatus] = useState<"idle" | "watching">("idle");
const start = async () => {
try {
await (window as any)?.aeBridge?.startWatcher(dir);
setStatus("watching");
} catch (e) {
console.error(e);
}
};
const stop = async () => {
try {
await (window as any)?.aeBridge?.stopWatcher();
setStatus("idle");
} catch (e) {
console.error(e);
}
};
return (
<div
style={{
width: "100vw",
height: "100vh",
background: "rgba(3, 7, 18, 0.88)",
color: "#e5e7eb",
fontFamily: "Inter, sans-serif",
padding: 20,
backdropFilter: "blur(10px)",
}}
>
<div style={{ marginBottom: 12, fontSize: 12, letterSpacing: "0.1em" }}>
AeThex Overlay On-Top Sidecar
</div>
<div
style={{
border: "1px solid #38bdf8",
borderRadius: 12,
padding: 14,
background: "rgba(14, 165, 233, 0.07)",
}}
>
<div style={{ fontWeight: 700, marginBottom: 8, fontSize: 14 }}>
File Watcher
</div>
<label style={{ display: "block", fontSize: 12, marginBottom: 4 }}>
Folder to watch (.lua / .cs / .ts / .tsx)
</label>
<input
value={dir}
onChange={(e) => setDir(e.target.value)}
style={{
width: "100%",
padding: 10,
borderRadius: 8,
border: "1px solid #1f2937",
background: "#0f172a",
color: "#e5e7eb",
marginBottom: 10,
}}
/>
<div style={{ display: "flex", gap: 8 }}>
<button
onClick={start}
style={{
padding: "10px 14px",
borderRadius: 10,
border: "1px solid #38bdf8",
background: "rgba(56, 189, 248, 0.14)",
color: "#e0f2fe",
cursor: "pointer",
}}
>
Start
</button>
<button
onClick={stop}
style={{
padding: "10px 14px",
borderRadius: 10,
border: "1px solid #ef4444",
background: "rgba(239, 68, 68, 0.1)",
color: "#fecdd3",
cursor: "pointer",
}}
>
Stop
</button>
<span style={{ fontSize: 12, opacity: 0.8, alignSelf: "center" }}>
Status: {status}
</span>
</div>
<div style={{ fontSize: 11, marginTop: 10, opacity: 0.75 }}>
PII is scrubbed locally before any processing.
</div>
</div>
</div>
);
}

17
electron-builder.yml Normal file
View file

@ -0,0 +1,17 @@
appId: com.aethex.desktop
productName: AeThex Desktop Terminal
files:
- "dist/**"
- "electron/**"
- "services/**"
asar: true
npmRebuild: false
buildDependenciesFromSource: false
win:
target: nsis
icon: build/icons/icon.ico
mac:
target: dmg
category: public.app-category.developer-tools
icon: build/icons/icon.icns

139
electron/main.js Normal file
View file

@ -0,0 +1,139 @@
import {
app,
BrowserWindow,
ipcMain,
globalShortcut,
clipboard,
} from "electron";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { startWatcher, stopWatcher } from "../services/watcher.js";
import { scrubPII } from "../services/pii-scrub.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let mainWindow = null;
let overlayWindow = null;
let pinned = false;
let sentinelInterval = null;
function getRendererUrl() {
return (
process.env.VITE_DEV_SERVER_URL ||
`file://${path.join(__dirname, "../dist/spa/index.html")}`
);
}
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
frame: false,
titleBarStyle: "hidden",
backgroundColor: "#030712",
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
},
});
mainWindow.loadURL(getRendererUrl());
}
function createOverlayWindow() {
overlayWindow = new BrowserWindow({
width: 420,
height: 640,
transparent: true,
frame: false,
alwaysOnTop: true,
resizable: true,
focusable: true,
backgroundColor: "#00000000",
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
},
});
overlayWindow.setAlwaysOnTop(true, "floating");
const base = getRendererUrl();
// Assumes your SPA has a route for /overlay
overlayWindow.loadURL(base + "#/overlay");
}
function toggleMainVisibility() {
if (!mainWindow) return;
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
mainWindow.focus();
}
}
function startClipboardSentinel() {
if (sentinelInterval) return;
let last = clipboard.readText();
sentinelInterval = setInterval(() => {
const now = clipboard.readText();
if (now !== last) {
last = now;
const scrubbed = scrubPII(now);
if (scrubbed !== now) {
mainWindow?.webContents.send("sentinel:clipboard-alert", now);
}
}
}, 1500);
}
app.whenReady().then(() => {
createMainWindow();
createOverlayWindow();
startClipboardSentinel();
// Global hotkey to toggle visibility
globalShortcut.register("Alt+Space", toggleMainVisibility);
ipcMain.handle("watcher:start", async (_e, dir) => {
await startWatcher(dir);
return true;
});
ipcMain.handle("watcher:stop", async () => {
await stopWatcher();
return true;
});
ipcMain.handle("window:toggle-pin", () => {
pinned = !pinned;
mainWindow?.setAlwaysOnTop(pinned, "floating");
return pinned;
});
ipcMain.handle("window:close", () => {
mainWindow?.close();
});
ipcMain.handle("window:minimize", () => {
mainWindow?.minimize();
});
ipcMain.handle("window:maximize", () => {
if (!mainWindow) return false;
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
return mainWindow.isMaximized();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});

13
electron/preload.js Normal file
View file

@ -0,0 +1,13 @@
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("aeBridge", {
startWatcher: (dir) => ipcRenderer.invoke("watcher:start", dir),
stopWatcher: () => ipcRenderer.invoke("watcher:stop"),
togglePin: () => ipcRenderer.invoke("window:toggle-pin"),
close: () => ipcRenderer.invoke("window:close"),
minimize: () => ipcRenderer.invoke("window:minimize"),
maximize: () => ipcRenderer.invoke("window:maximize"),
onClipboardAlert: (fn) =>
ipcRenderer.on("sentinel:clipboard-alert", (_e, payload) => fn(payload)),
});

3963
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,10 @@
"name": "fusion-starter", "name": "fusion-starter",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "0.1.0",
"description": "AeThex Terminal — desktop sidecar with overlay, watcher, and local sentinel.",
"author": "AeThex",
"main": "electron/main.js",
"pkg": { "pkg": {
"assets": [ "assets": [
"dist/spa/*" "dist/spa/*"
@ -19,13 +23,18 @@
"start": "node dist/server/production.mjs", "start": "node dist/server/production.mjs",
"test": "vitest --run", "test": "vitest --run",
"format.fix": "prettier --write .", "format.fix": "prettier --write .",
"typecheck": "tsc" "typecheck": "tsc",
"desktop:dev": "concurrently -k \"npm:desktop:renderer\" \"npm:desktop:electron\"",
"desktop:renderer": "npm run dev -- --host --port 5173",
"desktop:electron": "cross-env VITE_DEV_SERVER_URL=http://localhost:5173 electron .",
"desktop:build": "npm run build && electron-builder -c electron-builder.yml"
}, },
"dependencies": { "dependencies": {
"@builder.io/react": "^8.2.8", "@builder.io/react": "^8.2.8",
"@discord/embedded-app-sdk": "^2.4.0", "@discord/embedded-app-sdk": "^2.4.0",
"@supabase/supabase-js": "^2.53.0", "@supabase/supabase-js": "^2.53.0",
"@vercel/analytics": "^1.5.0", "@vercel/analytics": "^1.5.0",
"chokidar": "^3.6.0",
"dotenv": "^17.2.0", "dotenv": "^17.2.0",
"ethers": "^6.13.0", "ethers": "^6.13.0",
"express": "^4.18.2", "express": "^4.18.2",
@ -34,6 +43,10 @@
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"electron": "^32.2.0",
"electron-builder": "^25.1.8",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-alert-dialog": "^1.1.1",
@ -62,7 +75,7 @@
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.4",
"@react-three/drei": "^10.1.2", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^8.18.0", "@react-three/fiber": "^8.18.0",
"@swc/core": "^1.11.24", "@swc/core": "^1.11.24",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
@ -83,7 +96,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"framer-motion": "^12.6.2", "framer-motion": "^12.23.25",
"globals": "^15.9.0", "globals": "^15.9.0",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",

11
services/pii-scrub.js Normal file
View file

@ -0,0 +1,11 @@
const patterns = [
/api[_-]?key\s*=\s*["'][A-Za-z0-9_\-]{16,}["']/gi,
/\b[A-F0-9]{32,64}\b/gi,
/\b\d{3}-\d{2}-\d{4}\b/g,
/\b[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\b/g,
];
export function scrubPII(input) {
return patterns.reduce((out, p) => out.replace(p, "[REDACTED]"), input);
}

36
services/watcher.js Normal file
View file

@ -0,0 +1,36 @@
import chokidar from "chokidar";
import fs from "node:fs";
import path from "node:path";
import { scrubPII } from "./pii-scrub.js";
let watcher = null;
export async function startWatcher(dir) {
if (watcher) await watcher.close();
watcher = chokidar.watch(["**/*.lua", "**/*.cs", "**/*.ts", "**/*.tsx"], {
cwd: dir,
ignoreInitial: true,
});
watcher.on("change", (filePath) => {
const full = path.join(dir, filePath);
try {
const content = fs.readFileSync(full, "utf-8");
const safe = scrubPII(content);
console.log(`[watcher] ${filePath} changed`);
// TODO: route safe content to renderer or local analysis pipeline
console.log(safe.slice(0, 400));
} catch (err) {
console.error("watcher read error", err);
}
});
return true;
}
export async function stopWatcher() {
if (watcher) await watcher.close();
watcher = null;
return true;
}