modified: .replit
This commit is contained in:
parent
bce04f18cd
commit
82de4d9b41
17 changed files with 4918 additions and 430 deletions
4
.replit
4
.replit
|
|
@ -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
151
README.md
Normal 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
42
aethex-forge/.gitignore
vendored
Normal 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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
21
client/components/DesktopShell.tsx
Normal file
21
client/components/DesktopShell.tsx
Normal 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
308
client/components/Scene.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
82
client/components/TitleBar.tsx
Normal file
82
client/components/TitleBar.tsx
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 networking—all 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
107
client/pages/Overlay.tsx
Normal 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
17
electron-builder.yml
Normal 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
139
electron/main.js
Normal 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
13
electron/preload.js
Normal 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
3963
package-lock.json
generated
File diff suppressed because it is too large
Load diff
19
package.json
19
package.json
|
|
@ -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
11
services/pii-scrub.js
Normal 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
36
services/watcher.js
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in a new issue