Compare commits

..

8 commits

Author SHA1 Message Date
Claude
ffd3140fc8
docs: Add comprehensive visual feature guide with ASCII mockups
- Document main interface layout and components
- Add visual representations for all major features:
  - Visual Scripting node editor
  - Asset Library with grid/list views
  - 3D Live Preview with console
  - AI Code Generator with templates
  - Real-time Collaboration panel
- Include color scheme documentation
- Show responsive design breakpoints
- Provide ASCII art mockups for each feature
2026-01-24 04:08:16 +00:00
Claude
c08627a561
feat: Add Real-time Collaboration infrastructure
- Create collaboration types (CollaboratorInfo, Session, Chat, Permissions)
- Build collaboration store with Zustand and socket.io integration
- Add cursor/selection sharing, chat messaging, and typing indicators
- Include permission system (viewer, editor, admin, owner)
- Add mock mode for demo/testing without server
- Support follow mode and session settings
2026-01-24 02:57:50 +00:00
Claude
9c54fb3386
feat: Add AI Code Generation v2 with system templates and snippets
- Create comprehensive system templates (inventory, quests, currency, combat, friends)
- Add platform-specific code patterns and snippets for Roblox/UEFN/Spatial
- Build AIGenerationPanel component with template browser and parameter editor
- Add configurable generation with sliders, switches, and dropdowns
- Include code snippets library with copy/insert functionality
- Integrate AI Generate button into Toolbar (desktop and mobile)
- Support custom AI generation prompts (placeholder for future AI integration)
2026-01-23 23:12:28 +00:00
Claude
159e40f02c
feat: Add Live Game Preview with 3D viewport and Lua interpreter
- Create mock Roblox API (Vector3, Color3, CFrame, TweenService, RunService)
- Implement Lua-to-JavaScript transpiler for basic Roblox script execution
- Build 3D viewport using React Three Fiber with shadows, grid, and controls
- Add preview console with filtering, search, and output types
- Create LivePreview component with run/stop/pause controls and settings
- Add 3D Preview button to Toolbar (desktop and mobile)
- Fix pre-existing syntax error in FileTree.tsx toggleFolder function
2026-01-23 23:06:16 +00:00
Claude
5feb186c05
feat: Add Asset Library system for models, textures, and audio management
- Add asset types, categories, and file format mappings (src/lib/assets/types.ts)
- Create Zustand store with filtering, sorting, favorites support (src/stores/asset-store.ts)
- Build full Asset Library UI with grid/list views, drag-drop upload, details panel
- Integrate Asset Library button into Toolbar (desktop and mobile menus)
- Add lazy-loaded AssetLibrary modal to App.tsx
2026-01-23 22:58:30 +00:00
Claude
6aff5ac183
feat: Add Visual Scripting system with node-based editor
Implements a complete visual scripting system using React Flow:

Node System:
- 30+ node types across 5 categories (Events, Logic, Actions, Data, References)
- Event nodes: OnPlayerJoin, OnPartTouch, OnKeyPress, OnTimer, etc.
- Logic nodes: If/Else, For Loop, While, Wait, ForEach
- Action nodes: Print, SetProperty, CreatePart, Destroy, PlaySound, Tween
- Data nodes: Number, String, Boolean, Vector3, Color, Math, Compare, Random
- Reference nodes: GetPlayer, GetAllPlayers, FindChild, Workspace, GetService

Code Generation:
- Converts node graphs to platform-specific code
- Supports Roblox (Lua), UEFN (Verse), and Spatial (TypeScript)
- Validation with error/warning detection
- Template-based code generation with proper nesting

UI Features:
- Drag-and-drop node palette with search
- Category-based node organization with icons
- Custom node rendering with input fields
- Connection type validation (flow, data types)
- Undo/redo history
- MiniMap and controls
- Code preview dialog with copy functionality

State Management:
- Zustand store with persistence
- Auto-save to localStorage
2026-01-23 22:53:59 +00:00
Claude
4fa6d0c3ed
docs: Add comprehensive product roadmap and feature architecture
- Full product vision from basic IDE to game dev ecosystem
- 8-phase roadmap covering visual scripting, marketplace, AI, analytics
- Detailed architecture for priority features
- Revenue model and competitive positioning
- Tech stack evolution plan
- Success metrics and timeline
2026-01-23 22:38:27 +00:00
Claude
96163c8256
feat: Add AeThex cross-platform avatar toolkit
Implement comprehensive avatar import/export/rigging system supporting:
- Roblox (R6, R15, Rthro), VRChat, RecRoom, Spatial, Sandbox
- Decentraland, Meta Horizon, NeosVR, Resonite, ChilloutVR
- Universal AeThex format for lossless cross-platform conversion

Features:
- Platform-specific skeleton specs and bone mappings
- Auto-rig detection and universal bone name resolution
- Format handlers for GLB, GLTF, FBX, VRM, OBJ, PMX
- Validation against platform constraints (polygons, bones, textures)
- Avatar templates and optimization presets
- Compatibility scoring between platforms
2026-01-23 22:09:25 +00:00
202 changed files with 22455 additions and 25910 deletions

View file

@ -1,17 +1,6 @@
# AeThex Studio Environment Variables
# ===========================================
# SUPABASE (Required)
# ===========================================
# Get these from: https://supabase.com/dashboard/project/_/settings/api
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
# Service role key - KEEP SECRET, never expose to client
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here
# ===========================================
# Claude API Configuration
# ===========================================
# Get your API key from: https://console.anthropic.com/
# Required for cross-platform code translation feature
VITE_CLAUDE_API_KEY=sk-ant-api03-your-api-key-here
@ -19,13 +8,9 @@ VITE_CLAUDE_API_KEY=sk-ant-api03-your-api-key-here
# Optional: Override Claude model (default: claude-3-5-sonnet-20241022)
# VITE_CLAUDE_MODEL=claude-3-5-sonnet-20241022
# ===========================================
# PostHog Analytics (Optional)
# ===========================================
# VITE_POSTHOG_KEY=your-posthog-key
# VITE_POSTHOG_HOST=https://app.posthog.com
# ===========================================
# Sentry Error Tracking (Optional)
# ===========================================
# VITE_SENTRY_DSN=your-sentry-dsn

71
.gitignore vendored
View file

@ -7,62 +7,11 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# archives
*.zip
node_modules
dist
dist-ssr
*-dist
*.local
# Next.js
.next
@ -86,13 +35,3 @@ pids
.devcontainer/
.spark-workbench-id
.env
**/agent-eval-report*
packages
pids
.file-manifest
.devcontainer/
.spark-workbench-id
.env.local

File diff suppressed because it is too large Load diff

115
README.md
View file

@ -1,10 +1,23 @@
# Firebase Studio
# AeThex Studio
This is a NextJS starter in Firebase Studio.
A powerful, **multi-platform** browser-based IDE for game development with **AI-powered cross-platform code translation**, modern tooling, and an intuitive interface. Build once, deploy everywhere.
![AeThex Studio](https://img.shields.io/badge/version-1.0.0-blue.svg) ![License](https://img.shields.io/badge/license-MIT-green.svg) ![Next.js](https://img.shields.io/badge/Next.js-14.2-black.svg) ![React](https://img.shields.io/badge/React-18.3-blue.svg)
To get started, take a look at src/app/page.tsx.
## 🌟 What Makes AeThex Studio Different
**Cross-Platform Translation Engine** - The only IDE that translates your code between game platforms:
- 🎮 **Roblox Lua** → ⚡ **UEFN Verse** → 🌐 **Spatial TypeScript** → 🎯 **Core Lua**
- AI-powered intelligent code conversion
- Platform-specific best practices applied
- Side-by-side comparison view
- Explanation of key differences
**Build once, deploy everywhere.** Write your game logic in Roblox, translate to UEFN with one click.
## ✨ Features
### 🌍 **Multi-Platform Support** ⭐ NEW!
- **Platform Switching** - Work with Roblox, UEFN, Spatial, or Core
- **Platform-Specific Templates**:
- 🎮 **Roblox**: 25 Lua templates
@ -15,34 +28,26 @@ To get started, take a look at src/app/page.tsx.
- **Side-by-Side Comparison** - Compare original and translated code
- **Smart Editor** - Language highlighting adapts to selected platform
## 🎨 **Modern Code Editor**
### 🎨 **Modern Code Editor**
- **Monaco Editor** - The same editor that powers VS Code
- **Multi-language Support** - Lua, Verse, TypeScript
- **Real-time code validation** and linting
- **Multi-file editing** with tab management
- **File tree navigation** with drag-and-drop organization
## 🤖 **AI-Powered Assistant**
### 🤖 **AI-Powered Assistant**
- Built-in AI chat for code help and debugging
- Context-aware suggestions
- Code explanation and documentation
- Roblox API knowledge
## 📁 **Project Management**
### 📁 **Project Management**
- **File Tree** - Organize your scripts into folders
- **Drag-and-drop** - Rearrange files easily
- **Quick file search** (Cmd/Ctrl+P) - Find files instantly
- **Search in files** (Cmd/Ctrl+Shift+F) - Global text search
## 🎯 **Productivity Features**
### 🎯 **Productivity Features**
- **33+ Code Templates** - Ready-made scripts for multiple platforms
- **Roblox** (25 templates):
- Beginner templates (Hello World, Touch Detectors, etc.)
@ -58,9 +63,7 @@ To get started, take a look at src/app/page.tsx.
- **Keyboard Shortcuts** - Professional IDE shortcuts
- **Code Preview** - Test your scripts instantly
## 💻 **Interactive Terminal & CLI**
### 💻 **Interactive Terminal & CLI**
- **Built-in Terminal** - Full-featured command line interface
- **10+ CLI Commands** for Roblox development:
- `help` - Display available commands
@ -78,9 +81,7 @@ To get started, take a look at src/app/page.tsx.
- **Smart Suggestions** - Context-aware command hints
- **Toggle with Cmd/Ctrl + `** - Quick terminal access
## 🎨 **Customization**
### 🎨 **Customization**
- **5 Beautiful Themes**:
- **Dark** - Classic dark theme for comfortable coding
- **Light** - Clean light theme for bright environments
@ -89,27 +90,21 @@ To get started, take a look at src/app/page.tsx.
- **Ocean** - Deep blue theme
- **Persistent preferences** - Your settings are saved
## 📱 **Mobile Responsive**
### 📱 **Mobile Responsive**
- Optimized layouts for phones and tablets
- Touch-friendly controls
- Hamburger menu for mobile
- Collapsible panels
## 🚀 **Developer Experience**
### 🚀 **Developer Experience**
- **Code splitting** for fast loading
- **Error boundaries** with graceful error handling
- **Loading states** with spinners
- **Toast notifications** for user feedback
- **Testing infrastructure** with Vitest
## 🎮 Perfect For
- **Multi-Platform Developers** - Build for Roblox, UEFN, Spatial, and Core from one IDE
- **Game Studios** - Translate games between platforms with AI assistance
- **Roblox → UEFN Migration** - Converting existing Roblox games to Fortnite
@ -117,32 +112,26 @@ To get started, take a look at src/app/page.tsx.
- **Rapid Prototyping** - Build once, deploy to multiple platforms
- **Web-Based Development** - Code anywhere, no installation needed
## ⌨️ Keyboard Shortcuts
| Shortcut | Action |
| :----------------- | :---------------------------- |
| `Cmd/Ctrl + S` | Save file (auto-save enabled) |
| `Cmd/Ctrl + P` | Quick file search |
| `Cmd/Ctrl + K` | Command palette |
| `Cmd/Ctrl + N` | New project |
| `Cmd/Ctrl + F` | Find in editor |
| `Cmd/Ctrl + Shift + F` | Search in all files |
| ``Cmd/Ctrl + ` `` | Toggle terminal |
| Shortcut | Action |
|----------|--------|
| `Cmd/Ctrl + S` | Save file (auto-save enabled) |
| `Cmd/Ctrl + P` | Quick file search |
| `Cmd/Ctrl + K` | Command palette |
| `Cmd/Ctrl + N` | New project |
| `Cmd/Ctrl + F` | Find in editor |
| `Cmd/Ctrl + Shift + F` | Search in all files |
| ``Cmd/Ctrl + ` `` | Toggle terminal |
## 🚀 Getting Started
### Prerequisites
- Node.js 18+
- npm or yarn
### Installation
#
```bash
# Clone the repository
git clone https://github.com/AeThex-LABS/aethex-studio.git
@ -159,7 +148,6 @@ npm run dev
Visit `http://localhost:3000` to see the application.
### 🔑 Enabling Cross-Platform Translation
To unlock the **AI-powered code translation** feature, you need a Claude API key:
@ -167,7 +155,7 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
1. **Get API Key**: Visit [Anthropic Console](https://console.anthropic.com/settings/keys) and create a new API key
2. **Configure Environment**:
```bash
```bash
# Copy example environment file
cp .env.example .env.local
@ -176,7 +164,7 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
```
3. **Restart Dev Server**:
```bash
```bash
npm run dev
```
@ -189,10 +177,8 @@ To unlock the **AI-powered code translation** feature, you need a Claude API key
💡 **Note**: Without an API key, the app works perfectly but shows mock translations instead of real AI conversions.
### Building for Production
#
```bash
# Build the application
npm run build
@ -201,10 +187,8 @@ npm run build
npm start
```
## 📖 Usage Guide
### Creating Your First Script
1. Click **"New File"** in the file tree
@ -213,7 +197,6 @@ npm start
4. Click **"Preview"** to test
5. **Copy** or **Export** your script
### Using Templates
1. Click the **Templates** button in the toolbar
@ -221,7 +204,6 @@ npm start
3. Click a template to load it into your editor
4. Customize the code for your needs
### AI Assistant
1. Open the **AI Chat** panel (right side on desktop)
@ -232,7 +214,6 @@ npm start
- Best practices
3. Get instant, context-aware answers
### Organizing Files
- **Create folders** - Right-click in file tree
@ -240,14 +221,12 @@ npm start
- **Rename** - Click the menu (⋯) next to a file
- **Delete** - Use the menu to remove files
### Searching
- **Quick search** - `Cmd/Ctrl+P` to find files by name
- **Global search** - `Cmd/Ctrl+Shift+F` to search text across all files
- **In-editor search** - `Cmd/Ctrl+F` to find text in current file
## 🛠️ Tech Stack
- **Next.js 14** - React framework
@ -261,10 +240,8 @@ npm start
- **PostHog** - Analytics (optional)
- **Sentry** - Error tracking (optional)
## 🧪 Running Tests
#
```bash
# Run all tests
npm test
@ -279,10 +256,8 @@ npm run test:ui
npm run test:coverage
```
## 📂 Project Structure
#
```
aethex-studio/
├── src/
@ -303,7 +278,6 @@ aethex-studio/
└── tests/ # Test files
```
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
@ -314,52 +288,37 @@ Contributions are welcome! Please feel free to submit a Pull Request.
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## 📝 Code Templates
AeThex Studio includes 25+ production-ready templates:
**Beginner:**
- Hello World, Player Join Handler, Part Touch Detector, etc.
**Gameplay:**
- DataStore System, Teleport Part, Team System, Combat System, etc.
**UI:**
- GUI Buttons, Proximity Prompts, Countdown Timers, etc.
**Tools:**
- Give Tool, Sound Manager, Admin Commands, Chat Commands, etc.
**Advanced:**
- Round System, Inventory System, Pathfinding NPC, Shop System, etc.
## 🐛 Bug Reports
Found a bug? Please open an issue on GitHub with:
- Description of the bug
- Steps to reproduce
- Expected vs actual behavior
- Screenshots (if applicable)
## 📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🙏 Acknowledgments
- **Monaco Editor** - For the powerful code editor
@ -367,16 +326,12 @@ This project is licensed under the MIT License - see the LICENSE file for detail
- **Radix UI** - For accessible component primitives
- **Vercel** - For Next.js framework
## 📧 Contact
- **Website**: [aethex.com](https://aethex.com)
- **GitHub**: [@AeThex-LABS](https://github.com/AeThex-LABS)
- **Issues**: [GitHub Issues](https://github.com/AeThex-LABS/aethex-studio/issues)
---
---
**Built with ❤️ by the AeThex team**

503
ROADMAP.md Normal file
View file

@ -0,0 +1,503 @@
# AeThex Studio - Product Roadmap & Vision
## Current State: Basic IDE
What we have now is essentially a code editor with some templates. That's table stakes.
## Vision: The Unity/Unreal for Cross-Platform Metaverse Development
AeThex should be the **one-stop platform** where creators build once and deploy everywhere - with visual tools, AI assistance, marketplace, collaboration, and analytics.
---
## PHASE 1: Core IDE Enhancement (Foundation)
### 1.1 Visual Scripting System
**Why**: 70% of Roblox creators are visual learners / non-coders
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ On Player │───▶│ Check if │───▶│ Give Item │
│ Touch Part │ │ Has Pass │ │ to Player │
└─────────────┘ └─────────────┘ └─────────────┘
```
- Node-based editor (like Unreal Blueprints)
- Drag-and-drop logic blocks
- Auto-generates Lua/Verse/TS code
- Bi-directional: edit nodes OR code
### 1.2 Live Game Preview
**Why**: Can't test code without leaving the IDE
- Embedded Roblox-like 3D viewport
- Hot-reload code changes
- Console output in real-time
- Breakpoints and step debugging
- Mobile device preview mode
### 1.3 Real-time Collaboration
**Why**: Teams need to work together
- Google Docs-style multiplayer editing
- Cursor presence (see teammates typing)
- Voice/video chat integration
- Code review comments inline
- Conflict resolution
### 1.4 Version Control (Built-in Git)
**Why**: Professional teams need proper VCS
- Visual git interface (no command line needed)
- Branch visualization
- Pull request workflow
- Auto-commit on save (optional)
- Integration with GitHub/GitLab
---
## PHASE 2: Asset Pipeline & Creation Tools
### 2.1 3D Scene Editor
**Why**: Visual level design is essential
```
┌────────────────────────────────────────────────┐
│ Scene View │ Inspector │
│ ┌──────────────────────────┐ │ ┌────────────┐ │
│ │ 🎮 │ │ │ Transform │ │
│ │ ┌───┐ │ │ │ X: 0 Y: 5 │ │
│ │ │ │ 👤 │ │ │ Z: 10 │ │
│ │ └───┘ │ │ ├────────────┤ │
│ │ ████████████ │ │ │ Scripts │ │
│ └──────────────────────────┘ │ │ + Add │ │
│ Hierarchy │ Assets │ └────────────┘ │
└────────────────────────────────────────────────┘
```
- Drag-drop object placement
- Terrain editor
- Lighting system
- Physics simulation
- Cross-platform export (Roblox .rbxl, UEFN .umap)
### 2.2 Asset Library & Manager
**Why**: Games need models, textures, sounds
- Built-in asset browser
- Import: FBX, OBJ, GLB, PNG, WAV, MP3
- Auto-optimization per platform
- Asset variants (LODs, mobile versions)
- AI-powered asset generation (text-to-3D, text-to-texture)
### 2.3 Animation Editor
**Why**: Bring characters to life
- Timeline-based animation
- Skeletal animation support
- Blend trees and state machines
- Animation retargeting (apply human anim to any rig)
- Procedural animation tools
### 2.4 Particle System Editor
**Why**: Visual effects are crucial
- Visual particle editor
- Real-time preview
- Presets library (fire, smoke, magic, etc.)
- Cross-platform particle export
### 2.5 Audio Workstation (Basic)
**Why**: Sound design matters
- Waveform editor
- Spatial audio setup
- Music sequencer (simple)
- Sound effect library
- Adaptive audio triggers
---
## PHASE 3: AI-Powered Development
### 3.1 AI Code Generation (Not Just Translation)
**Why**: "Build me a combat system" should work
```
User: "Create a round-based game system with 3 minute rounds,
team scoring, and a lobby between rounds"
AI: [Generates complete system with:]
- RoundManager.lua
- TeamScoring.lua
- LobbySystem.lua
- UI components
- All connected and working
```
### 3.2 AI Game Designer
**Why**: Help with game design, not just code
- "What mechanics would make this more fun?"
- "Balance these weapon stats"
- "Suggest monetization that isn't predatory"
- Playtest analysis and suggestions
### 3.3 AI Asset Generation
**Why**: Not everyone can do art
- Text-to-3D model generation
- Text-to-texture
- AI music generation
- AI sound effects
- Style transfer (make assets match your game's style)
### 3.4 AI Bug Detection
**Why**: Find issues before players do
- Static analysis with AI reasoning
- "This loop might cause lag because..."
- Security vulnerability detection
- Memory leak detection
- Cross-platform compatibility warnings
### 3.5 AI Documentation
**Why**: Nobody likes writing docs
- Auto-generate code comments
- API documentation from code
- Tutorial generation from codebase
- Changelog generation from commits
---
## PHASE 4: Marketplace & Economy
### 4.1 AeThex Marketplace
**Why**: Creators want to monetize; buyers want shortcuts
```
┌─────────────────────────────────────────────────────┐
│ 🏪 AeThex Marketplace [Search] │
├─────────────────────────────────────────────────────┤
│ Featured │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Combat │ │ Vehicle │ │ Fantasy │ │ UI Kit │ │
│ │ System │ │ Physics │ │ Assets │ │ Pro │ │
│ │ ⭐4.9 │ │ ⭐4.7 │ │ ⭐4.8 │ │ ⭐5.0 │ │
│ │ $15 │ │ $25 │ │ $40 │ │ $10 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Categories: Scripts | 3D Models | UI | Audio | FX │
└─────────────────────────────────────────────────────┘
```
- Sell scripts, assets, templates, full game kits
- Revenue split (80/20 creator/AeThex)
- Ratings and reviews
- License management
- Cross-platform asset conversion included
### 4.2 Creator Subscriptions
**Why**: Recurring revenue for top creators
- Creators can offer subscription access
- Patreon-style tiers
- Early access to new content
- Direct support/consulting
### 4.3 Commission System
**Why**: Connect creators with developers who need custom work
- Post job: "Need inventory system, $500 budget"
- Verified creators can bid
- Escrow payment system
- Portfolio and reviews
---
## PHASE 5: Deployment & Analytics
### 5.1 One-Click Deploy
**Why**: Getting code into games is painful
```
┌─────────────────────────────────────────┐
│ 🚀 Deploy to Platform │
├─────────────────────────────────────────┤
│ ☑ Roblox [Connected] [Deploy] │
│ ☑ UEFN [Connected] [Deploy] │
│ ☐ Spatial [Connect Account] │
│ ☐ Core Games [Connect Account] │
├─────────────────────────────────────────┤
│ Deploy Options: │
│ ☑ Run validation checks │
│ ☑ Optimize for platform │
│ ☐ Deploy to staging first │
└─────────────────────────────────────────┘
```
- OAuth with each platform
- Direct publish to Roblox experiences
- UEFN island deployment
- Automatic platform optimization
- Rollback support
### 5.2 CI/CD Pipeline
**Why**: Professional development workflow
- Automated testing on commit
- Lint and format checks
- Cross-platform compatibility tests
- Staging → Production workflow
- Scheduled deployments
### 5.3 Analytics Dashboard
**Why**: Data-driven game development
```
┌─────────────────────────────────────────────────────┐
│ 📊 Game Analytics - "Epic Battle Sim" │
├─────────────────────────────────────────────────────┤
│ Players (7 days) Revenue (7 days) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 📈 +23% │ │ 📈 +15% │ │
│ │ ╲ │ │ ╱╲ │ │
│ │ ╲│ │ ╲ │ │
│ │╱ │ │ ╲ │ │
│ └─────────────────┘ └─────────────────┘ │
│ 45.2K daily active $12.4K this week │
├─────────────────────────────────────────────────────┤
│ Retention │ Monetization │ Performance │
│ D1: 42% │ ARPU: $0.28 │ Avg FPS: 58 │
│ D7: 18% │ Conv: 3.2% │ Crash rate: 0.1% │
│ D30: 8% │ LTV: $1.85 │ Load time: 2.3s │
└─────────────────────────────────────────────────────┘
```
- Cross-platform unified analytics
- Player behavior tracking
- Funnel analysis
- A/B testing built-in
- Revenue tracking
- Performance monitoring
- Error tracking (Sentry integration)
### 5.4 Performance Profiler
**Why**: Optimize before deploying
- CPU/GPU usage graphs
- Memory allocation tracking
- Script execution time
- Network usage (for multiplayer)
- Platform-specific bottleneck detection
- "This function runs 500x/second, consider caching"
---
## PHASE 6: Multiplayer & Backend
### 6.1 Backend-as-a-Service
**Why**: Multiplayer games need servers
- Managed game servers
- Real-time databases (like Firebase)
- Player authentication
- Leaderboards
- Matchmaking
- Cloud saves
- No server code needed (visual configuration)
### 6.2 Multiplayer Testing
**Why**: Can't test multiplayer alone
- Spawn multiple test clients
- Simulate latency/packet loss
- Record and replay sessions
- Bot players for testing
### 6.3 Economy Management
**Why**: In-game currencies are complex
- Virtual currency management
- Item database
- Trading system
- Anti-cheat for economy
- Cross-platform wallet
---
## PHASE 7: Community & Learning
### 7.1 Learning Platform
**Why**: Grow the next generation
```
┌─────────────────────────────────────────┐
│ 🎓 AeThex Academy │
├─────────────────────────────────────────┤
│ Your Progress: Level 12 | 2,400 XP │
│ ████████████░░░░░░░░ 60% to Level 13 │
├─────────────────────────────────────────┤
│ Current Course: Combat Systems │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ ✅ │ │ ✅ │ │ 🔵 │ │ ⚪ │ │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ Lesson 3: Hitboxes and Damage │
├─────────────────────────────────────────┤
│ 🏆 Achievements │
│ [First Script] [100 Lines] [Published] │
└─────────────────────────────────────────┘
```
- Interactive coding lessons
- Video tutorials
- Challenges with XP rewards
- Certifications
- Mentorship matching
### 7.2 Community Hub
**Why**: Creators need community
- Project showcases
- Forums / Discord integration
- Game jams (hosted by AeThex)
- Creator spotlights
- Job board
### 7.3 Creator Certification Program
**Why**: Verified expertise matters
- Skill assessments
- Platform-specific certifications
- Verified badges on marketplace
- Enterprise hiring pipeline
---
## PHASE 8: Enterprise & Teams
### 8.1 Team Workspaces
**Why**: Studios need organization
- Organization accounts
- Role-based permissions
- Project organization
- Shared asset libraries
- Team analytics
### 8.2 Enterprise Features
**Why**: Big studios have big needs
- SSO/SAML integration
- Audit logs
- Compliance tools
- Dedicated support
- Custom SLAs
- On-premise option
### 8.3 White-Label Solution
**Why**: Some companies want their own version
- Branded IDE for game studios
- Custom template libraries
- Internal marketplaces
- Integration with existing tools
---
## Revenue Model
### Free Tier
- Full IDE access
- 3 projects
- Community support
- Basic templates
- 1GB asset storage
### Pro ($15/mo)
- Unlimited projects
- AI code generation (100 requests/day)
- Advanced analytics
- Priority support
- 50GB asset storage
- Marketplace selling (85/15 split)
### Team ($40/user/mo)
- Everything in Pro
- Real-time collaboration
- Team workspaces
- Version control
- CI/CD pipelines
- 200GB shared storage
### Enterprise (Custom)
- Everything in Team
- SSO/SAML
- Dedicated support
- Custom SLAs
- Unlimited storage
- On-premise option
---
## Tech Stack Evolution
### Current
```
Next.js → React → Monaco Editor → localStorage
```
### Target
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND │
│ Next.js │ React │ Three.js │ Monaco │ Y.js (CRDT) │
├─────────────────────────────────────────────────────┤
│ BACKEND │
│ Node.js │ tRPC │ Prisma │ PostgreSQL │ Redis │
├─────────────────────────────────────────────────────┤
│ INFRASTRUCTURE │
│ Vercel │ AWS S3 │ CloudFlare │ Planetscale │
├─────────────────────────────────────────────────────┤
│ SERVICES │
│ Stripe │ Clerk Auth │ Liveblocks │ Sentry │ Posthog│
├─────────────────────────────────────────────────────┤
│ AI │
│ Claude API │ OpenAI │ Replicate (3D Gen) │
└─────────────────────────────────────────────────────┘
```
---
## Priority Implementation Order
### NOW (Next 2-3 months)
1. ✅ Avatar Toolkit (DONE)
2. 🔄 Visual Scripting MVP
3. 🔄 Live Game Preview (embedded)
4. 🔄 Asset Library v1
### NEXT (3-6 months)
5. Real-time Collaboration
6. Built-in Git
7. AI Code Generation v2
8. Marketplace MVP
### LATER (6-12 months)
9. Scene Editor
10. Animation Editor
11. Analytics Dashboard
12. One-Click Deploy
13. Backend-as-a-Service
### FUTURE (12+ months)
14. Enterprise Features
15. Learning Platform
16. Full Marketplace
17. Mobile App
---
## Competitive Positioning
| Feature | Roblox Studio | Unity | Unreal | AeThex |
|---------|--------------|-------|--------|--------|
| Browser-based | ❌ | ❌ | ❌ | ✅ |
| Cross-platform export | ❌ | Partial | ❌ | ✅ |
| AI code translation | ❌ | ❌ | ❌ | ✅ |
| Visual scripting | ❌ | ✅ | ✅ | 🔄 |
| Built-in marketplace | ✅ | ✅ | ✅ | 🔄 |
| Real-time collab | ❌ | Partial | ❌ | 🔄 |
| Free tier | ✅ | ✅ | ✅ | ✅ |
| Learning built-in | Partial | ❌ | ❌ | 🔄 |
**Our edge**: Browser-based + Cross-platform + AI-native
---
## Success Metrics
### Year 1
- 50,000 registered users
- 5,000 monthly active creators
- 500 marketplace listings
- $100K GMV on marketplace
### Year 2
- 250,000 registered users
- 25,000 monthly active creators
- 5,000 marketplace listings
- $1M GMV on marketplace
- 100 paying teams
### Year 3
- 1M registered users
- 100,000 monthly active creators
- 25,000 marketplace listings
- $10M GMV on marketplace
- 500 paying teams
- 10 enterprise clients

View file

@ -1,701 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AeThex Studio - IDE Mockup</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&family=JetBrains+Mono:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
background: #0a0a0a;
color: #e0e0e0;
overflow: hidden;
height: 100vh;
}
/* Scanline effect */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.15),
rgba(0, 0, 0, 0.15) 1px,
transparent 1px,
transparent 2px
);
pointer-events: none;
z-index: 1000;
}
/* Main Layout */
.ide-container {
display: flex;
flex-direction: column;
height: 100vh;
}
/* Title Bar */
.title-bar {
background: #0d0d0d;
border-bottom: 2px solid #1a1a1a;
padding: 8px 16px;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.title-bar::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, #ff0000 33%, #0066ff 33%, #0066ff 66%, #ffa500 66%);
}
.title-left {
display: flex;
align-items: center;
gap: 20px;
}
.logo-small {
font-size: 1.2em;
font-weight: 700;
letter-spacing: 3px;
background: linear-gradient(90deg, #ff0000, #0066ff, #ffa500);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.project-name {
color: #666;
font-size: 0.9em;
}
.project-name span {
color: #0066ff;
font-weight: 700;
}
.title-right {
display: flex;
gap: 15px;
font-size: 0.85em;
}
.status-indicator {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.status-dot.foundation { background: #ff0000; }
.status-dot.corporation { background: #0066ff; }
.status-dot.labs { background: #ffa500; }
@keyframes pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 8px currentColor; }
50% { opacity: 0.6; box-shadow: 0 0 4px currentColor; }
}
/* Main Content Area */
.main-content {
display: flex;
flex: 1;
overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 250px;
background: #0d0d0d;
border-right: 1px solid #1a1a1a;
display: flex;
flex-direction: column;
}
.sidebar-section {
border-bottom: 1px solid #1a1a1a;
}
.sidebar-header {
padding: 12px 16px;
font-size: 0.75em;
text-transform: uppercase;
letter-spacing: 2px;
color: #666;
display: flex;
align-items: center;
gap: 8px;
border-left: 3px solid;
}
.sidebar-header.foundation { border-color: #ff0000; }
.sidebar-header.corporation { border-color: #0066ff; }
.sidebar-header.labs { border-color: #ffa500; }
.file-tree {
padding: 8px 0;
}
.file-item {
padding: 6px 16px 6px 24px;
font-size: 0.85em;
cursor: pointer;
transition: background 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.file-item:hover {
background: #1a1a1a;
}
.file-item.active {
background: #1a1a1a;
border-left: 2px solid #0066ff;
}
.file-icon {
color: #666;
}
/* Editor Area */
.editor-area {
flex: 1;
display: flex;
flex-direction: column;
background: #0f0f0f;
}
.editor-tabs {
background: #0d0d0d;
border-bottom: 1px solid #1a1a1a;
display: flex;
padding: 0;
}
.editor-tab {
padding: 10px 20px;
font-size: 0.85em;
background: #0d0d0d;
border-right: 1px solid #1a1a1a;
cursor: pointer;
transition: background 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.editor-tab:hover {
background: #1a1a1a;
}
.editor-tab.active {
background: #0f0f0f;
border-bottom: 2px solid #0066ff;
}
.editor-content {
flex: 1;
overflow: auto;
padding: 20px;
}
.code-line {
display: flex;
font-size: 0.9em;
line-height: 1.6;
font-family: 'JetBrains Mono', monospace;
}
.line-number {
color: #333;
width: 40px;
text-align: right;
padding-right: 20px;
user-select: none;
}
.line-content {
flex: 1;
}
/* Syntax Highlighting */
.keyword { color: #ff0000; font-weight: 700; } /* Foundation - core functions */
.function { color: #0066ff; } /* Corporation - standard library */
.comment { color: #ffa500; font-style: italic; } /* Labs - experimental */
.string { color: #00ff88; }
.number { color: #ff6b9d; }
.variable { color: #e0e0e0; }
.operator { color: #999; }
/* Right Panel */
.right-panel {
width: 320px;
background: #0d0d0d;
border-left: 1px solid #1a1a1a;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 12px 16px;
font-size: 0.75em;
text-transform: uppercase;
letter-spacing: 2px;
border-bottom: 1px solid #1a1a1a;
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-content {
flex: 1;
overflow: auto;
padding: 16px;
}
.copilot-message {
margin-bottom: 16px;
padding: 12px;
background: #1a1a1a;
border-left: 3px solid;
font-size: 0.85em;
line-height: 1.6;
}
.copilot-message.labs {
border-color: #ffa500;
}
.copilot-message.foundation {
border-color: #ff0000;
}
.copilot-message.corporation {
border-color: #0066ff;
}
.copilot-label {
font-size: 0.75em;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
font-weight: 700;
}
.copilot-label.labs { color: #ffa500; }
.copilot-label.foundation { color: #ff0000; }
.copilot-label.corporation { color: #0066ff; }
/* Bottom Panel */
.bottom-panel {
height: 200px;
background: #0d0d0d;
border-top: 1px solid #1a1a1a;
display: flex;
flex-direction: column;
}
.bottom-tabs {
display: flex;
gap: 0;
border-bottom: 1px solid #1a1a1a;
}
.bottom-tab {
padding: 8px 16px;
font-size: 0.8em;
text-transform: uppercase;
letter-spacing: 1px;
cursor: pointer;
border-right: 1px solid #1a1a1a;
transition: background 0.2s;
}
.bottom-tab:hover {
background: #1a1a1a;
}
.bottom-tab.active {
background: #1a1a1a;
border-bottom: 2px solid #0066ff;
}
.terminal-output {
flex: 1;
overflow: auto;
padding: 12px;
font-size: 0.85em;
line-height: 1.6;
}
.terminal-line {
margin: 2px 0;
}
.terminal-line.foundation { color: #ff0000; }
.terminal-line.corporation { color: #0066ff; }
.terminal-line.labs { color: #ffa500; }
.terminal-line.success { color: #00ff00; }
.terminal-line.error { color: #ff0000; }
/* Network Visualization */
.network-viz {
position: fixed;
bottom: 220px;
right: 20px;
width: 300px;
background: rgba(13, 13, 13, 0.95);
border: 1px solid #1a1a1a;
padding: 16px;
font-size: 0.75em;
}
.network-viz-header {
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 12px;
color: #666;
}
.network-node {
display: flex;
align-items: center;
gap: 12px;
margin: 8px 0;
padding: 8px;
background: #0f0f0f;
}
.node-dot {
width: 12px;
height: 12px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.node-dot.foundation { background: #ff0000; }
.node-dot.corporation { background: #0066ff; }
.node-dot.labs { background: #ffa500; }
.node-info {
flex: 1;
}
.node-label {
font-weight: 700;
margin-bottom: 2px;
}
.node-status {
color: #666;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="ide-container">
<!-- Title Bar -->
<div class="title-bar">
<div class="title-left">
<div class="logo-small">AETHEX STUDIO</div>
<div class="project-name">Project: <span>AeThex Terminal</span></div>
</div>
<div class="title-right">
<div class="status-indicator">
<div class="status-dot foundation"></div>
<span style="color: #666;">Foundation</span>
</div>
<div class="status-indicator">
<div class="status-dot corporation"></div>
<span style="color: #666;">Corporation</span>
</div>
<div class="status-indicator">
<div class="status-dot labs"></div>
<span style="color: #666;">Labs</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-section">
<div class="sidebar-header foundation">
<span>🔴</span>
<span>Foundation APIs</span>
</div>
<div class="file-tree">
<div class="file-item">
<span class="file-icon">📄</span>
<span>auth.ts</span>
</div>
<div class="file-item">
<span class="file-icon">📄</span>
<span>passport.ts</span>
</div>
<div class="file-item">
<span class="file-icon">📄</span>
<span>security.ts</span>
</div>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-header corporation">
<span>🔵</span>
<span>Corporation Services</span>
</div>
<div class="file-tree">
<div class="file-item active">
<span class="file-icon">📄</span>
<span>terminal.ts</span>
</div>
<div class="file-item">
<span class="file-icon">📄</span>
<span>deployment.ts</span>
</div>
<div class="file-item">
<span class="file-icon">📄</span>
<span>analytics.ts</span>
</div>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-header labs">
<span>🟡</span>
<span>Labs Experimental</span>
</div>
<div class="file-tree">
<div class="file-item">
<span class="file-icon">📄</span>
<span>copilot.ts</span>
</div>
<div class="file-item">
<span class="file-icon">📄</span>
<span>nexus-v2.ts</span>
</div>
<div class="file-item">
<span class="file-icon">📄</span>
<span>experimental.ts ⚠</span>
</div>
</div>
</div>
</div>
<!-- Editor Area -->
<div class="editor-area">
<div class="editor-tabs">
<div class="editor-tab active">
<span>📄</span>
<span>terminal.ts</span>
</div>
<div class="editor-tab">
<span>📄</span>
<span>nexus-v2.ts</span>
</div>
</div>
<div class="editor-content">
<div class="code-line">
<div class="line-number">1</div>
<div class="line-content"><span class="comment">// AeThex Terminal - Corporation Service</span></div>
</div>
<div class="code-line">
<div class="line-number">2</div>
<div class="line-content"><span class="comment">// Powered by Foundation authentication & Labs Nexus Engine</span></div>
</div>
<div class="code-line">
<div class="line-number">3</div>
<div class="line-content"></div>
</div>
<div class="code-line">
<div class="line-number">4</div>
<div class="line-content"><span class="keyword">import</span> <span class="operator">{</span> <span class="variable">authenticate</span><span class="operator">,</span> <span class="variable">verifySession</span> <span class="operator">}</span> <span class="keyword">from</span> <span class="string">'@aethex/foundation/auth'</span><span class="operator">;</span></div>
</div>
<div class="code-line">
<div class="line-number">5</div>
<div class="line-content"><span class="keyword">import</span> <span class="operator">{</span> <span class="variable">NexusEngine</span> <span class="operator">}</span> <span class="keyword">from</span> <span class="string">'@aethex/labs/nexus-v2'</span><span class="operator">;</span></div>
</div>
<div class="code-line">
<div class="line-number">6</div>
<div class="line-content"><span class="keyword">import</span> <span class="operator">{</span> <span class="variable">DeploymentManager</span> <span class="operator">}</span> <span class="keyword">from</span> <span class="string">'@aethex/corporation/deploy'</span><span class="operator">;</span></div>
</div>
<div class="code-line">
<div class="line-number">7</div>
<div class="line-content"></div>
</div>
<div class="code-line">
<div class="line-number">8</div>
<div class="line-content"><span class="keyword">export</span> <span class="keyword">class</span> <span class="function">AeThexTerminal</span> <span class="operator">{</span></div>
</div>
<div class="code-line">
<div class="line-number">9</div>
<div class="line-content"> <span class="keyword">private</span> <span class="variable">nexus</span><span class="operator">:</span> <span class="function">NexusEngine</span><span class="operator">;</span></div>
</div>
<div class="code-line">
<div class="line-number">10</div>
<div class="line-content"> <span class="keyword">private</span> <span class="variable">deployer</span><span class="operator">:</span> <span class="function">DeploymentManager</span><span class="operator">;</span></div>
</div>
<div class="code-line">
<div class="line-number">11</div>
<div class="line-content"></div>
</div>
<div class="code-line">
<div class="line-number">12</div>
<div class="line-content"> <span class="keyword">async</span> <span class="function">deploy</span><span class="operator">(</span><span class="variable">options</span><span class="operator">:</span> <span class="function">DeployOptions</span><span class="operator">)</span> <span class="operator">{</span></div>
</div>
<div class="code-line">
<div class="line-number">13</div>
<div class="line-content"> <span class="comment">// Foundation: Verify authentication</span></div>
</div>
<div class="code-line">
<div class="line-number">14</div>
<div class="line-content"> <span class="keyword">const</span> <span class="variable">session</span> <span class="operator">=</span> <span class="keyword">await</span> <span class="function">verifySession</span><span class="operator">();</span></div>
</div>
<div class="code-line">
<div class="line-number">15</div>
<div class="line-content"> <span class="keyword">if</span> <span class="operator">(!</span><span class="variable">session</span><span class="operator">.</span><span class="variable">valid</span><span class="operator">)</span> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="function">Error</span><span class="operator">(</span><span class="string">'Authentication failed'</span><span class="operator">);</span></div>
</div>
<div class="code-line">
<div class="line-number">16</div>
<div class="line-content"></div>
</div>
<div class="code-line">
<div class="line-number">17</div>
<div class="line-content"> <span class="comment">// Labs: Compile with experimental Nexus Engine</span></div>
</div>
<div class="code-line">
<div class="line-number">18</div>
<div class="line-content"> <span class="keyword">const</span> <span class="variable">build</span> <span class="operator">=</span> <span class="keyword">await</span> <span class="keyword">this</span><span class="operator">.</span><span class="variable">nexus</span><span class="operator">.</span><span class="function">compile</span><span class="operator">(</span><span class="variable">options</span><span class="operator">.</span><span class="variable">source</span><span class="operator">);</span></div>
</div>
<div class="code-line">
<div class="line-number">19</div>
<div class="line-content"></div>
</div>
<div class="code-line">
<div class="line-number">20</div>
<div class="line-content"> <span class="comment">// Corporation: Deploy to production infrastructure</span></div>
</div>
<div class="code-line">
<div class="line-number">21</div>
<div class="line-content"> <span class="keyword">return</span> <span class="keyword">await</span> <span class="keyword">this</span><span class="operator">.</span><span class="variable">deployer</span><span class="operator">.</span><span class="function">deploy</span><span class="operator">(</span><span class="variable">build</span><span class="operator">);</span></div>
</div>
<div class="code-line">
<div class="line-number">22</div>
<div class="line-content"> <span class="operator">}</span></div>
</div>
<div class="code-line">
<div class="line-number">23</div>
<div class="line-content"><span class="operator">}</span></div>
</div>
</div>
</div>
<!-- Right Panel - Copilot -->
<div class="right-panel">
<div class="panel-header">
<span>AeThex Copilot</span>
<span style="color: #ffa500;">⚠ LABS</span>
</div>
<div class="panel-content">
<div class="copilot-message foundation">
<div class="copilot-label foundation">Foundation Mode</div>
<div>This code properly uses Foundation authentication. Consider adding rate limiting from @aethex/foundation/security for production use.</div>
</div>
<div class="copilot-message labs">
<div class="copilot-label labs">Labs Mode</div>
<div>Nice use of Nexus v2! Want to try the experimental parallel compilation feature? It's 40% faster but still in beta.</div>
</div>
<div class="copilot-message corporation">
<div class="copilot-label corporation">Corporation Mode</div>
<div>DeploymentManager is production-ready. This code follows AeThex Corporation best practices for Railway deployment.</div>
</div>
</div>
</div>
</div>
<!-- Bottom Panel - Terminal -->
<div class="bottom-panel">
<div class="bottom-tabs">
<div class="bottom-tab active">Terminal</div>
<div class="bottom-tab">Problems</div>
<div class="bottom-tab">Output</div>
<div class="bottom-tab">Debug Console</div>
</div>
<div class="terminal-output">
<div class="terminal-line foundation">[FOUNDATION] Authenticating user AX-2847-ANDERSON...</div>
<div class="terminal-line foundation">[FOUNDATION] Security clearance verified ✓</div>
<div class="terminal-line labs">[LABS] Initializing Nexus Engine v2.0-beta...</div>
<div class="terminal-line labs">[LABS] Experimental compilation started ⚠</div>
<div class="terminal-line labs">[LABS] Build completed in 1.24s</div>
<div class="terminal-line corporation">[CORPORATION] Connecting to production infrastructure...</div>
<div class="terminal-line corporation">[CORPORATION] Railway deployment #2847 initiated</div>
<div class="terminal-line corporation">[CORPORATION] Services: 12/12 online ✓</div>
<div class="terminal-line success">[SUCCESS] Deployment complete - https://terminal.aethex.io</div>
</div>
</div>
<!-- Network Visualization Overlay -->
<div class="network-viz">
<div class="network-viz-header">Trinity Infrastructure Status</div>
<div class="network-node">
<div class="node-dot foundation"></div>
<div class="node-info">
<div class="node-label" style="color: #ff0000;">Foundation</div>
<div class="node-status">Auth • Security • APIs</div>
</div>
</div>
<div class="network-node">
<div class="node-dot corporation"></div>
<div class="node-info">
<div class="node-label" style="color: #0066ff;">Corporation</div>
<div class="node-status">Deploy • Analytics • Production</div>
</div>
</div>
<div class="network-node">
<div class="node-dot labs"></div>
<div class="node-info">
<div class="node-label" style="color: #ffa500;">Labs</div>
<div class="node-status">Nexus v2 • Copilot • Experimental</div>
</div>
</div>
</div>
</div>
</body>
</html>

19
app/App.tsx Normal file
View file

@ -0,0 +1,19 @@
import { useState } from 'react';
import { Toaster } from '@/components/ui/sonner';
import { CodeEditor } from '@/components/CodeEditor';
import { AIChat } from '@/components/AIChat';
import { Toolbar } from '@/components/Toolbar';
import { TemplatesDrawer } from '@/components/TemplatesDrawer';
import { FileTree, FileNode } from '@/components/FileTree';
import { FileTabs } from '@/components/FileTabs';
import { PreviewModal } from '@/components/PreviewModal';
// Removed named imports for WelcomeDialog and NewProjectModal. Use lazy-loaded versions from src/App.tsx.
import { ConsolePanel } from '@/components/ConsolePanel';
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
import { useKV } from '@github/spark/hooks';
import { useIsMobile } from '@/hooks/use-mobile';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { toast } from 'sonner';
export { default } from '../src/App';

154
app/globals.css Normal file
View file

@ -0,0 +1,154 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* Default Dark Theme */
:root, .theme-dark {
--background: #0a0a0f;
--surface: #1a1a1f;
--primary: #8b5cf6;
--primary-light: #a78bfa;
--primary-dark: #7c3aed;
--secondary: #ec4899;
--accent: #06b6d4;
--border: #2a2a2f;
--foreground: #ffffff;
--muted: #6b7280;
}
/* Light Theme */
.theme-light {
--background: #ffffff;
--surface: #f9fafb;
--primary: #7c3aed;
--primary-light: #8b5cf6;
--primary-dark: #6d28d9;
--secondary: #db2777;
--accent: #0891b2;
--border: #e5e7eb;
--foreground: #111827;
--muted: #6b7280;
}
/* Synthwave Theme */
.theme-synthwave {
--background: #2b213a;
--surface: #241b2f;
--primary: #ff6ac1;
--primary-light: #ff8ad8;
--primary-dark: #ff4aaa;
--secondary: #9d72ff;
--accent: #72f1b8;
--border: #495495;
--foreground: #f8f8f2;
--muted: #a599e9;
}
/* Forest Theme */
.theme-forest {
--background: #0d1b1e;
--surface: #1a2f33;
--primary: #2dd4bf;
--primary-light: #5eead4;
--primary-dark: #14b8a6;
--secondary: #34d399;
--accent: #a7f3d0;
--border: #234e52;
--foreground: #ecfdf5;
--muted: #6ee7b7;
}
/* Ocean Theme */
.theme-ocean {
--background: #0c1821;
--surface: #1b2838;
--primary: #3b82f6;
--primary-light: #60a5fa;
--primary-dark: #2563eb;
--secondary: #06b6d4;
--accent: #38bdf8;
--border: #1e3a5f;
--foreground: #dbeafe;
--muted: #7dd3fc;
}
* {
border-color: var(--border);
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: var(--font-inter), 'Inter', sans-serif;
}
code, pre {
font-family: var(--font-jetbrains-mono), 'JetBrains Mono', monospace;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: #1a1a1f;
}
::-webkit-scrollbar-thumb {
background: #2a2a2f;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #3a3a3f;
}
/* Animation classes */
.animate-slide-in {
animation: slideIn 0.2s ease-out;
}
.animate-fade-in {
animation: fadeIn 0.2s ease-in-out;
}
@keyframes slideIn {
from {
transform: translateY(-10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Monaco Editor theme overrides */
.monaco-editor .margin {
background-color: #0a0a0f !important;
}
.monaco-editor {
background-color: #0a0a0f !important;
}

16
app/index.html Normal file
View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AeThex Studio - Roblox Lua Editor</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link href="/app/main.css" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/app/App.tsx"></script>
</body>
</html>

32
app/layout.tsx Normal file
View file

@ -0,0 +1,32 @@
import type { Metadata } from "next";
import { Inter, JetBrains_Mono } from "next/font/google";
import "./globals.css";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
});
export const metadata: Metadata = {
title: "AeThex Studio - Cross-Platform Game Development IDE",
description: "Professional game development IDE for Roblox, Web, Mobile, and Desktop",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="dark">
<body className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased bg-background text-white`}>
{children}
</body>
</html>
);
}

61
app/main.css Normal file
View file

@ -0,0 +1,61 @@
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
* {
border-color: var(--border);
}
body {
font-family: 'Inter', sans-serif;
background:
radial-gradient(circle at 20% 50%, oklch(0.20 0.08 265 / 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, oklch(0.20 0.08 150 / 0.2) 0%, transparent 50%),
oklch(0.15 0.02 265);
background-attachment: fixed;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Space Grotesk', sans-serif;
}
code, pre {
font-family: 'JetBrains Mono', monospace;
}
}
@layer components {
.btn-accent-hover {
transition: all 0.2s ease;
}
.btn-accent-hover:hover {
transform: scale(1.02);
filter: brightness(1.1);
}
.btn-accent-hover:active {
transform: scale(0.98);
}
}
:root {
--background: oklch(0.15 0.02 265);
--foreground: oklch(0.85 0.03 265);
--card: oklch(0.20 0.03 265);
--card-foreground: oklch(0.85 0.03 265);
--popover: oklch(0.20 0.03 265);
--popover-foreground: oklch(0.85 0.03 265);
--primary: oklch(0.45 0.20 265);
--primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.25 0.04 265);
--secondary-foreground: oklch(0.85 0.03 265);
--muted: oklch(0.22 0.03 265);
--muted-foreground: oklch(0.55 0.03 265);
--accent: oklch(0.75 0.20 150);
--accent-foreground: oklch(0.15 0.02 265);
--destructive: oklch(0.55 0.22 25);
--destructive-foreground: oklch(0.98 0 0);
--border: oklch(0.30 0.04 265);
--input: oklch(0.30 0.04 265);
--ring: oklch(0.75 0.20 150);
--radius: 0.5rem;
}

17
app/page.tsx Normal file
View file

@ -0,0 +1,17 @@
"use client";
import React from 'react';
import { Navbar } from '@/components/Navbar';
import { FileTree } from '@/components/FileTree';
import { CodeEditor } from '@/components/CodeEditor';
import { AIAssistant } from '@/components/AIAssistant';
import { ConsolePanel } from '@/components/ConsolePanel';
import { NewProjectModal } from '@/components/NewProjectModal';
import { useAppStore } from '@/store/app-store';
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react';
import App from '../src/App';
export default function Home() {
return <App />;
}

View file

@ -1,7 +0,0 @@
# Settings to manage and configure a Firebase App Hosting backend.
# https://firebase.google.com/docs/app-hosting/configure
runConfig:
# Increase this value if you'd like to automatically spin up
# more instances in response to increased traffic.
maxInstances: 1

View file

@ -1,11 +1,11 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"config": "tailwind.config.js",
"css": "src/main.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""

View file

@ -6,7 +6,7 @@ import { useEditorStore, FileNode } from '@/store/editor-store';
import { cn, getFileIcon, getPlatformIcon } from '@/lib/utils';
export function FileTree() {
const { files, openFile, moveFile } = useEditorStore();
const { files, openFile } = useEditorStore();
const [expandedFolders, setExpandedFolders] = React.useState<Set<string>>(
new Set(['roblox', 'web', 'mobile', 'desktop', 'shared'])
);
@ -26,20 +26,15 @@ export function FileTree() {
const renderNode = (node: FileNode, depth: number = 0) => {
const isExpanded = expandedFolders.has(node.id);
const isFolder = node.type === 'folder';
return (
<div key={node.id} draggable onDragStart={e => e.dataTransfer.setData('fileId', node.id)} onDrop={e => {
e.preventDefault();
const fileId = e.dataTransfer.getData('fileId');
if (fileId && fileId !== node.id && isFolder) {
moveFile(fileId, node.id);
}
}} onDragOver={e => isFolder && e.preventDefault()}>
<div key={node.id}>
<div
className={cn(
"flex items-center gap-3 px-2 py-1 cursor-pointer hover:bg-surface/50 transition-colors",
"flex items-center gap-2 px-2 py-1 cursor-pointer hover:bg-surface/50 transition-colors",
"text-sm"
)}
style={{ paddingLeft: `${depth * 14 + 8}px` }}
style={{ paddingLeft: `${depth * 12 + 8}px` }}
onClick={() => isFolder ? toggleFolder(node.id) : openFile(node)}
>
{isFolder && (
@ -48,16 +43,20 @@ export function FileTree() {
</span>
)}
{!isFolder && <span className="w-4" />}
<span className="text-lg mr-1">
<span className="text-lg">
{isFolder ? (isExpanded ? '📂' : '📁') : getFileIcon(node.name)}
</span>
<span className="flex-1 truncate text-gray-200">{node.name}</span>
{node.platform && (
<span className="text-xs opacity-50 ml-1">
<span className="text-xs opacity-50">
{getPlatformIcon(node.platform)}
</span>
)}
</div>
{isFolder && isExpanded && node.children && (
<div>
{node.children.map(child => renderNode(child, depth + 1))}
@ -82,7 +81,7 @@ export function FileTree() {
</button>
</div>
</div>
<div className="px-3 pt-2 pb-1 text-xs text-gray-500 font-semibold">PROJECT</div>
<div className="flex-1 overflow-y-auto">
{files.map((node: any) => renderNode(node))}
</div>

View file

@ -11,7 +11,7 @@ import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Progress } from '@/components/ui/progress';
import { useAppStore } from '@/store/app-store';
import { templates, Template } from '../lib/templates';
import { templates } from '@/lib/templates';
import { getPlatformIcon } from '@/lib/utils';
export function NewProjectModal() {

View file

@ -64,7 +64,7 @@ export function NexusSyncMonitor() {
{expandedItems.has('world') && (
<div className="ml-8 space-y-1 mt-1">
<div className="text-gray-500"> objects: [...]</div>
<div className="text-gray-500"> weather: &quot;sunny&quot;</div>
<div className="text-gray-500"> weather: "sunny"</div>
</div>
)}
</div>

View file

@ -1,4 +0,0 @@
// StudioBottomPanel is now obsolete and handled by ConsolePanel. This file is intentionally left blank.
// This file is kept only to avoid import errors during refactor.
export default function StudioBottomPanel() { return null; }

View file

@ -1,51 +0,0 @@
"use client";
import { useEditorStore } from "../store/editor-zustand";
function StudioEditor() {
const openTabs = useEditorStore((s) => s.openTabs);
const activeTabId = useEditorStore((s) => s.activeTabId);
const setActiveTab = useEditorStore((s) => s.setActiveTab);
const closeTab = useEditorStore((s) => s.closeTab);
// Find active file
const activeFile = openTabs.find(f => f.id === activeTabId);
return (
<div className="editor-area">
<div className="editor-tabs">
{openTabs.length === 0 && (
<div className="editor-tab empty">No files open</div>
)}
{openTabs.map((file) => (
<div
key={file.id}
className={"editor-tab" + (activeTabId === file.id ? " active" : "")}
onClick={() => setActiveTab(file.id)}
style={{ cursor: "pointer" }}
>
<span>📄</span>
<span>{file.name}</span>
<span
className="close-btn"
style={{ marginLeft: 8, color: "#888", cursor: "pointer" }}
onClick={e => { e.stopPropagation(); closeTab(file.id); }}
>×</span>
</div>
))}
</div>
<div className="editor-content">
{activeFile ? (
activeFile.content.split("\n").map((line, i) => (
<div className="code-line" key={i}>
<div className="line-number">{i + 1}</div>
<div className="line-content">{line}</div>
</div>
))
) : (
<div style={{ color: "#888", padding: 32, textAlign: "center" }}>
No file open. Select a file to begin.
</div>
)}
</div>
</div>
);
}
export default StudioEditor;

View file

@ -1,81 +0,0 @@
"use client";
import StudioSidebar from "./StudioSidebar";
import StudioEditor from "./StudioEditor";
import StudioRightPanel from "./StudioRightPanel";
import StudioBottomPanel from "./StudioBottomPanel";
import StudioNetworkViz from "./StudioNetworkViz";
import { useState } from "react";
import { toast } from "sonner";
export default function StudioLayout({ children }: { children?: React.ReactNode }) {
const [platform, setPlatform] = useState("Roblox");
const [actionsOpen, setActionsOpen] = useState(false);
const handlePlatformChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setPlatform(e.target.value);
toast.success(`Platform changed to ${e.target.value}`);
};
const handleAction = (action: string) => {
toast.info(`${action} action triggered!`);
setActionsOpen(false);
};
return (
<div className="ide-container">
{/* Title Bar */}
<div className="title-bar" style={{ position: 'relative', zIndex: 10 }}>
<div className="flex items-center w-full gap-4">
<div className="logo-small">AETHEX STUDIO</div>
<div className="project-name">Project: <span>AeThex Terminal</span></div>
<div className="flex items-center gap-3 ml-6">
<span className="text-xs">Platform:</span>
<select
className="bg-[#22242A] text-xs px-3 py-1 rounded border border-blue-500 focus:ring-2 focus:ring-blue-400"
value={platform}
onChange={handlePlatformChange}
>
<option>Roblox</option>
<option>Web</option>
<option>Mobile</option>
<option>Desktop</option>
</select>
</div>
<div className="group relative">
<button
className="px-3 py-1 text-xs bg-gray-700 rounded hover:bg-gray-800 flex items-center gap-1"
tabIndex={0}
aria-label="Actions"
onClick={() => setActionsOpen((v) => !v)}
>
<span>Actions</span>
<svg className="w-3 h-3 ml-1" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" /></svg>
</button>
{actionsOpen && (
<div className="absolute right-0 mt-2 w-32 bg-[#23272F] border border-border rounded shadow-lg z-50">
<button className="block w-full text-left px-4 py-2 text-xs hover:bg-blue-600" onClick={() => handleAction('Save')}>Save</button>
<button className="block w-full text-left px-4 py-2 text-xs hover:bg-green-600" onClick={() => handleAction('Run')}>Run</button>
<button className="block w-full text-left px-4 py-2 text-xs hover:bg-gray-600" onClick={() => handleAction('Export')}>Export</button>
<button className="block w-full text-left px-4 py-2 text-xs hover:bg-gray-600" onClick={() => handleAction('Settings')}>Settings</button>
</div>
)}
</div>
{/* Legend */}
<div className="flex items-center gap-4 ml-auto">
<span className="flex items-center gap-1 text-xs"><span className="w-2 h-2 rounded-full bg-red-500 inline-block"></span> Foundation</span>
<span className="flex items-center gap-1 text-xs"><span className="w-2 h-2 rounded-full bg-blue-500 inline-block"></span> Corporation</span>
<span className="flex items-center gap-1 text-xs"><span className="w-2 h-2 rounded-full bg-yellow-400 inline-block"></span> Labs</span>
</div>
</div>
</div>
<div className="main-content">
<div className="sidebar"><StudioSidebar /></div>
<div className="editor-area"><StudioEditor /></div>
<div className="right-panel"><StudioRightPanel /></div>
</div>
<div className="bottom-panel"><StudioBottomPanel /></div>
<div className="network-viz"><StudioNetworkViz /></div>
{children}
</div>
);
}

View file

@ -1,29 +0,0 @@
function StudioNetworkViz() {
return (
<section className="network-viz">
<div className="network-viz-header">Trinity Infrastructure Status</div>
<div className="network-node">
<div className="node-dot foundation"></div>
<div className="node-info">
<div className="node-label" style={{ color: "#ff0000" }}>Foundation</div>
<div className="node-status">Auth Security APIs</div>
</div>
</div>
<div className="network-node">
<div className="node-dot corporation"></div>
<div className="node-info">
<div className="node-label" style={{ color: "#0066ff" }}>Corporation</div>
<div className="node-status">Deploy Analytics Production</div>
</div>
</div>
<div className="network-node">
<div className="node-dot labs"></div>
<div className="node-info">
<div className="node-label" style={{ color: "#ffa500" }}>Labs</div>
<div className="node-status">Nexus v2 Copilot Experimental</div>
</div>
</div>
</section>
);
}
export default StudioNetworkViz;

View file

@ -1,26 +0,0 @@
function StudioRightPanel() {
return (
<div className="right-panel">
<div className="panel-header">
<span>AeThex Copilot</span>
<span style={{ color: '#ffa500' }}> LABS</span>
</div>
<div className="panel-content">
<div className="copilot-message foundation">
<div className="copilot-label foundation">Foundation Mode</div>
<div>This code properly uses Foundation authentication. Consider adding rate limiting from @aethex/foundation/security for production use.</div>
</div>
<div className="copilot-message labs">
<div className="copilot-label labs">Labs Mode</div>
<div>Nice use of Nexus v2! Want to try the experimental parallel compilation feature? It&apos;s 40% faster but still in beta.</div>
</div>
<div className="copilot-message corporation">
<div className="copilot-label corporation">Corporation Mode</div>
<div>DeploymentManager is production-ready. This code follows AeThex Corporation best practices for Railway deployment.</div>
</div>
</div>
</div>
);
}
export default StudioRightPanel;

View file

@ -1,75 +0,0 @@
"use client";
import { useEditorStore, FileTab } from "../store/editor-zustand";
function StudioSidebar() {
const files = useEditorStore((s: any) => s.files);
const openFile = useEditorStore((s: any) => s.openFile);
const activeTabId = useEditorStore((s: any) => s.activeTabId);
// Split files into mockup sections
const foundationFiles = files.filter((f: FileTab) => f.name === "auth.ts" || f.name === "passport.ts" || f.name === "security.ts");
const corporationFiles = files.filter((f: FileTab) => f.name === "terminal.ts" || f.name === "deployment.ts" || f.name === "analytics.ts");
const labsFiles = files.filter((f: FileTab) => f.name === "copilot.ts" || f.name === "nexus-v2.ts" || f.name === "experimental.ts");
return (
<div className="sidebar">
<div className="sidebar-section">
<div className="sidebar-header foundation">
<span>🔴</span>
<span>Foundation APIs</span>
</div>
<div className="file-tree">
{foundationFiles.map((file: FileTab) => (
<div
key={file.id}
className={"file-item" + (activeTabId === file.id ? " active" : "")}
onClick={() => openFile(file)}
style={{ cursor: "pointer" }}
>
<span className="file-icon">📄</span>
<span>{file.name}</span>
</div>
))}
</div>
</div>
<div className="sidebar-section">
<div className="sidebar-header corporation">
<span>🔵</span>
<span>Corporation Services</span>
</div>
<div className="file-tree">
{corporationFiles.map((file: FileTab) => (
<div
key={file.id}
className={"file-item" + (activeTabId === file.id ? " active" : "")}
onClick={() => openFile(file)}
style={{ cursor: "pointer" }}
>
<span className="file-icon">📄</span>
<span>{file.name}</span>
</div>
))}
</div>
</div>
<div className="sidebar-section">
<div className="sidebar-header labs">
<span>🟡</span>
<span>Labs Experimental</span>
</div>
<div className="file-tree">
{labsFiles.map((file: FileTab) => (
<div
key={file.id}
className={"file-item" + (activeTabId === file.id ? " active" : "")}
onClick={() => openFile(file)}
style={{ cursor: "pointer" }}
>
<span className="file-icon">📄</span>
<span>{file.name}</span>
</div>
))}
</div>
</div>
</div>
);
}
export default StudioSidebar;

View file

@ -0,0 +1,464 @@
# AeThex Studio - Feature Architecture
## Immediate Priority Features (Next Sprint)
---
## 1. VISUAL SCRIPTING SYSTEM
### Why It's Critical
- 70% of Roblox creators prefer visual tools
- Lowers barrier to entry massively
- Differentiator from text-only IDEs
### Architecture
```
src/
├── components/
│ └── visual-scripting/
│ ├── VisualScriptingCanvas.tsx # Main React Flow canvas
│ ├── NodePalette.tsx # Draggable node library
│ ├── nodes/
│ │ ├── EventNodes.tsx # OnPlayerJoin, OnTouch, etc.
│ │ ├── LogicNodes.tsx # If, Loop, Wait, etc.
│ │ ├── ActionNodes.tsx # SetProperty, Destroy, etc.
│ │ ├── DataNodes.tsx # Variables, Math, etc.
│ │ └── CustomNodes.tsx # User-defined functions
│ ├── NodeInspector.tsx # Edit node properties
│ ├── ConnectionLine.tsx # Custom edge rendering
│ └── CodePreview.tsx # Live Lua/Verse output
├── lib/
│ └── visual-scripting/
│ ├── node-definitions.ts # All node type definitions
│ ├── code-generator.ts # Nodes → Code conversion
│ ├── code-parser.ts # Code → Nodes (reverse)
│ └── validation.ts # Check for errors
```
### Node Types
```typescript
// Event Nodes (Green) - Entry points
- OnPlayerJoin
- OnPlayerLeave
- OnPartTouch
- OnClick
- OnKeyPress
- OnTimer
- OnValueChange
// Logic Nodes (Blue) - Control flow
- If/Else
- For Loop
- While Loop
- Wait/Delay
- Random Branch
- Switch/Case
// Action Nodes (Purple) - Do things
- Print/Log
- SetProperty
- CreatePart
- DestroyObject
- PlaySound
- TweenProperty
- FireEvent
- CallFunction
// Data Nodes (Orange) - Values
- Number
- String
- Boolean
- Variable (Get/Set)
- MathOperation
- StringOperation
- TableOperation
// Reference Nodes (Yellow) - Game objects
- GetPlayer
- GetPart
- FindFirstChild
- GetService
- GetChildren
```
### Tech Stack
- **React Flow** - Node canvas library
- **Zustand** - State management for nodes
- **Monaco** - Side-by-side code preview
---
## 2. LIVE GAME PREVIEW
### Why It's Critical
- Can't test without leaving IDE = friction
- Immediate feedback loop is essential
- See changes in real-time
### Architecture
```
src/
├── components/
│ └── preview/
│ ├── GamePreview.tsx # Main preview container
│ ├── PreviewCanvas.tsx # Three.js 3D viewport
│ ├── PreviewControls.tsx # Play/Pause/Reset
│ ├── PreviewConsole.tsx # Output logs
│ ├── DeviceFrame.tsx # Phone/tablet mockup
│ └── PlayerSimulator.tsx # Fake player for testing
├── lib/
│ └── preview/
│ ├── lua-interpreter.ts # Run Lua in browser (Fengari)
│ ├── roblox-api-mock.ts # Mock Roblox APIs
│ ├── scene-manager.ts # 3D scene setup
│ └── hot-reload.ts # Update without restart
```
### Capabilities
```
┌─────────────────────────────────────────────────────┐
│ Preview [▶️] [⏸️] [🔄] │
├─────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 3D Game View │ │
│ │ │ │
│ │ [Cube] [Player] │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ Console: │
│ > Player touched Part1 │
│ > Score updated: 10 │
│ > [Error] Line 15: attempt to index nil │
└─────────────────────────────────────────────────────┘
```
### Tech Stack
- **Three.js** - 3D rendering
- **Fengari** - Lua interpreter in JS
- **Roblox API Mocks** - Simulate game.Players, workspace, etc.
---
## 3. ASSET LIBRARY
### Why It's Critical
- Games need more than code
- Models, textures, sounds
- Professional workflow
### Architecture
```
src/
├── components/
│ └── assets/
│ ├── AssetLibrary.tsx # Main panel
│ ├── AssetBrowser.tsx # Grid/list view
│ ├── AssetUploader.tsx # Drag-drop upload
│ ├── AssetPreview.tsx # 3D/2D/audio preview
│ ├── AssetInspector.tsx # Metadata, tags
│ └── AssetSearch.tsx # Search + filters
├── lib/
│ └── assets/
│ ├── asset-types.ts # Type definitions
│ ├── asset-processor.ts # Optimize on upload
│ ├── format-converter.ts # GLB→FBX, etc.
│ └── storage.ts # IndexedDB + cloud sync
```
### Supported Formats
```
3D Models: .glb, .gltf, .fbx, .obj
Textures: .png, .jpg, .webp, .svg
Audio: .mp3, .wav, .ogg
Data: .json, .csv
Animations: .fbx (with anims), .bvh
```
### Features
- Drag-drop upload
- Auto-optimization per platform
- AI tagging (auto-detect "sword", "tree", etc.)
- Version history
- Cloud sync across devices
- Platform-specific export (Roblox mesh → UEFN static mesh)
---
## 4. REAL-TIME COLLABORATION
### Why It's Critical
- Teams are the norm now
- Remote work is standard
- Google Docs set the expectation
### Architecture
```
src/
├── components/
│ └── collaboration/
│ ├── PresenceCursors.tsx # See teammate cursors
│ ├── CollabPanel.tsx # Who's online
│ ├── VoiceChat.tsx # Audio (optional)
│ ├── ChatSidebar.tsx # Text chat
│ └── CommentThread.tsx # Inline comments
├── lib/
│ └── collaboration/
│ ├── crdt.ts # Conflict-free sync (Yjs)
│ ├── presence.ts # Cursor positions
│ ├── awareness.ts # Who's editing what
│ └── websocket.ts # Real-time connection
```
### Tech Stack
- **Yjs** - CRDT for conflict-free editing
- **Liveblocks** or **PartyKit** - Real-time infrastructure
- **WebRTC** - Voice chat (optional)
---
## 5. MARKETPLACE
### Why It's Critical
- Monetization for creators
- Shortcuts for developers
- Platform stickiness (they stay for the ecosystem)
### Architecture
```
src/
├── components/
│ └── marketplace/
│ ├── MarketplaceBrowser.tsx # Browse listings
│ ├── ListingCard.tsx # Product preview
│ ├── ListingDetail.tsx # Full product page
│ ├── SellerDashboard.tsx # For sellers
│ ├── PurchaseFlow.tsx # Buy flow
│ └── ReviewSection.tsx # Ratings
├── lib/
│ └── marketplace/
│ ├── types.ts # Listing, Purchase, etc.
│ ├── search.ts # Algolia/Meilisearch
│ ├── payments.ts # Stripe integration
│ └── licensing.ts # License management
```
### Categories
```
Scripts & Systems
├── Combat Systems
├── Vehicle Physics
├── Inventory Systems
├── Dialogue Systems
├── Economy/Shop Systems
└── Admin Tools
3D Assets
├── Characters
├── Environments
├── Props
├── Vehicles
└── Effects
UI Kits
├── Menus
├── HUDs
├── Shops
└── Inventory UIs
Audio
├── Music
├── Sound Effects
└── Ambient
```
---
## 6. AI CODE GENERATION V2
### Why It's Critical
- Current AI is just translation
- "Build me X" is the dream
- Massive productivity boost
### Architecture
```
src/
├── lib/
│ └── ai/
│ ├── code-generator.ts # Generate from prompt
│ ├── code-explainer.ts # Explain code
│ ├── bug-detector.ts # Find issues
│ ├── optimizer.ts # Suggest improvements
│ ├── documentation.ts # Auto-generate docs
│ └── prompts/
│ ├── roblox-system.md # Roblox context
│ ├── uefn-system.md # UEFN context
│ └── generation-templates.md # Structured output
```
### Capabilities
```
User: "Create a round-based game with teams"
AI generates:
├── RoundManager.lua
│ ├── startRound()
│ ├── endRound()
│ ├── checkWinCondition()
│ └── Configuration (roundTime, teamSize)
├── TeamManager.lua
│ ├── assignTeams()
│ ├── balanceTeams()
│ └── getTeamScore()
├── ScoreboardUI.lua
│ └── Full UI with team scores
└── README.md
└── Setup instructions
[All files connected and working together]
```
---
## 7. ONE-CLICK DEPLOY
### Why It's Critical
- Getting code into games is painful
- Manual copy-paste is error-prone
- Professional workflow needs automation
### Architecture
```
src/
├── components/
│ └── deploy/
│ ├── DeployPanel.tsx # Main deploy UI
│ ├── PlatformConnection.tsx # OAuth with platforms
│ ├── DeployProgress.tsx # Real-time status
│ ├── DeployHistory.tsx # Past deployments
│ └── RollbackButton.tsx # Revert if needed
├── lib/
│ └── deploy/
│ ├── roblox-deploy.ts # Roblox Open Cloud API
│ ├── uefn-deploy.ts # UEFN deployment
│ ├── spatial-deploy.ts # Spatial deployment
│ ├── validator.ts # Pre-deploy checks
│ └── optimizer.ts # Platform optimization
```
### Flow
```
1. Connect accounts (OAuth)
└── Roblox: Open Cloud API key
└── UEFN: Epic Games login
└── Spatial: API token
2. Select target
└── Which game/experience
└── Which environment (staging/production)
3. Validate
└── Syntax check
└── Platform compatibility
└── Security scan
4. Deploy
└── Optimize code
└── Upload assets
└── Update game
5. Verify
└── Health check
└── Rollback if failed
```
---
## Implementation Priority Matrix
| Feature | Impact | Effort | Priority |
|---------|--------|--------|----------|
| Visual Scripting | 🔥🔥🔥 | ⚡⚡⚡ | P0 |
| Live Preview | 🔥🔥🔥 | ⚡⚡ | P0 |
| Asset Library | 🔥🔥 | ⚡⚡ | P1 |
| Real-time Collab | 🔥🔥🔥 | ⚡⚡⚡ | P1 |
| AI Generation v2 | 🔥🔥🔥 | ⚡⚡ | P1 |
| Marketplace | 🔥🔥🔥 | ⚡⚡⚡ | P2 |
| One-Click Deploy | 🔥🔥 | ⚡⚡⚡ | P2 |
---
## File Structure After Implementation
```
src/
├── components/
│ ├── ui/ # Primitives (existing)
│ ├── editor/ # Code editor (existing)
│ ├── visual-scripting/ # NEW: Node editor
│ ├── preview/ # NEW: Game preview
│ ├── assets/ # NEW: Asset management
│ ├── collaboration/ # NEW: Real-time collab
│ ├── marketplace/ # NEW: Asset store
│ ├── deploy/ # NEW: Deployment
│ └── avatar/ # EXISTS: Avatar toolkit
├── lib/
│ ├── platforms.ts # Existing
│ ├── templates.ts # Existing
│ ├── avatar-*.ts # Existing
│ ├── visual-scripting/ # NEW
│ ├── preview/ # NEW
│ ├── assets/ # NEW
│ ├── collaboration/ # NEW
│ ├── marketplace/ # NEW
│ ├── deploy/ # NEW
│ └── ai/ # NEW: Enhanced AI
├── hooks/
│ ├── use-visual-script.ts # NEW
│ ├── use-preview.ts # NEW
│ ├── use-collaboration.ts # NEW
│ └── use-assets.ts # NEW
└── stores/
├── editor-store.ts # Existing (Zustand)
├── visual-script-store.ts # NEW
├── asset-store.ts # NEW
└── collab-store.ts # NEW
```
---
## Dependencies to Add
```json
{
"dependencies": {
// Visual Scripting
"reactflow": "^11.10.0",
// 3D Preview
"@react-three/fiber": "^8.15.0",
"@react-three/drei": "^9.88.0",
"three": "^0.159.0",
// Lua Interpreter
"fengari-web": "^0.1.4",
// Real-time Collaboration
"yjs": "^13.6.0",
"@liveblocks/client": "^1.4.0",
"@liveblocks/react": "^1.4.0",
// Search
"meilisearch": "^0.35.0",
// Payments
"@stripe/stripe-js": "^2.2.0",
// State Management
"zustand": "^4.4.0",
"immer": "^10.0.0"
}
}
```

358
docs/VISUAL_GUIDE.md Normal file
View file

@ -0,0 +1,358 @@
# AeThex Studio - Visual Feature Guide
> 🎨 A comprehensive visual guide to AeThex Studio's interface and features
---
## 📐 Main Interface Layout
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ AeThex Studio [Platform ▼] [Toolbar Buttons]│
├────┬────────────────────────────────────────────────────────┬───────────────────┤
│ │ [script.lua] [main.lua] [+] │ │
│ A │ ─────────────────────────────────────────────────────── │ AI Chat │
│ C │ │ │
│ T │ 1 -- Welcome to AeThex Studio! │ ┌─────────────┐ │
│ I │ 2 -- Write your code here │ │ How can I │ │
│ V │ 3 │ │ help you? │ │
│ I │ 4 local Players = game:GetService("Players") │ └─────────────┘ │
│ T │ 5 │ │
│ Y │ 6 Players.PlayerAdded:Connect(function(player) │ [Your message] │
│ │ 7 print(player.Name .. " joined!") │ │
│ B │ 8 end) │ ───────────── │
│ A │ 9 │ Claude: I can │
│ R │ │ help you with │
│ │ Monaco Code Editor │ Roblox code... │
│ │ │ │
├────┼─────────────────────────────────────────────────────────┼───────────────────┤
│ │ Console / Terminal │ Education │
│ │ > run │ Panel │
│ │ ✓ Script executed successfully │ │
│ │ > _ │ [Tutorials] │
└────┴─────────────────────────────────────────────────────────┴───────────────────┘
```
**Layout Components:**
- **Activity Bar** (left): Quick access icons for Files, AI, Learn
- **File Tree**: Collapsible folder structure with drag-drop
- **Editor Tabs**: Multiple open files with close buttons
- **Code Editor**: Monaco editor with syntax highlighting
- **AI Chat**: Context-aware coding assistant
- **Console**: Terminal with CLI commands
- **Education Panel**: Tutorials and documentation
---
## 🔧 Toolbar Features
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ AeThex Studio │ [Roblox ▼] │ Translate │ Templates │ Visual │ Assets │ 3D │ AI │
│ │ │ │ │ Script │ │ │ │
└─────────────────────────────────────────────────────────────────────────────────┘
│ │ │ │ │ │ │
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
Platform Cross-Platform Code Node-Based Model 3D AI
Selector Translation Library Editor Library View Gen
```
---
## 🎨 Visual Scripting Editor
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Visual Scripting [Generate Code] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ ┌─────────┐ │
│ │ EVENTS │ ┌──────────────────┐ │
│ ├─────────┤ │ 🎮 Player Joined │ │
│ │ Player │ │ ─────────────── │ ┌──────────────────┐ │
│ │ Joined │────▶│ player ●────────┼─────────▶│ 📝 Print │ │
│ │ │ └──────────────────┘ │ ─────────────── │ │
│ ├─────────┤ │ message: ●──────┤ │
│ │ On │ │ "Player joined!"│ │
│ │ Touch │ ┌──────────────────┐ └──────────────────┘ │
│ │ │ │ ⏱️ Wait │ │ │
│ ├─────────┤ │ ─────────────── │ ▼ │
│ │ Key │ │ seconds: [2] │ ┌──────────────────┐ │
│ │ Press │ │ ●───────────────┼─────────▶│ 💫 Tween │ │
│ └─────────┘ └──────────────────┘ │ ─────────────── │ │
│ │ target: ● │ │
│ ┌─────────┐ │ property: Size │ │
│ │ LOGIC │ │ value: [10,10] │ │
│ ├─────────┤ └──────────────────┘ │
│ │ Branch │ │
│ │ Loop │ [Mini Map] ┌───┐ │
│ │ Compare │ │ ▪ │ │
│ └─────────┘ └───┘ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ Palette │ Events │ Logic │ Actions │ Data │ References │ [+ Add] │
└─────────────────────────────────────────────────────────────────────────────────┘
```
**Visual Scripting Features:**
- Drag-and-drop node placement
- 30+ node types across 5 categories
- Connection wires with data flow
- Mini-map for navigation
- Generates Lua/Verse/TypeScript code
---
## 📦 Asset Library
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Asset Library [Grid ▣] [List ☰] [×] │
├──────────────────┬──────────────────────────────────────────────────────────────┤
│ Categories │ 🔍 Search assets... [Filter ▼] [Sort ▼] │
│ ─────────────── │ ──────────────────────────────────────────────────────────── │
│ ▼ All Assets │ │
│ 📁 Models (12) │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ 🖼️ Textures(8) │ │ 🏠 │ │ ⚔️ │ │ 🌳 │ │ 🚗 │ │
│ 🔊 Audio (5) │ │ │ │ │ │ │ │ │ │
│ 📜 Scripts (3) │ │ House │ │ Sword │ │ Tree │ │ Car │ │
│ │ │ .glb │ │ .fbx │ │ .glb │ │ .obj │ │
│ ▼ Folders │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ 📂 Characters │ │
│ 📂 Environment │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ 📂 Props │ │ 🧱 │ │ 🔊 │ │ 🎵 │ │ 📄 │ │
│ │ │ │ │ │ │ │ │ │ │
│ ─────────────────│ │ Brick │ │ Ambient │ │ Music │ │ Config │ │
│ ★ Favorites (3) │ │ .png │ │ .mp3 │ │ .ogg │ │ .json │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├──────────────────┴──────────────────────────────────────────────────────────────┤
│ ╔═══════════════════════════════════════╗ │
│ ║ Drop files here to upload ║ │
│ ║ 📁 or click to browse ║ │
│ ╚═══════════════════════════════════════╝ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
**Asset Library Features:**
- Grid/List view toggle
- Drag-and-drop upload
- Category filtering
- Search functionality
- Favorites system
- Thumbnail previews
---
## 🎮 3D Live Preview
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Live Preview [Beta] [▶ Run] [⏸ Pause] [⏹ Stop] [↺] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ☀️ │ │
│ │ ╱╲ │ │
│ │ ▓▓▓▓▓ ╲ ╭───╮ │ │
│ │ ▓▓▓▓▓ ╲ │ 🧍│ │ │
│ │ ▓▓▓▓▓ ──────── ╰───╯ │ │
│ │ │ │
│ │ ═══════════════════════════════════════════════════════════ │ [⚙️] │
│ │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ ░░░░░░░░░░░░░░░░░░ BASEPLATE ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ ┌───┐ │ │
│ │ ● Running 3 objects │▪▪▪│ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ Console [🔍] [Filter ▼] [🗑️]│
│ ─────────────────────────────────────────────────────────────────────────────── │
│ 12:34:56.123 Starting script execution... │
│ 12:34:56.234 📝 Player1 joined the game! │
│ 12:34:57.456 ⚠️ Warning: Part not anchored │
│ 12:34:58.789 Script executed successfully │
└─────────────────────────────────────────────────────────────────────────────────┘
```
**3D Preview Features:**
- Real-time Lua script execution
- Three.js viewport with shadows
- Orbit camera controls
- Grid and axes helpers
- Console output with filtering
- Run/Pause/Stop controls
---
## 🤖 AI Code Generator
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ AI Code Generator [ROBLOX] [×] │
├───────────────────────┬─────────────────────────────────────────────────────────┤
│ [Systems] [Snippets] │ │
│ [Custom] │ Inventory System │
│ │ ════════════════════════════════════════════════════ │
│ 🔍 Search... │ Complete inventory management with slots, stacking, │
│ │ drag-drop, and persistence │
│ ───────────────── │ │
│ [All] [Gameplay] │ Features: Item management • Stack splitting • Search │
│ [Economy] [Combat] │ Quick transfer • Categories • Tooltips │
│ │ │
│ ┌───────────────────┐ │ ┌─ Configuration ────────────────────────────────────┐ │
│ │ 📦 Inventory │ │ │ │ │
│ │ System │ │ │ Max Slots [────────●──] 20 │ │
│ │ ───────────────── │ │ │ Max Stack [──────●────] 99 │ │
│ │ intermediate │ │ │ Enable Drag&Drop [✓] │ │
│ │ ~450 lines │ │ │ Persist Data [✓] │ │
│ └───────────────────┘ │ │ Enable Hotbar [✓] │ │
│ │ │ Hotbar Slots [────●──────] 8 │ │
│ ┌───────────────────┐ │ │ │ │
│ │ 📜 Quest System │ │ └────────────────────────────────────────────────────┘ │
│ │ ───────────────── │ │ [✨ Generate] │
│ │ advanced │ │ ────────────────────────────────────────────────────── │
│ │ ~600 lines │ │ │
│ └───────────────────┘ │ -- Inventory System │
│ │ -- Generated by AeThex Studio AI │
│ ┌───────────────────┐ │ │
│ │ 💰 Currency │ │ local Players = game:GetService("Players") │
│ │ System │ │ local ReplicatedStorage = game:GetService(...) │
│ │ ───────────────── │ │ │
│ │ intermediate │ │ local MAX_SLOTS = 20 │
│ │ ~350 lines │ │ local MAX_STACK = 99 │
│ └───────────────────┘ │ ... │
│ │ [📋 Copy] [→ Insert] │
└───────────────────────┴─────────────────────────────────────────────────────────┘
```
**AI Generator Features:**
- 5+ system templates (Inventory, Quests, Currency, Combat, Friends)
- Configurable parameters with sliders/switches
- Multi-platform code generation
- Instant preview of generated code
- Copy or insert directly into editor
---
## 👥 Real-time Collaboration
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Collaborators (3) [💬 Chat] [×] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ ● You (Owner) [Following] │ │
│ │ Line 12, Column 5 │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ ● Alice ✏️ typing │ │
│ │ Line 45, Column 18 [Follow] │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ ○ Bob 30s ago │ │
│ │ Line 23, Column 1 [Follow] │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Session: Demo Collaboration │
│ File: script.lua │
│ [🔗 Copy Invite Link] [⚙️ Settings] [🚪 Leave] │
│ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ Chat │
│ ─────────────────────────────────────────────────────────────────────────────── │
│ ● Alice (2 min ago) │
│ Hey, I just pushed some changes to the combat system! │
│ │
│ ● Bob (1 min ago) │
│ Nice! Let me take a look at the damage calculation. │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Type a message... [Send]│ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
**Collaboration Features:**
- Real-time presence indicators
- Colored cursors for each user
- Follow mode to watch others
- Built-in chat system
- Typing indicators
- Permission management
---
## 🌈 Color Scheme
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ AeThex Studio Theme │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Background ████████ #0a0a0f (Dark navy) │
│ Card/Panel ████████ #1a1a2e (Elevated dark) │
│ Border ████████ #2d2d44 (Subtle divider) │
│ │
│ Primary/Accent ████████ #8b5cf6 (Purple - "Thex" highlight) │
│ Primary Hover ████████ #a78bfa (Lighter purple) │
│ │
│ Text Primary ████████ #f8fafc (White) │
│ Text Muted ████████ #94a3b8 (Gray) │
│ │
│ Success ████████ #22c55e (Green) │
│ Warning ████████ #eab308 (Yellow) │
│ Error ████████ #ef4444 (Red) │
│ Info ████████ #3b82f6 (Blue) │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
---
## 📱 Responsive Design
**Desktop (1200px+)**
```
┌──────┬─────────────────────────┬──────────┐
│ File │ Code Editor │ AI Chat │
│ Tree │ │ │
│ │ │ │
│ ├─────────────────────────┤ │
│ │ Console │ Education│
└──────┴─────────────────────────┴──────────┘
```
**Tablet (768px - 1199px)**
```
┌──────┬─────────────────────────┐
│ File │ Code Editor │
│ Tree │ │
│ ├─────────────────────────┤
│ │ Console / AI Chat │
└──────┴─────────────────────────┘
```
**Mobile (<768px)**
```
┌─────────────────────────┐
│ [☰ Menu] AeThex │
├─────────────────────────┤
│ │
│ Code Editor │
│ │
├─────────────────────────┤
│ [Files] [AI] [More] │
└─────────────────────────┘
```
---
## 🚀 Getting Started
1. **Clone**: `git clone https://github.com/AeThex-LABS/aethex-studio.git`
2. **Install**: `npm install`
3. **Run**: `npm run dev`
4. **Open**: http://localhost:3000
---
*Generated by AeThex Studio Visual Documentation*

View file

@ -1,19 +0,0 @@
import { type NextRequest } from 'next/server';
import { updateSession } from '@/lib/supabase/middleware';
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};

3
next-env.d.ts vendored
View file

@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View file

@ -3,7 +3,7 @@
const path = require('path');
const nextConfig = {
reactStrictMode: true,
// swcMinify: true, // Removed for compatibility with current Next.js version
swcMinify: true,
webpack: (config) => {
config.resolve.alias['@'] = path.resolve(__dirname, 'src');
return config;

View file

@ -1,35 +0,0 @@
import type {NextConfig} from 'next';
const nextConfig: NextConfig = {
/* config options here */
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'placehold.co',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'picsum.photos',
port: '',
pathname: '/**',
},
],
},
};
export default nextConfig;

16803
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,90 +1,84 @@
{
"name": "nextn",
"version": "0.1.0",
"name": "aethex-studio",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev -p 9002",
"genkit:dev": "genkit start -- tsx src/ai/dev.ts",
"genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts",
"build": "NODE_ENV=production next build",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit"
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@genkit-ai/google-genai": "^1.20.0",
"@genkit-ai/next": "^1.20.0",
"@hookform/resolvers": "^4.1.3",
"@monaco-editor/react": "^4.7.0",
"@monaco-editor/react": "^4.6.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-menubar": "^1.1.6",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.1.8",
"@sentry/browser": "^10.38.0",
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.93.3",
"@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.23",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.6",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.5.0",
"@reactflow/background": "^11.3.14",
"@reactflow/controls": "^11.2.14",
"@reactflow/minimap": "^11.7.14",
"@reactflow/node-toolbar": "^1.3.14",
"@sentry/browser": "^10.34.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^3.6.0",
"dotenv": "^16.5.0",
"embla-carousel-react": "^8.6.0",
"firebase": "^11.9.1",
"genkit": "^1.20.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.475.0",
"marked": "^12.0.2",
"next": "15.5.9",
"fengari-web": "^0.1.4",
"framer-motion": "^11.15.0",
"immer": "^11.1.3",
"lucide-react": "^0.462.0",
"monaco-editor": "^0.52.2",
"next": "^14.2.35",
"next-themes": "^0.4.6",
"patch-package": "^8.0.0",
"posthog-js": "^1.337.0",
"react": "^19.2.1",
"react-day-picker": "^9.11.3",
"react-dom": "^19.2.1",
"react-hook-form": "^7.54.2",
"react-resizable-panels": "^4.5.9",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1",
"posthog-js": "^1.328.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^6.1.0",
"react-resizable-panels": "^4.4.1",
"reactflow": "^11.11.4",
"socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "^3.24.2",
"three": "^0.182.0",
"zustand": "^5.0.10"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"@types/react-syntax-highlighter": "^15.5.13",
"genkit-cli": "^1.20.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@testing-library/user-event": "^14.6.1",
"@types/node": "22.19.7",
"@types/react": "18.3.27",
"@types/react-dom": "^18",
"@types/three": "^0.182.0",
"@vitejs/plugin-react": "^5.1.2",
"autoprefixer": "^10.4.23",
"eslint": "^8",
"eslint-config-next": "14.2.15",
"jsdom": "^27.4.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.19",
"typescript": "^5",
"vitest": "^4.0.17"
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,15 +3,10 @@ import { Button } from "./components/ui/button";
import { AlertTriangleIcon, RefreshCwIcon } from "lucide-react";
interface ErrorFallbackProps {
error: { message: string };
resetErrorBoundary: () => void;
}
export const ErrorFallback = ({ error, resetErrorBoundary }: ErrorFallbackProps) => {
export const ErrorFallback = ({ error, resetErrorBoundary }) => {
// When encountering an error in the development mode, rethrow it and don't display the boundary.
// The parent UI will take care of showing a more helpful dialog.
if (process.env.NODE_ENV === 'development') throw error;
if (import.meta.env.DEV) throw error;
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">

View file

@ -1,6 +0,0 @@
import { config } from 'dotenv';
config();
import '@/ai/flows/ai-suggested-sync-conflict-resolution.ts';
import '@/ai/flows/contextual-code-suggestions.ts';
import '@/ai/flows/ai-help-from-prompt.ts';

View file

@ -1,85 +0,0 @@
"use server";
import { ai } from "../../ai/genkit";
import { z } from "genkit";
const AIHelpFromPromptInputSchema = z.object({
prompt: z.string().describe("A prompt describing the type of application to build."),
});
export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>;
const AIHelpFromPromptOutputSchema = z.object({
suggestedFiles: z.array(z.object({
filePath: z.string().describe("The path for the suggested file."),
fileContent: z.string().describe("The content of the suggested file."),
})).describe("An array of suggested code files and their content."),
explanation: z.string().describe("An explanation of the suggested file structure and code."),
});
export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>;
const prompt = ai.definePrompt({
name: "aiHelpFromPromptPrompt",
input: { schema: AIHelpFromPromptInputSchema },
output: { schema: AIHelpFromPromptOutputSchema },
prompt: `You are an AI assistant designed to help new users quickly start developing applications.
Based on the user's prompt describing the desired application, suggest an initial set of code files and a project structure to get them started.
Provide the suggested files as an array of objects, each containing the file path and the file content.
Explain the suggested file structure and the code in detail so that the user understands the purpose of each file and how they fit together.
User Prompt: {{{prompt}}}
Example Output:
{
"suggestedFiles": [
{
"filePath": "src/components/MyComponent.tsx",
"fileContent": "// MyComponent.tsx\nimport React from 'react';\n\nconst MyComponent = () => {\n return (\n <div>\n <h1>Hello, world!</h1>\n </div>\n );\n};\n\nexport default MyComponent;"
},
{
"filePath": "src/pages/index.tsx",
"fileContent": "// index.tsx\nimport MyComponent from '../components/MyComponent';\n\nconst Home = () => {\n return (\n <div>\n <MyComponent />\n </div>\n );\n};\n\nexport default Home;"
}
],
"explanation": "This project structure includes a component (MyComponent.tsx) and a page (index.tsx) that uses the component. This is a basic structure for a React application."
}
`,
});
const aiHelpFromPromptFlow = ai.defineFlow(
{
name: "aiHelpFromPromptFlow",
inputSchema: AIHelpFromPromptInputSchema,
outputSchema: AIHelpFromPromptOutputSchema,
},
async (input) => {
const { output } = await prompt(input, {
config: {
safetySettings: [
{
category: "HARM_CATEGORY_HATE_SPEECH",
threshold: "BLOCK_ONLY_HIGH",
},
{
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: "BLOCK_NONE",
},
{
category: "HARM_CATEGORY_HARASSMENT",
threshold: "BLOCK_MEDIUM_AND_ABOVE",
},
{
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold: "BLOCK_LOW_AND_ABOVE",
},
],
},
});
return output!;
}
);
export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> {
return aiHelpFromPromptFlow(input);
}

View file

@ -1,57 +0,0 @@
import {ai} from '../../ai/genkit';
import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({
robloxCode: z.string().describe('The Lua code for the Roblox platform.'),
webCode: z.string().describe('The JavaScript code for the web platform.'),
mobileCode: z.string().describe('The React Native code for the mobile platform.'),
sharedState: z.string().describe('The shared state data in JSON format.'),
});
export type AISuggestedSyncConflictResolutionInput = z.infer<typeof AISuggestedSyncConflictResolutionInputSchema>;
const AISuggestedSyncConflictResolutionOutputSchema = z.object({
conflictDetected: z.boolean().describe('Whether a synchronization conflict was detected.'),
suggestedSolutions: z.array(z.string()).describe('An array of suggested solutions to resolve the conflicts.'),
explanation: z.string().describe('Explanation of the detected conflicts and suggested solutions.'),
});
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
const prompt = ai.definePrompt({
name: 'aiSuggestedSyncConflictResolutionPrompt',
input: {schema: AISuggestedSyncConflictResolutionInputSchema},
output: {schema: AISuggestedSyncConflictResolutionOutputSchema},
prompt: `You are an AI assistant specialized in detecting synchronization conflicts between different platform codebases and suggesting solutions.
You are given the code for Roblox (Lua), Web (JavaScript), and Mobile (React Native), as well as the shared state data in JSON format. Analyze the code and the shared state to identify any inconsistencies or conflicts.
Based on your analysis, determine if there are any conflicts, and suggest solutions to resolve them. Explain the conflicts and the suggested solutions in detail.
Roblox Code:
{{robloxCode}}
Web Code:
{{webCode}}
Mobile Code:
{{mobileCode}}
Shared State:
{{sharedState}}`,
});
const aiSuggestedSyncConflictResolutionFlow = ai.defineFlow(
{
name: 'aiSuggestedSyncConflictResolutionFlow',
inputSchema: AISuggestedSyncConflictResolutionInputSchema,
outputSchema: AISuggestedSyncConflictResolutionOutputSchema,
},
async (input: AISuggestedSyncConflictResolutionInput) => {
const {output} = await prompt(input);
return output!;
}
);
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input);
}

View file

@ -1,77 +0,0 @@
'use server';
/**
* @fileOverview This file defines a Genkit flow for providing contextual code suggestions.
*
* - contextualCodeSuggestions - A function that takes the current file content and cursor position
* and returns code suggestions.
* - ContextualCodeSuggestionsInput - The input type for the contextualCodeSuggestions function.
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/
import {ai} from '../../ai/genkit';
import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({
fileContent: z.string().describe('The content of the currently open file.'),
cursorPosition: z.number().describe('The cursor position within the file.'),
language: z.string().describe('The programming language of the file.'),
context: z.string().optional().describe('Additional context for code suggestions, e.g., error messages or related code snippets.'),
});
export type ContextualCodeSuggestionsInput = z.infer<
typeof ContextualCodeSuggestionsInputSchema
>;
const ContextualCodeSuggestionsOutputSchema = z.object({
suggestions: z
.array(z.string())
.describe('An array of code suggestions based on the context.'),
});
export type ContextualCodeSuggestionsOutput = z.infer<
typeof ContextualCodeSuggestionsOutputSchema
>;
export async function contextualCodeSuggestions(
input: ContextualCodeSuggestionsInput
): Promise<ContextualCodeSuggestionsOutput> {
return contextualCodeSuggestionsFlow(input);
}
const prompt = ai.definePrompt({
name: 'contextualCodeSuggestionsPrompt',
input: {schema: ContextualCodeSuggestionsInputSchema},
output: {schema: ContextualCodeSuggestionsOutputSchema},
prompt: `You are an AI assistant that provides code suggestions and autocompletions based on the context of the currently open file and cursor position.
Given the following file content, cursor position, programming language, and any available context, provide a list of code suggestions that would be helpful to the developer.
File Content:
{{fileContent}}
Cursor Position: {{cursorPosition}}
Programming Language: {{language}}
Context: {{context}}
Suggestions should be relevant to the current context, incorporate best practices, and avoid common mistakes. Return the suggestions as an array of strings.
Example:
[
"console.log('Hello, world!');",
"// Add a comment to explain the code",
"function myFunction() {\n // Function body\n }",
]`,
});
const contextualCodeSuggestionsFlow = ai.defineFlow(
{
name: 'contextualCodeSuggestionsFlow',
inputSchema: ContextualCodeSuggestionsInputSchema,
outputSchema: ContextualCodeSuggestionsOutputSchema,
},
async (input: ContextualCodeSuggestionsInput) => {
const {output} = await prompt(input);
return output!;
}
);
'use server';

View file

@ -1,7 +0,0 @@
import {genkit} from 'genkit';
import {googleAI} from '@genkit-ai/google-genai';
export const ai = genkit({
plugins: [googleAI()],
model: 'googleai/gemini-2.5-flash',
});

View file

@ -1,66 +0,0 @@
"use client";
import Link from "next/link";
export default function AuthCodeErrorPage() {
return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center px-4">
<div className="max-w-md w-full text-center">
<div className="w-16 h-16 mx-auto mb-6 rounded-full bg-red-500/20 flex items-center justify-center">
<svg
className="w-8 h-8 text-red-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</div>
<h1 className="text-2xl font-bold text-white mb-2">
Authentication Error
</h1>
<p className="text-[#888] mb-8">
Something went wrong during sign in. This could happen if:
</p>
<ul className="text-left text-[#888] mb-8 space-y-2">
<li className="flex items-start gap-2">
<span className="text-red-500"></span>
<span>The sign in link expired or was already used</span>
</li>
<li className="flex items-start gap-2">
<span className="text-red-500"></span>
<span>You denied access to your account</span>
</li>
<li className="flex items-start gap-2">
<span className="text-red-500"></span>
<span>There was a network error during authentication</span>
</li>
</ul>
<div className="space-y-3">
<Link
href="/auth/login"
className="block w-full py-3 px-4 bg-gradient-to-r from-purple-600 to-blue-600 text-white font-medium rounded-lg hover:opacity-90 transition"
>
Try Again
</Link>
<Link
href="/"
className="block w-full py-3 px-4 bg-[#1a1a1a] text-[#888] font-medium rounded-lg hover:bg-[#222] transition"
>
Go Home
</Link>
</div>
</div>
</div>
);
}

View file

@ -1,21 +0,0 @@
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get('code');
// Default redirect to /ide after successful OAuth login
const next = searchParams.get('next') ?? '/ide';
if (code) {
const supabase = await createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) {
return NextResponse.redirect(`${origin}${next}`);
}
console.error('OAuth callback error:', error.message);
}
// Return the user to an error page with instructions
return NextResponse.redirect(`${origin}/auth/auth-code-error`);
}

View file

@ -1,169 +0,0 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSupabaseAuth } from "@/hooks/use-supabase";
export default function LoginPage() {
const router = useRouter();
const { signIn, signInWithOAuth } = useSupabaseAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
const { error } = await signIn(email, password);
if (error) {
setError(error.message);
setLoading(false);
} else {
router.push("/ide");
}
};
const handleOAuth = async (provider: "github" | "google" | "discord") => {
setError("");
const { error } = await signInWithOAuth(provider);
if (error) {
setError(error.message);
}
};
return (
<main className="min-h-screen bg-[#0a0a0a] flex items-center justify-center px-4">
<div className="w-full max-w-md">
{/* Logo */}
<div className="flex justify-center mb-8">
<Link href="/" className="flex items-center gap-2">
<div className="h-10 w-10 rounded-xl bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<span className="text-2xl font-bold text-white">AeThex Studio</span>
</Link>
</div>
{/* Card */}
<div className="bg-[#141414] border border-[#222] rounded-2xl p-8">
<h1 className="text-2xl font-semibold text-white text-center mb-2">Welcome back</h1>
<p className="text-[#888] text-center mb-8">Sign in to your AeThex account</p>
{error && (
<div className="bg-red-500/10 border border-red-500/20 text-red-400 px-4 py-3 rounded-lg mb-6 text-sm">
{error}
</div>
)}
{/* OAuth Buttons */}
<div className="space-y-3 mb-6">
<button
onClick={() => handleOAuth("github")}
className="w-full flex items-center justify-center gap-3 bg-[#1b1b1b] hover:bg-[#222] border border-[#333] text-white py-3 px-4 rounded-lg transition-colors"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Continue with GitHub
</button>
<button
onClick={() => handleOAuth("google")}
className="w-full flex items-center justify-center gap-3 bg-[#1b1b1b] hover:bg-[#222] border border-[#333] text-white py-3 px-4 rounded-lg transition-colors"
>
<svg width="20" height="20" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Continue with Google
</button>
<button
onClick={() => handleOAuth("discord")}
className="w-full flex items-center justify-center gap-3 bg-[#1b1b1b] hover:bg-[#222] border border-[#333] text-white py-3 px-4 rounded-lg transition-colors"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="#5865F2">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
</svg>
Continue with Discord
</button>
</div>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-[#333]"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-[#141414] text-[#666]">or continue with email</span>
</div>
</div>
{/* Email Form */}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-[#888] mb-2">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
placeholder="you@example.com"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-[#888] mb-2">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
placeholder="••••••••"
required
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-white hover:bg-[#e5e5e5] text-[#0a0a0a] font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? "Signing in..." : "Sign in"}
</button>
</form>
<p className="mt-6 text-center text-[#888] text-sm">
Don't have an account?{" "}
<Link href="/auth/signup" className="text-purple-400 hover:text-purple-300 transition-colors">
Sign up
</Link>
</p>
</div>
{/* Ecosystem Links */}
<div className="mt-8 text-center">
<p className="text-[#666] text-xs mb-3">Part of the AeThex ecosystem</p>
<div className="flex items-center justify-center gap-4 text-sm">
<a href="https://aethex.dev" className="text-[#888] hover:text-white transition-colors">aethex.dev</a>
<span className="text-[#444]"></span>
<a href="https://aethex.foundation" className="text-[#888] hover:text-white transition-colors">aethex.foundation</a>
<span className="text-[#444]"></span>
<a href="https://aethex.studio" className="text-purple-400 hover:text-purple-300 transition-colors">aethex.studio</a>
</div>
</div>
</div>
</main>
);
}

View file

@ -1,230 +0,0 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSupabaseAuth } from "@/hooks/use-supabase";
export default function SignUpPage() {
const router = useRouter();
const { signUp, signInWithOAuth } = useSupabaseAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (password !== confirmPassword) {
setError("Passwords do not match");
return;
}
if (password.length < 8) {
setError("Password must be at least 8 characters");
return;
}
setLoading(true);
const { error } = await signUp(email, password);
if (error) {
setError(error.message);
setLoading(false);
} else {
setSuccess(true);
}
};
const handleOAuth = async (provider: "github" | "google" | "discord") => {
setError("");
const { error } = await signInWithOAuth(provider);
if (error) {
setError(error.message);
}
};
if (success) {
return (
<main className="min-h-screen bg-[#0a0a0a] flex items-center justify-center px-4">
<div className="w-full max-w-md text-center">
<div className="bg-[#141414] border border-[#222] rounded-2xl p-8">
<div className="h-16 w-16 rounded-full bg-green-500/10 flex items-center justify-center mx-auto mb-6">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2">
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
<h1 className="text-2xl font-semibold text-white mb-2">Check your email</h1>
<p className="text-[#888] mb-6">
We sent a confirmation link to <span className="text-white">{email}</span>
</p>
<Link
href="/auth/login"
className="inline-block bg-white hover:bg-[#e5e5e5] text-[#0a0a0a] font-medium py-3 px-6 rounded-lg transition-colors"
>
Back to login
</Link>
</div>
</div>
</main>
);
}
return (
<main className="min-h-screen bg-[#0a0a0a] flex items-center justify-center px-4">
<div className="w-full max-w-md">
{/* Logo */}
<div className="flex justify-center mb-8">
<Link href="/" className="flex items-center gap-2">
<div className="h-10 w-10 rounded-xl bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<span className="text-2xl font-bold text-white">AeThex Studio</span>
</Link>
</div>
{/* Card */}
<div className="bg-[#141414] border border-[#222] rounded-2xl p-8">
<h1 className="text-2xl font-semibold text-white text-center mb-2">Create your account</h1>
<p className="text-[#888] text-center mb-8">Join the AeThex ecosystem</p>
{error && (
<div className="bg-red-500/10 border border-red-500/20 text-red-400 px-4 py-3 rounded-lg mb-6 text-sm">
{error}
</div>
)}
{/* OAuth Buttons */}
<div className="space-y-3 mb-6">
<button
onClick={() => handleOAuth("github")}
className="w-full flex items-center justify-center gap-3 bg-[#1b1b1b] hover:bg-[#222] border border-[#333] text-white py-3 px-4 rounded-lg transition-colors"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Continue with GitHub
</button>
<button
onClick={() => handleOAuth("google")}
className="w-full flex items-center justify-center gap-3 bg-[#1b1b1b] hover:bg-[#222] border border-[#333] text-white py-3 px-4 rounded-lg transition-colors"
>
<svg width="20" height="20" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Continue with Google
</button>
<button
onClick={() => handleOAuth("discord")}
className="w-full flex items-center justify-center gap-3 bg-[#1b1b1b] hover:bg-[#222] border border-[#333] text-white py-3 px-4 rounded-lg transition-colors"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="#5865F2">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
</svg>
Continue with Discord
</button>
</div>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-[#333]"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-[#141414] text-[#666]">or continue with email</span>
</div>
</div>
{/* Email Form */}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-[#888] mb-2">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
placeholder="you@example.com"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-[#888] mb-2">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
placeholder="••••••••"
required
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-[#888] mb-2">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
placeholder="••••••••"
required
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-white hover:bg-[#e5e5e5] text-[#0a0a0a] font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? "Creating account..." : "Create account"}
</button>
</form>
<p className="mt-6 text-center text-[#888] text-sm">
Already have an account?{" "}
<Link href="/auth/login" className="text-purple-400 hover:text-purple-300 transition-colors">
Sign in
</Link>
</p>
</div>
{/* Terms */}
<p className="mt-6 text-center text-[#666] text-xs">
By creating an account, you agree to our{" "}
<Link href="/terms" className="text-[#888] hover:text-white transition-colors">Terms of Service</Link>
{" "}and{" "}
<Link href="/privacy" className="text-[#888] hover:text-white transition-colors">Privacy Policy</Link>
</p>
{/* Ecosystem Links */}
<div className="mt-6 text-center">
<p className="text-[#666] text-xs mb-3">Part of the AeThex ecosystem</p>
<div className="flex items-center justify-center gap-4 text-sm">
<a href="https://aethex.dev" className="text-[#888] hover:text-white transition-colors">aethex.dev</a>
<span className="text-[#444]"></span>
<a href="https://aethex.foundation" className="text-[#888] hover:text-white transition-colors">aethex.foundation</a>
<span className="text-[#444]"></span>
<a href="https://aethex.studio" className="text-purple-400 hover:text-purple-300 transition-colors">aethex.studio</a>
</div>
</div>
</div>
</main>
);
}

View file

@ -1,5 +0,0 @@
import { DashboardPage } from "../../components/aethex/dashboard-page";
export default function Page() {
return <DashboardPage />;
}

View file

@ -1,145 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 262 83% 58%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 262 83% 58%;
--radius: 0.5rem;
}
.dark {
--background: 240 6% 6%;
--foreground: 0 0% 95%;
--card: 240 6% 8%;
--card-foreground: 0 0% 95%;
--popover: 240 6% 8%;
--popover-foreground: 0 0% 95%;
--primary: 262 83% 58%;
--primary-foreground: 0 0% 98%;
--secondary: 240 5% 12%;
--secondary-foreground: 0 0% 95%;
--muted: 240 5% 14%;
--muted-foreground: 240 5% 55%;
--accent: 240 5% 14%;
--accent-foreground: 0 0% 95%;
--destructive: 0 62.8% 45%;
--destructive-foreground: 0 0% 98%;
--border: 240 4% 16%;
--input: 240 4% 16%;
--ring: 262 83% 58%;
}
* {
border-color: hsl(var(--border));
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* Custom scrollbar - minimal and modern */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}
::-webkit-scrollbar-corner {
background: transparent;
}
/* IDE-specific scrollbar */
.ide-scroll::-webkit-scrollbar {
width: 10px;
}
.ide-scroll::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.08);
border: 2px solid transparent;
background-clip: padding-box;
border-radius: 5px;
}
.ide-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.15);
border: 2px solid transparent;
background-clip: padding-box;
}
/* Smooth transitions */
* {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
/* Selection styling */
::selection {
background: rgba(139, 92, 246, 0.3);
color: white;
}
/* Focus rings */
:focus-visible {
outline: 2px solid rgba(139, 92, 246, 0.5);
outline-offset: 2px;
}
/* Hide scrollbar utility */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}

View file

@ -1,9 +0,0 @@
import { AethexStudio } from "@/src/components/aethex/aethex-studio";
export default function IdePage() {
return (
<main className="h-[100svh] w-screen overflow-hidden bg-background">
<AethexStudio />
</main>
);
}

View file

@ -1,34 +0,0 @@
import Link from "next/link";
export default function LandingPage() {
return (
<main className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-gray-800 to-gray-950 text-white p-8">
<div className="max-w-xl w-full flex flex-col items-center">
<img src="/logo.svg" alt="AeThex Studio Logo" className="w-24 h-24 mb-6" />
<h1 className="text-4xl font-bold mb-4 text-center">Welcome to AeThex Studio</h1>
<p className="text-lg mb-8 text-center opacity-80">
The Next-Generation Cross-Platform IDE for Creators, Developers, and Teams.
</p>
<div className="flex flex-col gap-4 w-full">
<a
href="https://aethex.com/download"
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg text-center transition-colors shadow-lg"
target="_blank"
rel="noopener noreferrer"
>
Download AeThex Studio
</a>
<Link
href="/ide"
className="bg-gray-800 hover:bg-gray-700 text-white font-semibold py-3 rounded-lg text-center transition-colors border border-gray-700"
>
Open in Browser
</Link>
</div>
<p className="mt-8 text-sm text-gray-400 text-center">
Need help? <a href="https://aethex.com/docs" className="underline hover:text-blue-400">Read the Docs</a>
</p>
</div>
</main>
);
}

View file

@ -1,35 +0,0 @@
import type { Metadata } from "next";
import Toaster from "../components/ui/toaster";
import "./globals.css";
export const metadata: Metadata = {
title: "AeThex Studio",
description: "The Next-Generation Cross-Platform IDE",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="dark">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-sans antialiased">
{children}
<Toaster />
</body>
</html>
);
}

View file

@ -1,458 +0,0 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSupabaseAuth, useProfile } from "@/hooks/use-supabase";
export default function Page() {
const router = useRouter();
const { user, loading } = useSupabaseAuth();
const { profile } = useProfile();
const [prompt, setPrompt] = useState("");
const handlePromptSubmit = () => {
if (prompt.trim()) {
sessionStorage.setItem("aethex_prompt", prompt);
router.push("/ide");
}
};
return (
<main className="min-h-screen bg-[#0a0a0a]">
{/* Fixed Navbar */}
<nav className="fixed top-0 left-0 right-0 z-50 bg-[#0a0a0a]/90 backdrop-blur-md border-b border-[#1a1a1a]">
<div className="flex h-14 items-center justify-between px-4 md:px-10 max-w-6xl mx-auto">
<div className="flex items-center gap-8">
<Link href="/" className="flex items-center gap-2">
<div className="h-8 w-8 rounded-lg bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<span className="text-xl font-bold text-white">AeThex Studio</span>
</Link>
<div className="hidden md:flex items-center gap-6">
<a href="#features" className="text-sm font-medium text-[#666] hover:text-white transition-colors">Features</a>
<a href="#platforms" className="text-sm font-medium text-[#666] hover:text-white transition-colors">Platforms</a>
<a href="#pricing" className="text-sm font-medium text-[#666] hover:text-white transition-colors">Pricing</a>
<a href="https://github.com/AeThex-LABS/aethex-studio" target="_blank" rel="noopener noreferrer" className="text-sm font-medium text-[#666] hover:text-white transition-colors">GitHub</a>
</div>
</div>
<div className="flex items-center gap-4">
{!loading && (
<>
{user ? (
<>
<Link href="/profile" className="flex items-center gap-2 text-sm text-[#666] hover:text-white transition-colors">
<div className="h-7 w-7 rounded-full bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center text-xs font-medium text-white">
{profile?.username?.[0]?.toUpperCase() || user.email?.[0]?.toUpperCase() || "?"}
</div>
</Link>
<Link href="/ide" className="rounded-full bg-white px-5 py-2 text-sm font-medium text-[#0a0a0a] transition-all hover:bg-[#e5e5e5]">
Launch IDE
</Link>
</>
) : (
<>
<Link href="/auth/login" className="text-sm font-medium text-[#666] hover:text-white transition-colors">Sign in</Link>
<Link href="/auth/signup" className="rounded-full bg-white px-5 py-2 text-sm font-medium text-[#0a0a0a] transition-all hover:bg-[#e5e5e5]">Get started</Link>
</>
)}
</>
)}
</div>
</div>
</nav>
{/* Hero Section - Prompt First */}
<section className="relative flex flex-col items-center px-6 pt-32 pb-20 md:pt-44 md:pb-28 overflow-hidden">
<div className="pointer-events-none absolute inset-0" style={{
background: "radial-gradient(ellipse 80% 50% at 50% -20%, rgba(120, 119, 198, 0.3), transparent)"
}} />
<h1 className="relative max-w-4xl text-center text-4xl font-bold tracking-tight text-white md:text-6xl lg:text-7xl">
Build games for{" "}
<span className="bg-gradient-to-r from-purple-400 via-pink-400 to-blue-400 bg-clip-text text-transparent">
every platform
</span>
</h1>
<p className="relative mt-6 max-w-2xl text-center text-lg leading-relaxed text-[#888] md:text-xl">
The AI-powered cross-platform game IDE. Write once, deploy to Roblox, UEFN, Spatial, and the Web.
</p>
{/* Prompt Input Box */}
<div className="relative mt-10 w-full max-w-2xl px-4">
<div className="bg-[#111] border border-[#222] rounded-2xl p-4 shadow-2xl shadow-purple-500/5">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handlePromptSubmit();
}
}}
placeholder="Describe your game... e.g. 'Create a tower defense game with wave spawning'"
className="w-full min-h-[60px] bg-transparent text-white text-base resize-none outline-none placeholder-[#555]"
rows={2}
/>
<div className="flex items-center justify-between mt-3 pt-3 border-t border-[#222]">
<div className="flex items-center gap-2">
<button className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-[#1a1a1a] text-[#666] text-sm hover:bg-[#222] hover:text-white transition-colors">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" /></svg>
Attach
</button>
<button className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-[#1a1a1a] text-[#666] text-sm hover:bg-[#222] hover:text-white transition-colors">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect width="18" height="7" x="3" y="3" rx="1"/><rect width="9" height="7" x="3" y="14" rx="1"/><rect width="5" height="7" x="16" y="14" rx="1"/></svg>
Template
</button>
</div>
<button
onClick={handlePromptSubmit}
disabled={!prompt.trim()}
className="flex items-center justify-center w-10 h-10 rounded-full bg-white text-[#0a0a0a] disabled:bg-[#333] disabled:text-[#666] disabled:cursor-not-allowed transition-colors hover:bg-[#e5e5e5]"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</button>
</div>
</div>
</div>
{/* Platform badges */}
<div className="relative mt-12 flex flex-wrap items-center justify-center gap-3">
{[
{ name: "Roblox", color: "#e11d48" },
{ name: "UEFN", color: "#8b5cf6" },
{ name: "Spatial", color: "#10b981" },
{ name: "Web", color: "#3b82f6" },
].map((platform) => (
<div key={platform.name} className="flex items-center gap-2 rounded-full border border-[#222] bg-[#111] px-4 py-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: platform.color }} />
<span className="text-sm font-medium text-white">{platform.name}</span>
</div>
))}
</div>
</section>
{/* Stats Banner */}
<section className="border-y border-[#1a1a1a] bg-[#0d0d0d]">
<div className="mx-auto flex max-w-5xl flex-col items-center justify-center gap-8 px-6 py-10 md:flex-row md:gap-16">
<div className="text-center">
<p className="text-3xl font-bold text-white">4</p>
<p className="mt-1 text-sm text-[#666]">Platforms supported</p>
</div>
<div className="hidden h-8 w-px bg-[#222] md:block" />
<div className="text-center">
<p className="text-3xl font-bold text-white">AI-Powered</p>
<p className="mt-1 text-sm text-[#666]">Code translation</p>
</div>
<div className="hidden h-8 w-px bg-[#222] md:block" />
<div className="text-center">
<p className="text-3xl font-bold text-white">Open Source</p>
<p className="mt-1 text-sm text-[#666]">MIT Licensed</p>
</div>
</div>
</section>
{/* Why AeThex vs Others */}
<section className="px-6 py-20 md:py-28">
<div className="mx-auto max-w-6xl">
<div className="text-center mb-16">
<p className="text-sm font-medium uppercase tracking-widest text-purple-400">Why AeThex?</p>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-white md:text-5xl">
One IDE. Every platform.
</h2>
<p className="mx-auto mt-4 max-w-2xl text-lg text-[#888]">
While others lock you into one platform, AeThex lets you build for all of them
</p>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div className="rounded-2xl border-2 border-purple-500/50 bg-gradient-to-b from-purple-500/10 to-transparent p-8">
<div className="flex items-center gap-3 mb-6">
<div className="h-10 w-10 rounded-lg bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<span className="text-xl font-bold text-white">AeThex Studio</span>
</div>
<ul className="space-y-4">
{[
"Roblox + UEFN + Spatial + Web",
"Full browser-based IDE",
"AI translates between platforms",
"Write once, deploy everywhere",
"Visual UI builder for all platforms",
"Open source (MIT license)",
].map((item, i) => (
<li key={i} className="flex items-center gap-3 text-white">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg>
{item}
</li>
))}
</ul>
</div>
<div className="rounded-2xl border border-[#222] bg-[#111] p-8">
<div className="flex items-center gap-3 mb-6">
<div className="h-10 w-10 rounded-lg bg-[#222] flex items-center justify-center text-[#666]">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/></svg>
</div>
<span className="text-xl font-bold text-[#666]">Single-platform tools</span>
</div>
<ul className="space-y-4">
{[
"Roblox only OR UEFN only",
"Generates code snippets",
"No cross-platform translation",
"Locked to one ecosystem",
"Platform-specific UI code",
"Proprietary / closed source",
].map((item, i) => (
<li key={i} className="flex items-center gap-3 text-[#666]">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ef4444" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
{item}
</li>
))}
</ul>
</div>
</div>
</div>
</section>
{/* Features Grid */}
<section id="features" className="border-t border-[#1a1a1a] bg-[#0d0d0d] px-6 py-20 md:py-28">
<div className="mx-auto max-w-6xl">
<div className="text-center mb-16">
<p className="text-sm font-medium uppercase tracking-widest text-[#666]">Features</p>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-white md:text-5xl">
Everything you need to ship games
</h2>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{[
{ title: "AI Code Generation", desc: "Describe what you want in plain English. Get production-ready code instantly.", icon: "code" },
{ title: "Cross-Platform Translation", desc: "Write in Luau, TypeScript, or C#. Deploy to any supported platform.", icon: "translate" },
{ title: "Visual UI Builder", desc: "Design game UIs visually. Export to platform-native components.", icon: "ui" },
{ title: "Live Preview", desc: "See changes across all platforms in real-time. Instant feedback loop.", icon: "preview" },
{ title: "Studio Sync", desc: "Sync directly to Roblox Studio, UEFN, and more via plugin.", icon: "sync" },
{ title: "Asset Library", desc: "Thousands of free assets, sounds, and scripts ready to use.", icon: "assets" },
].map((feature, i) => (
<div key={i} className="group rounded-2xl border border-[#1a1a1a] bg-[#0a0a0a] p-6 transition-all hover:border-[#333] hover:bg-[#111]">
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-[#1a1a1a] text-white transition-all group-hover:bg-purple-500">
{feature.icon === "code" && <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>}
{feature.icon === "translate" && <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m5 8 6 6"/><path d="m4 14 6-6 2-3"/><path d="M2 5h12"/><path d="M7 2h1"/><path d="m22 22-5-10-5 10"/><path d="M14 18h6"/></svg>}
{feature.icon === "ui" && <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>}
{feature.icon === "preview" && <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>}
{feature.icon === "sync" && <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><path d="M7.8 16.2a5 5 0 0 1 0-8.5"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8a5 5 0 0 1 0 8.5"/><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19"/></svg>}
{feature.icon === "assets" && <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>}
</div>
<h3 className="text-lg font-semibold text-white">{feature.title}</h3>
<p className="mt-2 text-sm text-[#888] leading-relaxed">{feature.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* Platforms */}
<section id="platforms" className="px-6 py-20 md:py-28">
<div className="mx-auto max-w-6xl">
<div className="text-center mb-16">
<p className="text-sm font-medium uppercase tracking-widest text-[#666]">Platforms</p>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-white md:text-5xl">
Deploy everywhere
</h2>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{[
{ name: "Roblox", color: "#e11d48", lang: "Luau", users: "70M+ daily players" },
{ name: "UEFN", color: "#8b5cf6", lang: "Verse", users: "Fortnite Creative" },
{ name: "Spatial", color: "#10b981", lang: "C#", users: "Metaverse platform" },
{ name: "Web", color: "#3b82f6", lang: "TypeScript", users: "HTML5 Canvas" },
].map((platform) => (
<div key={platform.name} className="rounded-2xl border border-[#1a1a1a] bg-[#0d0d0d] p-6 text-center hover:border-[#333] transition-colors">
<div className="mx-auto mb-4 h-16 w-16 rounded-2xl flex items-center justify-center" style={{ backgroundColor: platform.color + "15" }}>
<div className="h-8 w-8 rounded-lg" style={{ backgroundColor: platform.color }} />
</div>
<h3 className="text-xl font-bold text-white">{platform.name}</h3>
<p className="mt-1 text-sm text-purple-400 font-medium">{platform.lang}</p>
<p className="mt-2 text-xs text-[#666]">{platform.users}</p>
</div>
))}
</div>
</div>
</section>
{/* Pricing */}
<section id="pricing" className="border-t border-[#1a1a1a] bg-[#0d0d0d] px-6 py-20 md:py-28">
<div className="mx-auto max-w-6xl">
<div className="text-center mb-16">
<p className="text-sm font-medium uppercase tracking-widest text-[#666]">Pricing</p>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-white md:text-5xl">
Simple, transparent pricing
</h2>
<p className="mx-auto mt-4 max-w-xl text-lg text-[#888]">
Start free. Upgrade when you need more power.
</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
<div className="rounded-2xl border border-[#1a1a1a] bg-[#0a0a0a] p-6">
<h3 className="text-xl font-bold text-white">Free</h3>
<p className="mt-2 text-sm text-[#666] h-10">Get started with AeThex</p>
<p className="mt-4"><span className="text-4xl font-bold text-white">$0</span><span className="text-[#666] ml-1">/mo</span></p>
<Link href="/auth/signup" className="mt-6 block w-full rounded-lg border border-[#333] py-2.5 text-center text-sm font-medium text-white hover:bg-[#1a1a1a] transition-colors">
Get Started
</Link>
<ul className="mt-6 space-y-3">
{["50 AI generations/month", "2 projects", "Community support", "All 4 platforms"].map((item, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-[#888]">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2" className="mt-0.5 shrink-0"><polyline points="20 6 9 17 4 12"/></svg>
{item}
</li>
))}
</ul>
</div>
<div className="rounded-2xl border-2 border-purple-500 bg-gradient-to-b from-purple-500/10 to-[#0a0a0a] p-6 relative">
<div className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 bg-purple-500 rounded-full text-xs font-medium text-white">Popular</div>
<h3 className="text-xl font-bold text-white">Pro</h3>
<p className="mt-2 text-sm text-[#666] h-10">For serious game developers</p>
<p className="mt-4"><span className="text-4xl font-bold text-white">$15</span><span className="text-[#666] ml-1">/mo</span></p>
<Link href="/auth/signup" className="mt-6 block w-full rounded-lg bg-purple-500 py-2.5 text-center text-sm font-medium text-white hover:bg-purple-600 transition-colors">
Get Started
</Link>
<ul className="mt-6 space-y-3">
{["500 AI generations/month", "Unlimited projects", "Priority support", "Advanced AI models", "Team collaboration"].map((item, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-[#888]">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2" className="mt-0.5 shrink-0"><polyline points="20 6 9 17 4 12"/></svg>
{item}
</li>
))}
</ul>
</div>
<div className="rounded-2xl border border-[#1a1a1a] bg-[#0a0a0a] p-6">
<h3 className="text-xl font-bold text-white">Team</h3>
<p className="mt-2 text-sm text-[#666] h-10">For studios and teams</p>
<p className="mt-4"><span className="text-4xl font-bold text-white">$40</span><span className="text-[#666] ml-1">/mo</span></p>
<Link href="/auth/signup" className="mt-6 block w-full rounded-lg border border-[#333] py-2.5 text-center text-sm font-medium text-white hover:bg-[#1a1a1a] transition-colors">
Get Started
</Link>
<ul className="mt-6 space-y-3">
{["2000 AI generations/month", "Shared team workspace", "Admin controls", "Custom templates", "Dedicated support"].map((item, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-[#888]">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2" className="mt-0.5 shrink-0"><polyline points="20 6 9 17 4 12"/></svg>
{item}
</li>
))}
</ul>
</div>
<div className="rounded-2xl border border-[#1a1a1a] bg-[#0a0a0a] p-6">
<h3 className="text-xl font-bold text-white">Enterprise</h3>
<p className="mt-2 text-sm text-[#666] h-10">Custom solutions at scale</p>
<p className="mt-4"><span className="text-4xl font-bold text-white">Custom</span></p>
<a href="mailto:enterprise@aethex.studio" className="mt-6 block w-full rounded-lg border border-[#333] py-2.5 text-center text-sm font-medium text-white hover:bg-[#1a1a1a] transition-colors">
Contact Sales
</a>
<ul className="mt-6 space-y-3">
{["Unlimited generations", "SLA guarantee", "On-premise option", "Custom integrations", "24/7 support"].map((item, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-[#888]">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2" className="mt-0.5 shrink-0"><polyline points="20 6 9 17 4 12"/></svg>
{item}
</li>
))}
</ul>
</div>
</div>
</div>
</section>
{/* Final CTA */}
<section className="px-6 py-20 md:py-28">
<div className="mx-auto max-w-4xl text-center">
<h2 className="text-3xl font-bold tracking-tight text-white md:text-5xl">
Ready to build cross-platform games?
</h2>
<p className="mx-auto mt-4 max-w-xl text-lg text-[#888]">
Join thousands of developers building the next generation of games.
</p>
<div className="mt-10 flex flex-col sm:flex-row items-center justify-center gap-4">
<Link href="/ide" className="flex items-center gap-2 rounded-full bg-white px-8 py-3 text-base font-medium text-[#0a0a0a] transition-all hover:bg-[#e5e5e5]">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
Launch IDE
</Link>
<a href="https://github.com/AeThex-LABS/aethex-studio" target="_blank" rel="noopener noreferrer" className="flex items-center gap-2 rounded-full border border-[#333] px-8 py-3 text-base font-medium text-white transition-all hover:bg-[#1a1a1a]">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
Star on GitHub
</a>
</div>
</div>
</section>
{/* Footer */}
<footer className="border-t border-[#1a1a1a] bg-[#0a0a0a]">
<div className="mx-auto max-w-6xl px-6 py-12">
<div className="grid gap-8 md:grid-cols-4">
<div className="md:col-span-1">
<div className="flex items-center gap-2 mb-4">
<div className="h-8 w-8 rounded-lg bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<span className="text-lg font-bold text-white">AeThex</span>
</div>
<p className="text-sm text-[#666]">The cross-platform game development IDE.</p>
</div>
<div>
<h4 className="text-sm font-semibold text-white mb-4">Product</h4>
<ul className="space-y-2 text-sm">
<li><Link href="/ide" className="text-[#666] hover:text-white transition-colors">IDE</Link></li>
<li><a href="#features" className="text-[#666] hover:text-white transition-colors">Features</a></li>
<li><a href="#pricing" className="text-[#666] hover:text-white transition-colors">Pricing</a></li>
</ul>
</div>
<div>
<h4 className="text-sm font-semibold text-white mb-4">Ecosystem</h4>
<ul className="space-y-2 text-sm">
<li><a href="https://aethex.dev" className="text-[#666] hover:text-white transition-colors">aethex.dev</a></li>
<li><a href="https://aethex.foundation" className="text-[#666] hover:text-white transition-colors">aethex.foundation</a></li>
<li><a href="https://aethex.studio" className="text-purple-400 hover:text-purple-300 transition-colors">aethex.studio</a></li>
</ul>
</div>
<div>
<h4 className="text-sm font-semibold text-white mb-4">Legal</h4>
<ul className="space-y-2 text-sm">
<li><Link href="/terms" className="text-[#666] hover:text-white transition-colors">Terms</Link></li>
<li><Link href="/privacy" className="text-[#666] hover:text-white transition-colors">Privacy</Link></li>
</ul>
</div>
</div>
<div className="mt-12 pt-8 border-t border-[#1a1a1a] flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-xs text-[#444]">© 2026 AeThex Labs. All rights reserved.</p>
<div className="flex items-center gap-4">
<a href="https://github.com/AeThex-LABS" target="_blank" rel="noopener noreferrer" className="text-[#666] hover:text-white transition-colors">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
</a>
<a href="https://discord.gg/aethex" target="_blank" rel="noopener noreferrer" className="text-[#666] hover:text-white transition-colors">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
</a>
<a href="https://x.com/aethexlabs" target="_blank" rel="noopener noreferrer" className="text-[#666] hover:text-white transition-colors">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</main>
);
}

View file

@ -1,249 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSupabaseAuth, useProfile } from "@/hooks/use-supabase";
import { getSupabase } from "@/lib/supabase/client";
export default function ProfilePage() {
const router = useRouter();
const { user, loading: authLoading, signOut } = useSupabaseAuth();
const { profile, loading: profileLoading } = useProfile();
const [username, setUsername] = useState("");
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState("");
useEffect(() => {
if (!authLoading && !user) {
router.push("/auth/login");
}
}, [user, authLoading, router]);
useEffect(() => {
if (profile?.username) {
setUsername(profile.username);
}
}, [profile]);
const handleSave = async () => {
if (!user) return;
setSaving(true);
setMessage("");
const supabase = getSupabase();
const { error } = await supabase
.from("profiles")
.update({ username, updated_at: new Date().toISOString() })
.eq("id", user.id);
if (error) {
setMessage("Error saving profile: " + error.message);
} else {
setMessage("Profile saved successfully!");
}
setSaving(false);
};
const handleSignOut = async () => {
await signOut();
router.push("/");
};
if (authLoading || profileLoading) {
return (
<main className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="animate-spin h-8 w-8 border-2 border-purple-500 border-t-transparent rounded-full"></div>
</main>
);
}
if (!user) {
return null;
}
const subscriptionColors: Record<string, string> = {
free: "bg-gray-500",
studio: "bg-blue-500",
pro: "bg-purple-500",
enterprise: "bg-gradient-to-r from-purple-500 to-blue-500",
};
return (
<main className="min-h-screen bg-[#0a0a0a]">
{/* Header */}
<nav className="border-b border-[#222] bg-[#141414]">
<div className="max-w-4xl mx-auto px-4 h-14 flex items-center justify-between">
<Link href="/" className="flex items-center gap-2">
<div className="h-8 w-8 rounded-lg bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
</div>
<span className="text-lg font-bold text-white">AeThex Studio</span>
</Link>
<div className="flex items-center gap-4">
<Link href="/ide" className="text-sm text-[#888] hover:text-white transition-colors">
Open IDE
</Link>
<button
onClick={handleSignOut}
className="text-sm text-[#888] hover:text-white transition-colors"
>
Sign out
</button>
</div>
</div>
</nav>
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold text-white mb-8">Profile</h1>
{message && (
<div className={`px-4 py-3 rounded-lg mb-6 text-sm ${
message.includes("Error")
? "bg-red-500/10 border border-red-500/20 text-red-400"
: "bg-green-500/10 border border-green-500/20 text-green-400"
}`}>
{message}
</div>
)}
<div className="grid gap-6 md:grid-cols-2">
{/* Profile Card */}
<div className="bg-[#141414] border border-[#222] rounded-2xl p-6">
<div className="flex items-start gap-4 mb-6">
<div className="h-16 w-16 rounded-full bg-gradient-to-br from-purple-500 to-blue-600 flex items-center justify-center text-2xl font-bold text-white">
{username?.[0]?.toUpperCase() || user.email?.[0]?.toUpperCase() || "?"}
</div>
<div className="flex-1">
<h2 className="text-xl font-semibold text-white">
{username || "Set your username"}
</h2>
<p className="text-[#888] text-sm">{user.email}</p>
<div className="mt-2">
<span className={`inline-block px-2 py-1 rounded text-xs font-medium text-white ${subscriptionColors[profile?.subscription_tier || "free"]}`}>
{(profile?.subscription_tier || "free").toUpperCase()}
</span>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-[#888] mb-2">
Username
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
placeholder="Enter username"
/>
</div>
<div>
<label className="block text-sm font-medium text-[#888] mb-2">
Email
</label>
<input
type="email"
value={user.email || ""}
disabled
className="w-full bg-[#1b1b1b] border border-[#333] rounded-lg px-4 py-3 text-[#666] cursor-not-allowed"
/>
</div>
<button
onClick={handleSave}
disabled={saving}
className="w-full bg-white hover:bg-[#e5e5e5] text-[#0a0a0a] font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-50"
>
{saving ? "Saving..." : "Save changes"}
</button>
</div>
</div>
{/* Stats Card */}
<div className="bg-[#141414] border border-[#222] rounded-2xl p-6">
<h3 className="text-lg font-semibold text-white mb-6">Usage</h3>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-[#888]">Translations used</span>
<span className="text-white font-medium">{profile?.translation_count || 0}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-[#888]">Account created</span>
<span className="text-white font-medium">
{profile?.created_at ? new Date(profile.created_at).toLocaleDateString() : "—"}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-[#888]">Subscription</span>
<span className="text-white font-medium capitalize">{profile?.subscription_tier || "Free"}</span>
</div>
</div>
{profile?.subscription_tier === "free" && (
<div className="mt-6 p-4 bg-purple-500/10 border border-purple-500/20 rounded-lg">
<p className="text-purple-400 text-sm mb-3">Upgrade to Pro for unlimited translations</p>
<Link
href="/pricing"
className="inline-block bg-purple-500 hover:bg-purple-600 text-white font-medium py-2 px-4 rounded-lg text-sm transition-colors"
>
View plans
</Link>
</div>
)}
</div>
{/* Connected Accounts */}
<div className="bg-[#141414] border border-[#222] rounded-2xl p-6 md:col-span-2">
<h3 className="text-lg font-semibold text-white mb-6">Connected accounts</h3>
<div className="grid gap-4 sm:grid-cols-3">
<div className="flex items-center gap-3 p-4 bg-[#1b1b1b] rounded-lg border border-[#333]">
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
<div>
<p className="text-white font-medium">GitHub</p>
<p className="text-[#666] text-xs">Not connected</p>
</div>
</div>
<div className="flex items-center gap-3 p-4 bg-[#1b1b1b] rounded-lg border border-[#333]">
<svg width="24" height="24" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
<div>
<p className="text-white font-medium">Google</p>
<p className="text-[#666] text-xs">Not connected</p>
</div>
</div>
<div className="flex items-center gap-3 p-4 bg-[#1b1b1b] rounded-lg border border-[#333]">
<svg width="24" height="24" viewBox="0 0 24 24" fill="#5865F2">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
</svg>
<div>
<p className="text-white font-medium">Discord</p>
<p className="text-[#666] text-xs">Not connected</p>
</div>
</div>
</div>
</div>
</div>
{/* Ecosystem Links */}
<div className="mt-12 text-center border-t border-[#222] pt-8">
<p className="text-[#666] text-xs mb-3">Your AeThex account works across</p>
<div className="flex items-center justify-center gap-6 text-sm">
<a href="https://aethex.dev" className="text-[#888] hover:text-white transition-colors">aethex.dev</a>
<a href="https://aethex.foundation" className="text-[#888] hover:text-white transition-colors">aethex.foundation</a>
<a href="https://aethex.studio" className="text-purple-400 hover:text-purple-300 transition-colors">aethex.studio</a>
</div>
</div>
</div>
</main>
);
}

View file

@ -4,7 +4,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Textarea } from '@/components/ui/textarea';
import { Sparkle, PaperPlaneRight } from '@phosphor-icons/react';
import { toast } from 'sonner';
import { captureError } from '../lib/sentry';
import { captureError } from '@/lib/sentry';
interface Message {
role: 'user' | 'assistant';

View file

@ -0,0 +1,925 @@
'use client';
import { useState, useCallback, useMemo } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import {
Upload,
Download,
Settings,
Wand2,
CheckCircle2,
XCircle,
AlertTriangle,
Info,
ChevronRight,
RefreshCw,
Sparkles,
Users,
Box,
Gamepad2,
Glasses,
PartyPopper,
Globe,
Cpu,
Heart,
Landmark,
Headphones,
ArrowLeftRight,
FileType,
Bone,
Layers,
ImageIcon,
FileBox,
Zap,
} from 'lucide-react';
import {
AvatarPlatformId,
avatarPlatforms,
supportedPlatforms,
getConstraintsForPlatform,
} from '@/lib/avatar-platforms';
import {
AvatarFileFormat,
ParsedAvatar,
AvatarValidationResult,
ExportOptions,
FORMAT_SPECS,
getSupportedImportFormats,
getSupportedExportFormats,
validateForPlatform,
createDemoAvatar,
generateExportFilename,
getOptimizationRecommendations,
} from '@/lib/avatar-formats';
import {
getConversionPaths,
calculatePlatformCompatibility,
} from '@/lib/avatar-rigging';
import {
avatarTemplates,
platformPresets,
getTemplatesForPlatform,
getPresetsForPlatform,
AvatarTemplate,
AvatarPreset,
} from '@/lib/templates-avatars';
interface AvatarToolkitProps {
isOpen: boolean;
onClose: () => void;
}
type TabValue = 'import' | 'export' | 'convert' | 'templates' | 'validate';
// Platform icon mapping
const platformIcons: Record<AvatarPlatformId, React.ReactNode> = {
roblox: <Gamepad2 className="h-4 w-4" />,
vrchat: <Glasses className="h-4 w-4" />,
recroom: <PartyPopper className="h-4 w-4" />,
spatial: <Globe className="h-4 w-4" />,
sandbox: <Box className="h-4 w-4" />,
neos: <Cpu className="h-4 w-4" />,
resonite: <Sparkles className="h-4 w-4" />,
chilloutvr: <Heart className="h-4 w-4" />,
decentraland: <Landmark className="h-4 w-4" />,
'meta-horizon': <Headphones className="h-4 w-4" />,
universal: <Sparkles className="h-4 w-4" />,
};
export default function AvatarToolkit({ isOpen, onClose }: AvatarToolkitProps) {
const [activeTab, setActiveTab] = useState<TabValue>('import');
const [selectedPlatform, setSelectedPlatform] = useState<AvatarPlatformId>('universal');
const [importedAvatar, setImportedAvatar] = useState<ParsedAvatar | null>(null);
const [validationResult, setValidationResult] = useState<AvatarValidationResult | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
const [processingProgress, setProcessingProgress] = useState(0);
const [selectedTemplate, setSelectedTemplate] = useState<AvatarTemplate | null>(null);
const [selectedPreset, setSelectedPreset] = useState<AvatarPreset | null>(null);
// Export options state
const [exportOptions, setExportOptions] = useState<Partial<ExportOptions>>({
optimizeForPlatform: true,
embedTextures: true,
compressTextures: true,
preserveBlendShapes: true,
preserveAnimations: true,
generateLODs: false,
});
// Conversion state
const [sourcePlatform, setSourcePlatform] = useState<AvatarPlatformId>('universal');
const [targetPlatform, setTargetPlatform] = useState<AvatarPlatformId>('vrchat');
// Handle file import
const handleFileImport = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
setIsProcessing(true);
setProcessingProgress(0);
// Simulate file processing with progress
const progressInterval = setInterval(() => {
setProcessingProgress((prev) => Math.min(prev + 10, 90));
}, 200);
try {
// In a real implementation, this would parse the actual file
// For demo purposes, we create a mock avatar
await new Promise((resolve) => setTimeout(resolve, 2000));
const demoAvatar = createDemoAvatar(file.name.replace(/\.[^.]+$/, ''), selectedPlatform);
setImportedAvatar(demoAvatar);
// Validate against selected platform
const validation = validateForPlatform(demoAvatar, selectedPlatform);
setValidationResult(validation);
setProcessingProgress(100);
} catch (error) {
console.error('Import error:', error);
} finally {
clearInterval(progressInterval);
setIsProcessing(false);
}
}, [selectedPlatform]);
// Handle export
const handleExport = useCallback(async () => {
if (!importedAvatar) return;
setIsProcessing(true);
setProcessingProgress(0);
const progressInterval = setInterval(() => {
setProcessingProgress((prev) => Math.min(prev + 15, 90));
}, 200);
try {
await new Promise((resolve) => setTimeout(resolve, 1500));
const format = avatarPlatforms[selectedPlatform].exportFormat as AvatarFileFormat;
const filename = generateExportFilename(importedAvatar.metadata.name, selectedPlatform, format);
// Create a demo export blob
const exportData = JSON.stringify({
avatar: importedAvatar,
platform: selectedPlatform,
options: exportOptions,
exportedAt: new Date().toISOString(),
}, null, 2);
const blob = new Blob([exportData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename.replace(/\.[^.]+$/, '.json'); // Demo uses JSON
a.click();
URL.revokeObjectURL(url);
setProcessingProgress(100);
} catch (error) {
console.error('Export error:', error);
} finally {
clearInterval(progressInterval);
setIsProcessing(false);
}
}, [importedAvatar, selectedPlatform, exportOptions]);
// Get conversion paths
const conversionPaths = useMemo(() => {
return getConversionPaths(sourcePlatform);
}, [sourcePlatform]);
// Get templates for selected platform
const platformTemplates = useMemo(() => {
return getTemplatesForPlatform(selectedPlatform);
}, [selectedPlatform]);
// Get presets for selected platform
const platformPresetsList = useMemo(() => {
return getPresetsForPlatform(selectedPlatform);
}, [selectedPlatform]);
// Apply preset settings
const applyPreset = useCallback((preset: AvatarPreset) => {
setSelectedPreset(preset);
setExportOptions({
...exportOptions,
optimizeForPlatform: true,
preserveBlendShapes: preset.settings.preserveBlendShapes,
generateLODs: preset.settings.generateLODs,
});
}, [exportOptions]);
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-5xl max-h-[85vh] flex flex-col">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-primary" />
AeThex Avatar Toolkit
</DialogTitle>
<DialogDescription>
Import, export, and convert avatars across platforms like Roblox, VRChat, RecRoom, Spatial, and more
</DialogDescription>
</DialogHeader>
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as TabValue)} className="flex-1 flex flex-col min-h-0">
<TabsList className="grid grid-cols-5 w-full">
<TabsTrigger value="import" className="flex items-center gap-1.5">
<Upload className="h-4 w-4" />
Import
</TabsTrigger>
<TabsTrigger value="export" className="flex items-center gap-1.5">
<Download className="h-4 w-4" />
Export
</TabsTrigger>
<TabsTrigger value="convert" className="flex items-center gap-1.5">
<ArrowLeftRight className="h-4 w-4" />
Convert
</TabsTrigger>
<TabsTrigger value="templates" className="flex items-center gap-1.5">
<Users className="h-4 w-4" />
Templates
</TabsTrigger>
<TabsTrigger value="validate" className="flex items-center gap-1.5">
<CheckCircle2 className="h-4 w-4" />
Validate
</TabsTrigger>
</TabsList>
<div className="flex-1 min-h-0 mt-4">
{/* Import Tab */}
<TabsContent value="import" className="h-full m-0">
<div className="grid grid-cols-2 gap-6 h-full">
<div className="space-y-4">
<div>
<Label className="text-sm font-medium">Target Platform</Label>
<Select value={selectedPlatform} onValueChange={(v) => setSelectedPlatform(v as AvatarPlatformId)}>
<SelectTrigger className="mt-1.5">
<SelectValue />
</SelectTrigger>
<SelectContent>
{supportedPlatforms.map((platform) => (
<SelectItem key={platform.id} value={platform.id}>
<div className="flex items-center gap-2">
{platformIcons[platform.id]}
<span>{platform.displayName}</span>
{platform.status === 'beta' && (
<Badge variant="secondary" className="text-xs">Beta</Badge>
)}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="border-2 border-dashed rounded-lg p-8 text-center">
<input
type="file"
accept=".glb,.gltf,.fbx,.vrm,.obj,.pmx"
onChange={handleFileImport}
className="hidden"
id="avatar-import"
disabled={isProcessing}
/>
<label htmlFor="avatar-import" className="cursor-pointer">
<Upload className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
<p className="font-medium">Drop your avatar file here</p>
<p className="text-sm text-muted-foreground mt-1">
Supports GLB, GLTF, FBX, VRM, OBJ, PMX
</p>
<Button variant="outline" className="mt-4" disabled={isProcessing}>
Browse Files
</Button>
</label>
</div>
{isProcessing && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span>Processing...</span>
<span>{processingProgress}%</span>
</div>
<Progress value={processingProgress} />
</div>
)}
<div>
<Label className="text-sm font-medium">Supported Formats</Label>
<div className="flex flex-wrap gap-2 mt-2">
{getSupportedImportFormats().map((format) => (
<Badge key={format} variant="outline">
{FORMAT_SPECS[format].extension}
</Badge>
))}
</div>
</div>
</div>
<div className="space-y-4">
<h4 className="font-medium">Platform Requirements</h4>
{selectedPlatform && (
<div className="space-y-3 text-sm">
{(() => {
const constraints = getConstraintsForPlatform(selectedPlatform);
const platform = avatarPlatforms[selectedPlatform];
return (
<>
<div className="flex items-center justify-between p-2 bg-muted/50 rounded">
<span className="flex items-center gap-2">
<Layers className="h-4 w-4" /> Max Polygons
</span>
<span className="font-mono">{constraints.maxPolygons.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between p-2 bg-muted/50 rounded">
<span className="flex items-center gap-2">
<Bone className="h-4 w-4" /> Max Bones
</span>
<span className="font-mono">{constraints.maxBones}</span>
</div>
<div className="flex items-center justify-between p-2 bg-muted/50 rounded">
<span className="flex items-center gap-2">
<Box className="h-4 w-4" /> Max Materials
</span>
<span className="font-mono">{constraints.maxMaterials}</span>
</div>
<div className="flex items-center justify-between p-2 bg-muted/50 rounded">
<span className="flex items-center gap-2">
<ImageIcon className="h-4 w-4" /> Max Texture Size
</span>
<span className="font-mono">{constraints.maxTextureSize}px</span>
</div>
<div className="flex items-center justify-between p-2 bg-muted/50 rounded">
<span className="flex items-center gap-2">
<FileBox className="h-4 w-4" /> Max File Size
</span>
<span className="font-mono">{constraints.maxFileSize}MB</span>
</div>
<Separator />
<div>
<span className="font-medium">Supported Features:</span>
<div className="flex flex-wrap gap-1.5 mt-2">
{platform.features.map((feature) => (
<Badge key={feature} variant="secondary" className="text-xs">
{feature}
</Badge>
))}
</div>
</div>
</>
);
})()}
</div>
)}
</div>
</div>
</TabsContent>
{/* Export Tab */}
<TabsContent value="export" className="h-full m-0">
<ScrollArea className="h-[400px]">
<div className="grid grid-cols-2 gap-6 pr-4">
<div className="space-y-4">
<div>
<Label className="text-sm font-medium">Export Platform</Label>
<Select value={selectedPlatform} onValueChange={(v) => setSelectedPlatform(v as AvatarPlatformId)}>
<SelectTrigger className="mt-1.5">
<SelectValue />
</SelectTrigger>
<SelectContent>
{supportedPlatforms.map((platform) => (
<SelectItem key={platform.id} value={platform.id}>
<div className="flex items-center gap-2">
{platformIcons[platform.id]}
<span>{platform.displayName}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label className="text-sm font-medium">Export Preset</Label>
<Select
value={selectedPreset?.id || ''}
onValueChange={(v) => {
const preset = platformPresetsList.find((p) => p.id === v);
if (preset) applyPreset(preset);
}}
>
<SelectTrigger className="mt-1.5">
<SelectValue placeholder="Select a preset..." />
</SelectTrigger>
<SelectContent>
{platformPresetsList.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
<div>
<div className="font-medium">{preset.name}</div>
<div className="text-xs text-muted-foreground">{preset.description}</div>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Separator />
<div className="space-y-3">
<h4 className="font-medium text-sm">Export Options</h4>
<div className="flex items-center justify-between">
<Label htmlFor="optimize" className="text-sm">Optimize for Platform</Label>
<Switch
id="optimize"
checked={exportOptions.optimizeForPlatform}
onCheckedChange={(v) => setExportOptions({ ...exportOptions, optimizeForPlatform: v })}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="embed" className="text-sm">Embed Textures</Label>
<Switch
id="embed"
checked={exportOptions.embedTextures}
onCheckedChange={(v) => setExportOptions({ ...exportOptions, embedTextures: v })}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="compress" className="text-sm">Compress Textures</Label>
<Switch
id="compress"
checked={exportOptions.compressTextures}
onCheckedChange={(v) => setExportOptions({ ...exportOptions, compressTextures: v })}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="blendshapes" className="text-sm">Preserve Blend Shapes</Label>
<Switch
id="blendshapes"
checked={exportOptions.preserveBlendShapes}
onCheckedChange={(v) => setExportOptions({ ...exportOptions, preserveBlendShapes: v })}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="animations" className="text-sm">Preserve Animations</Label>
<Switch
id="animations"
checked={exportOptions.preserveAnimations}
onCheckedChange={(v) => setExportOptions({ ...exportOptions, preserveAnimations: v })}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="lods" className="text-sm">Generate LODs</Label>
<Switch
id="lods"
checked={exportOptions.generateLODs}
onCheckedChange={(v) => setExportOptions({ ...exportOptions, generateLODs: v })}
/>
</div>
</div>
</div>
<div className="space-y-4">
{importedAvatar ? (
<>
<div className="p-4 border rounded-lg">
<h4 className="font-medium mb-3">Avatar Preview</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Name:</span>
<span className="font-medium">{importedAvatar.metadata.name}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Triangles:</span>
<span className="font-mono">{importedAvatar.stats.totalTriangles.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Bones:</span>
<span className="font-mono">{importedAvatar.stats.totalBones}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Materials:</span>
<span className="font-mono">{importedAvatar.stats.totalMaterials}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Blend Shapes:</span>
<span className="font-mono">{importedAvatar.stats.totalBlendShapes}</span>
</div>
</div>
</div>
<div className="flex gap-2">
<Button onClick={handleExport} disabled={isProcessing} className="flex-1">
<Download className="h-4 w-4 mr-2" />
Export for {avatarPlatforms[selectedPlatform].name}
</Button>
</div>
{isProcessing && (
<Progress value={processingProgress} />
)}
</>
) : (
<div className="flex flex-col items-center justify-center h-full text-center p-8 border-2 border-dashed rounded-lg">
<Upload className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground">
Import an avatar first to enable export options
</p>
<Button
variant="outline"
className="mt-4"
onClick={() => setActiveTab('import')}
>
Go to Import
</Button>
</div>
)}
</div>
</div>
</ScrollArea>
</TabsContent>
{/* Convert Tab */}
<TabsContent value="convert" className="h-full m-0">
<div className="grid grid-cols-3 gap-6">
<div className="space-y-4">
<div>
<Label className="text-sm font-medium">Source Platform</Label>
<Select value={sourcePlatform} onValueChange={(v) => setSourcePlatform(v as AvatarPlatformId)}>
<SelectTrigger className="mt-1.5">
<SelectValue />
</SelectTrigger>
<SelectContent>
{supportedPlatforms.map((platform) => (
<SelectItem key={platform.id} value={platform.id}>
<div className="flex items-center gap-2">
{platformIcons[platform.id]}
<span>{platform.displayName}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="text-center py-4">
<ArrowLeftRight className="h-8 w-8 mx-auto text-muted-foreground" />
</div>
<div>
<Label className="text-sm font-medium">Target Platform</Label>
<Select value={targetPlatform} onValueChange={(v) => setTargetPlatform(v as AvatarPlatformId)}>
<SelectTrigger className="mt-1.5">
<SelectValue />
</SelectTrigger>
<SelectContent>
{supportedPlatforms.map((platform) => (
<SelectItem key={platform.id} value={platform.id}>
<div className="flex items-center gap-2">
{platformIcons[platform.id]}
<span>{platform.displayName}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-sm font-medium mb-2">Compatibility Score</div>
<div className="text-3xl font-bold text-primary">
{calculatePlatformCompatibility(sourcePlatform, targetPlatform)}%
</div>
</div>
</div>
<div className="col-span-2">
<h4 className="font-medium mb-3">Conversion Paths from {avatarPlatforms[sourcePlatform].name}</h4>
<ScrollArea className="h-[350px]">
<div className="space-y-2 pr-4">
{conversionPaths.map((path) => (
<div
key={path.target}
className={`p-3 border rounded-lg cursor-pointer transition-colors ${
path.target === targetPlatform
? 'border-primary bg-primary/5'
: 'hover:bg-muted/50'
}`}
onClick={() => setTargetPlatform(path.target)}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
{platformIcons[path.target]}
<span className="font-medium">{avatarPlatforms[path.target].displayName}</span>
</div>
<Badge variant={path.compatibility >= 80 ? 'default' : path.compatibility >= 60 ? 'secondary' : 'outline'}>
{path.compatibility}% compatible
</Badge>
</div>
{path.warnings.length > 0 && (
<div className="space-y-1">
{path.warnings.map((warning, i) => (
<div key={i} className="flex items-center gap-1.5 text-xs text-muted-foreground">
<AlertTriangle className="h-3 w-3 text-yellow-500" />
{warning}
</div>
))}
</div>
)}
</div>
))}
</div>
</ScrollArea>
</div>
</div>
</TabsContent>
{/* Templates Tab */}
<TabsContent value="templates" className="h-full m-0">
<div className="space-y-4">
<div className="flex items-center gap-4">
<div className="flex-1">
<Input placeholder="Search templates..." />
</div>
<Select value={selectedPlatform} onValueChange={(v) => setSelectedPlatform(v as AvatarPlatformId)}>
<SelectTrigger className="w-[200px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{supportedPlatforms.map((platform) => (
<SelectItem key={platform.id} value={platform.id}>
<div className="flex items-center gap-2">
{platformIcons[platform.id]}
<span>{platform.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<ScrollArea className="h-[350px]">
<div className="grid grid-cols-3 gap-4 pr-4">
{(selectedPlatform === 'universal' ? avatarTemplates : platformTemplates).map((template) => (
<div
key={template.id}
className={`p-4 border rounded-lg cursor-pointer transition-all ${
selectedTemplate?.id === template.id
? 'border-primary ring-2 ring-primary/20'
: 'hover:border-primary/50'
}`}
onClick={() => setSelectedTemplate(template)}
>
<div className="aspect-square bg-muted rounded mb-3 flex items-center justify-center">
<Users className="h-12 w-12 text-muted-foreground" />
</div>
<h4 className="font-medium text-sm">{template.name}</h4>
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
{template.description}
</p>
<div className="flex flex-wrap gap-1 mt-2">
<Badge variant="outline" className="text-xs">{template.style}</Badge>
<Badge variant="outline" className="text-xs">{template.polyCount} poly</Badge>
</div>
<div className="flex gap-1 mt-2">
{template.platforms.slice(0, 3).map((p) => (
<span key={p} className="text-muted-foreground">
{platformIcons[p]}
</span>
))}
{template.platforms.length > 3 && (
<span className="text-xs text-muted-foreground">+{template.platforms.length - 3}</span>
)}
</div>
</div>
))}
</div>
</ScrollArea>
{selectedTemplate && (
<div className="p-4 border rounded-lg bg-muted/30">
<div className="flex items-start justify-between">
<div>
<h4 className="font-medium">{selectedTemplate.name}</h4>
<p className="text-sm text-muted-foreground mt-1">{selectedTemplate.description}</p>
</div>
<Button size="sm">
<Download className="h-4 w-4 mr-1.5" />
Use Template
</Button>
</div>
<div className="flex flex-wrap gap-1.5 mt-3">
{selectedTemplate.features.map((feature) => (
<Badge key={feature} variant="secondary" className="text-xs">{feature}</Badge>
))}
</div>
</div>
)}
</div>
</TabsContent>
{/* Validate Tab */}
<TabsContent value="validate" className="h-full m-0">
<div className="grid grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<Label className="text-sm font-medium">Validation Target</Label>
<Select value={selectedPlatform} onValueChange={(v) => setSelectedPlatform(v as AvatarPlatformId)}>
<SelectTrigger className="mt-1.5">
<SelectValue />
</SelectTrigger>
<SelectContent>
{supportedPlatforms.map((platform) => (
<SelectItem key={platform.id} value={platform.id}>
<div className="flex items-center gap-2">
{platformIcons[platform.id]}
<span>{platform.displayName}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{!importedAvatar ? (
<div className="border-2 border-dashed rounded-lg p-8 text-center">
<Wand2 className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
<p className="text-muted-foreground">
Import an avatar to validate it against platform requirements
</p>
<Button variant="outline" className="mt-4" onClick={() => setActiveTab('import')}>
Import Avatar
</Button>
</div>
) : (
<div className="space-y-3">
<Button
onClick={() => {
const validation = validateForPlatform(importedAvatar, selectedPlatform);
setValidationResult(validation);
}}
className="w-full"
>
<RefreshCw className="h-4 w-4 mr-2" />
Validate for {avatarPlatforms[selectedPlatform].name}
</Button>
{validationResult && (
<div className="p-4 border rounded-lg">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
{validationResult.isValid ? (
<CheckCircle2 className="h-5 w-5 text-green-500" />
) : (
<XCircle className="h-5 w-5 text-red-500" />
)}
<span className="font-medium">
{validationResult.isValid ? 'Compatible' : 'Issues Found'}
</span>
</div>
<Badge variant={validationResult.score >= 80 ? 'default' : validationResult.score >= 50 ? 'secondary' : 'destructive'}>
Score: {validationResult.score}%
</Badge>
</div>
<div className="space-y-2">
{Object.entries(validationResult.constraints).map(([key, value]) => (
<div key={key} className="flex items-center justify-between text-sm">
<span className="capitalize">{key.replace(/([A-Z])/g, ' $1').trim()}</span>
<div className="flex items-center gap-2">
<span className="font-mono text-xs">
{value.current.toLocaleString()} / {value.max.toLocaleString()}
</span>
{value.passed ? (
<CheckCircle2 className="h-4 w-4 text-green-500" />
) : (
<XCircle className="h-4 w-4 text-red-500" />
)}
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
<div className="space-y-4">
<h4 className="font-medium">Validation Results</h4>
{validationResult ? (
<ScrollArea className="h-[350px]">
<div className="space-y-3 pr-4">
{validationResult.issues.length === 0 ? (
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<div className="flex items-center gap-2 text-green-600">
<CheckCircle2 className="h-5 w-5" />
<span className="font-medium">All checks passed!</span>
</div>
<p className="text-sm text-muted-foreground mt-1">
Your avatar is fully compatible with {avatarPlatforms[selectedPlatform].name}
</p>
</div>
) : (
validationResult.issues.map((issue, i) => (
<div
key={i}
className={`p-3 border rounded-lg ${
issue.type === 'error'
? 'border-red-500/20 bg-red-500/5'
: issue.type === 'warning'
? 'border-yellow-500/20 bg-yellow-500/5'
: 'border-blue-500/20 bg-blue-500/5'
}`}
>
<div className="flex items-start gap-2">
{issue.type === 'error' ? (
<XCircle className="h-4 w-4 text-red-500 mt-0.5" />
) : issue.type === 'warning' ? (
<AlertTriangle className="h-4 w-4 text-yellow-500 mt-0.5" />
) : (
<Info className="h-4 w-4 text-blue-500 mt-0.5" />
)}
<div className="flex-1">
<p className="text-sm font-medium">{issue.message}</p>
{issue.details && (
<p className="text-xs text-muted-foreground mt-0.5">{issue.details}</p>
)}
{issue.autoFix && (
<Badge variant="outline" className="mt-2 text-xs">
<Zap className="h-3 w-3 mr-1" />
Auto-fixable
</Badge>
)}
</div>
</div>
</div>
))
)}
{validationResult.optimizationSuggestions.length > 0 && (
<>
<Separator />
<div className="space-y-2">
<h5 className="text-sm font-medium flex items-center gap-1.5">
<Wand2 className="h-4 w-4" />
Optimization Suggestions
</h5>
{validationResult.optimizationSuggestions.map((suggestion, i) => (
<div key={i} className="flex items-start gap-2 text-sm text-muted-foreground">
<ChevronRight className="h-4 w-4 mt-0.5" />
<span>{suggestion}</span>
</div>
))}
</div>
</>
)}
</div>
</ScrollArea>
) : (
<div className="flex items-center justify-center h-[350px] border-2 border-dashed rounded-lg">
<p className="text-muted-foreground">Validation results will appear here</p>
</div>
)}
</div>
</div>
</TabsContent>
</div>
</Tabs>
</DialogContent>
</Dialog>
);
}

View file

@ -1,9 +1,9 @@
import Editor from '@monaco-editor/react';
import { usePersistentState } from '../lib/usePersistentState';
import { usePersistentState } from '@/lib/usePersistentState';
import { useEffect, useMemo } from 'react';
import { LoadingSpinner } from './ui/loading-spinner';
import { toast } from 'sonner';
import { PlatformId } from '../lib/platforms';
import { PlatformId } from '@/lib/platforms';
interface CodeEditorProps {
onCodeChange?: (code: string) => void;

View file

@ -22,7 +22,6 @@ export interface Command {
action: () => void;
keywords?: string[];
}
export default CommandPalette;
interface CommandPaletteProps {
open: boolean;

View file

@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from './ui/badge';
import { Badge } from '@/components/ui/badge';
import { Trash, Terminal, Code } from '@phosphor-icons/react';
import { InteractiveTerminal } from './InteractiveTerminal';

View file

@ -47,7 +47,7 @@ export function EducationPanel() {
<div className="prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: chapter ? chapter.content.replace(/\n/g, '<br/>') : '' }} />
<div className="mt-4 flex justify-end">
<Button
variant={completed.includes(selected) ? 'outline' : 'secondary'}
variant={completed.includes(selected) ? 'outline' : 'accent'}
size="sm"
disabled={completed.includes(selected)}
onClick={() => markComplete(selected)}

View file

@ -1,9 +1,22 @@
import { useState, useCallback, memo } from 'react';
import { Button } from './ui/button';
import { ScrollArea } from './ui/scroll-area';
import { Input } from './ui/input';
import { File, Folder, FolderOpen, Plus, DotsThree, Trash, PencilSimple } from '@phosphor-icons/react';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Input } from '@/components/ui/input';
import {
File,
Folder,
FolderOpen,
Plus,
DotsThree,
Trash,
PencilSimple,
} from '@phosphor-icons/react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { toast } from 'sonner';
export interface FileNode {
@ -51,9 +64,28 @@ export function FileTree({
});
}, []);
const startRename = useCallback((node: FileNode) => {
setEditingId(node.id);
setEditingName(node.name);
}, []);
const finishRename = useCallback((id: string) => {
if (editingName.trim()) {
onFileRename(id, editingName.trim());
}
setEditingId(null);
setEditingName('');
}, [editingName, onFileRename]);
const handleDelete = useCallback((node: FileNode) => {
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
onFileDelete(node.id);
}
}, [onFileDelete]);
const handleDragStart = useCallback((e: React.DragEvent, node: FileNode) => {
e.stopPropagation();
setDraggedId(node.id);
setDropTargetId(null);
e.dataTransfer.effectAllowed = 'move';
}, []);
@ -102,23 +134,6 @@ export function FileTree({
setDropTargetId(null);
}, []);
const finishRename = (id: string) => {
if (editingName.trim() && editingId) {
onFileRename(editingId, editingName.trim());
}
setEditingId(null);
setEditingName('');
};
const startRename = (node: FileNode) => {
setEditingId(node.id);
setEditingName(node.name);
};
const handleDelete = (node: FileNode) => {
onFileDelete(node.id);
};
const renderNode = (node: FileNode, depth: number = 0) => {
const isExpanded = expandedFolders.has(node.id);
const isSelected = selectedFileId === node.id;

View file

@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Input } from '@/components/ui/input';
// import { executeCommand, CLIContext, CLIResult } from '@/lib/cli-commands';
import { executeCommand, CLIContext, CLIResult } from '@/lib/cli-commands';
import { toast } from 'sonner';
interface TerminalLine {
@ -73,8 +73,36 @@ export function InteractiveTerminal({
// Add input line
addLog(`$ ${command}`, 'input');
// Command execution logic removed: CLIContext, CLIResult, and executeCommand are not available.
addLog('Command execution is not available in this build.', 'error');
// Execute command
const context: CLIContext = {
currentCode,
currentFile,
files,
setCode: onCodeChange,
addLog,
};
try {
const result: CLIResult = await executeCommand(command, context);
// Handle special commands
if (result.output === '__CLEAR__') {
setLines([]);
return;
}
// Add output
if (result.output) {
addLog(result.output, result.type || 'log');
}
if (!result.success && result.type !== 'warn') {
toast.error(result.output);
}
} catch (error) {
addLog(`Error: ${error}`, 'error');
toast.error('Command execution failed');
}
}, [currentCode, currentFile, files, onCodeChange, addLog]);
const handleSubmit = (e: React.FormEvent) => {

View file

@ -16,7 +16,6 @@ interface NewProjectModalProps {
onClose: () => void;
onCreateProject: (config: ProjectConfig) => void;
}
export default NewProjectModal;
export interface ProjectConfig {
name: string;

View file

@ -9,7 +9,6 @@ interface PassportLoginProps {
onClose: () => void;
onLoginSuccess: (user: { login: string; avatarUrl: string; email: string }) => void;
}
export default PassportLogin;
export function PassportLogin({ open, onClose, onLoginSuccess }: PassportLoginProps) {
const handleLogin = async () => {
@ -33,7 +32,7 @@ export function PassportLogin({ open, onClose, onLoginSuccess }: PassportLoginPr
</DialogHeader>
<Card className="p-4 mt-2">
<p className="text-sm mb-4">Sign in with your AeThex Passport account to access private projects and sync your progress.</p>
<Button variant="secondary" className="w-full" onClick={handleLogin}>
<Button variant="accent" className="w-full" onClick={handleLogin}>
Sign in with Passport
</Button>
</Card>

View file

@ -6,16 +6,8 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Badge } from './ui/badge';
// Temporary stub for PlatformId, platforms, and activePlatforms
type PlatformId = string;
const platforms: Record<string, any> = {
web: { id: 'web', icon: '🌐', displayName: 'Web', status: 'stable' },
roblox: { id: 'roblox', icon: '🕹️', displayName: 'Roblox', status: 'beta' },
mobile: { id: 'mobile', icon: '📱', displayName: 'Mobile', status: 'beta' },
};
const activePlatforms = Object.values(platforms);
import { Badge } from '@/components/ui/badge';
import { PlatformId, platforms, activePlatforms } from '@/lib/platforms';
interface PlatformSelectorProps {
value: PlatformId;
@ -35,18 +27,12 @@ export const PlatformSelector = memo(function PlatformSelector({
<SelectTrigger className="w-[180px] h-8 text-xs">
<SelectValue>
<div className="flex items-center gap-2">
{currentPlatform ? (
<>
<span>{currentPlatform.icon}</span>
<span>{currentPlatform.displayName}</span>
{currentPlatform.status === 'beta' && (
<Badge variant="secondary" className="text-[10px] px-1 py-0">
BETA
</Badge>
)}
</>
) : (
<span className="text-muted-foreground">Unknown Platform</span>
<span>{currentPlatform.icon}</span>
<span>{currentPlatform.displayName}</span>
{currentPlatform.status === 'beta' && (
<Badge variant="secondary" className="text-[10px] px-1 py-0">
BETA
</Badge>
)}
</div>
</SelectValue>

View file

@ -2,8 +2,8 @@ import { useState } from 'react';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card } from './ui/card';
import { Badge } from './ui/badge';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { X, ArrowsClockwise } from '@phosphor-icons/react';
interface PreviewModalProps {
@ -11,7 +11,6 @@ interface PreviewModalProps {
onClose: () => void;
code: string;
}
export default PreviewModal;
interface SharedState {
variable: string;

View file

@ -2,7 +2,7 @@ import { useState, useCallback, useMemo } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Badge } from './ui/badge';
import { Badge } from '@/components/ui/badge';
import { MagnifyingGlass, X, FileText } from '@phosphor-icons/react';
import { FileNode } from './FileTree';
@ -168,7 +168,7 @@ export function SearchInFilesPanel({
<div className="px-4 py-2 space-y-1">
{results.length === 0 && searchQuery && !isSearching && (
<div className="text-center text-muted-foreground py-8 text-sm">
No results found for &quot;{searchQuery}&quot;
No results found for "{searchQuery}"
</div>
)}

View file

@ -1 +0,0 @@
export default function StudioBottomPanel() { return <footer>Bottom Panel</footer>; }

View file

@ -1 +0,0 @@
export default function StudioEditor() { return <div>Editor</div>; }

View file

@ -1 +0,0 @@
export default function StudioNetworkViz() { return <div>Network Viz</div>; }

View file

@ -1 +0,0 @@
export default function StudioRightPanel() { return <aside>Right Panel</aside>; }

View file

@ -1 +0,0 @@
export default function StudioSidebar() { return <aside>Sidebar</aside>; }

View file

@ -1 +0,0 @@
export default function Suspense({ children }: { children: React.ReactNode }) { return <>{children}</>; }

View file

@ -1,25 +1,17 @@
import { Button } from '@/components/ui/button';
import { Card } from './ui/card';
import { Card } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from './ui/badge';
import { Badge } from '@/components/ui/badge';
import { X } from '@phosphor-icons/react';
// import { templates, type ScriptTemplate, getTemplatesForPlatform } from '../lib/templates';
// Temporary stub for ScriptTemplate and getTemplatesForPlatform
type ScriptTemplate = { id: string; name: string; description: string; code: string; category: string; platform: string };
const getTemplatesForPlatform = (platform: string): ScriptTemplate[] => [];
// Temporary stub for PlatformId and getPlatform
type PlatformId = string;
const getPlatform = (id: PlatformId) => ({ id, icon: '❓', displayName: id, status: 'unknown' });
import { templates, type ScriptTemplate, getTemplatesForPlatform } from '@/lib/templates';
import { PlatformId, getPlatform } from '@/lib/platforms';
interface TemplatesDrawerProps {
onSelectTemplate: (code: string) => void;
onClose: () => void;
currentPlatform: PlatformId;
}
export default TemplatesDrawer;
export function TemplatesDrawer({ onSelectTemplate, onClose, currentPlatform }: TemplatesDrawerProps) {
const platform = getPlatform(currentPlatform);

View file

@ -8,14 +8,11 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
// import { useTheme, Theme } from '@/hooks/use-theme';
import { useTheme, Theme } from '@/hooks/use-theme';
import { Check } from '@phosphor-icons/react';
export function ThemeSwitcher() {
// Temporary stub for useTheme
const theme = 'light';
const setTheme = (_: string) => {};
const themes = ['light', 'dark'];
const { theme, setTheme, themes } = useTheme();
return (
<DropdownMenu>
@ -35,16 +32,16 @@ export function ThemeSwitcher() {
{Object.entries(themes).map(([key, config]) => (
<DropdownMenuItem
key={key}
onClick={() => setTheme(key)}
onClick={() => setTheme(key as Theme)}
className="flex items-start gap-2 cursor-pointer"
>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-medium text-sm">{config}</span>
<span className="font-medium text-sm">{config.label}</span>
{theme === key && <Check size={14} className="text-accent" weight="bold" />}
</div>
<p className="text-xs text-muted-foreground mt-0.5">
Theme: {config}
{config.description}
</p>
</div>
</DropdownMenuItem>

View file

@ -7,13 +7,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List, ArrowsLeftRight } from '@phosphor-icons/react';
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List, ArrowsLeftRight, UserCircle, GitBranch, Package, Cube, MagicWand } from '@phosphor-icons/react';
import { toast } from 'sonner';
import { useState, useEffect, useCallback, memo } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './ui/dialog';
import { ThemeSwitcher } from './ThemeSwitcher';
import { PlatformSelector } from './PlatformSelector';
type PlatformId = string;
import { PlatformId } from '../lib/platforms';
import { Button } from './ui/button';
interface ToolbarProps {
@ -24,9 +24,14 @@ interface ToolbarProps {
currentPlatform: PlatformId;
onPlatformChange: (platform: PlatformId) => void;
onTranslateClick?: () => void;
onAvatarToolkitClick?: () => void;
onVisualScriptingClick?: () => void;
onAssetLibraryClick?: () => void;
onLivePreviewClick?: () => void;
onAIGenerationClick?: () => void;
}
export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectClick, currentPlatform, onPlatformChange, onTranslateClick }: ToolbarProps) {
export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectClick, currentPlatform, onPlatformChange, onTranslateClick, onAvatarToolkitClick, onVisualScriptingClick, onAssetLibraryClick, onLivePreviewClick, onAIGenerationClick }: ToolbarProps) {
const [showInfo, setShowInfo] = useState(false);
const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(null);
@ -98,6 +103,101 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
</Tooltip>
)}
{/* Avatar Toolkit Button */}
{onAvatarToolkitClick && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={onAvatarToolkitClick}
className="h-8 px-3 text-xs gap-1"
aria-label="Avatar Toolkit"
>
<UserCircle size={14} />
<span>Avatars</span>
</Button>
</TooltipTrigger>
<TooltipContent>Cross-Platform Avatar Toolkit</TooltipContent>
</Tooltip>
)}
{/* Visual Scripting Button */}
{onVisualScriptingClick && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={onVisualScriptingClick}
className="h-8 px-3 text-xs gap-1"
aria-label="Visual Scripting"
>
<GitBranch size={14} />
<span>Visual</span>
</Button>
</TooltipTrigger>
<TooltipContent>Visual Scripting (Node Editor)</TooltipContent>
</Tooltip>
)}
{/* Asset Library Button */}
{onAssetLibraryClick && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={onAssetLibraryClick}
className="h-8 px-3 text-xs gap-1"
aria-label="Asset Library"
>
<Package size={14} />
<span>Assets</span>
</Button>
</TooltipTrigger>
<TooltipContent>Asset Library (Models, Textures, Audio)</TooltipContent>
</Tooltip>
)}
{/* Live Preview Button */}
{onLivePreviewClick && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={onLivePreviewClick}
className="h-8 px-3 text-xs gap-1 bg-primary/10 border-primary/30 hover:bg-primary/20"
aria-label="Live Preview"
>
<Cube size={14} />
<span>3D Preview</span>
</Button>
</TooltipTrigger>
<TooltipContent>Live 3D Preview with Lua Execution</TooltipContent>
</Tooltip>
)}
{/* AI Generation Button */}
{onAIGenerationClick && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={onAIGenerationClick}
className="h-8 px-3 text-xs gap-1 bg-accent/10 border-accent/30 hover:bg-accent/20"
aria-label="AI Code Generator"
>
<MagicWand size={14} />
<span>AI Generate</span>
</Button>
</TooltipTrigger>
<TooltipContent>AI-Powered Code & System Generation</TooltipContent>
</Tooltip>
)}
<div className="h-6 w-px bg-border mx-1" />
<Tooltip>
@ -212,6 +312,36 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
<FileCode className="mr-2" size={16} />
<span>Templates</span>
</DropdownMenuItem>
{onAvatarToolkitClick && (
<DropdownMenuItem onClick={onAvatarToolkitClick}>
<UserCircle className="mr-2" size={16} />
<span>Avatar Toolkit</span>
</DropdownMenuItem>
)}
{onVisualScriptingClick && (
<DropdownMenuItem onClick={onVisualScriptingClick}>
<GitBranch className="mr-2" size={16} />
<span>Visual Scripting</span>
</DropdownMenuItem>
)}
{onAssetLibraryClick && (
<DropdownMenuItem onClick={onAssetLibraryClick}>
<Package className="mr-2" size={16} />
<span>Asset Library</span>
</DropdownMenuItem>
)}
{onLivePreviewClick && (
<DropdownMenuItem onClick={onLivePreviewClick}>
<Cube className="mr-2" size={16} />
<span>3D Preview</span>
</DropdownMenuItem>
)}
{onAIGenerationClick && (
<DropdownMenuItem onClick={onAIGenerationClick}>
<MagicWand className="mr-2" size={16} />
<span>AI Generate</span>
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={handleCopy}>
<Copy className="mr-2" size={16} />
<span>Copy Code</span>

View file

@ -1,16 +1,7 @@
// Temporary stub for translateCode
const translateCode = async (_: TranslationRequest) => ({ output: 'Translation not available in this build.' });
// Temporary stub for TranslationRequest
type TranslationRequest = any;
// Temporary stub for TranslationResult
type TranslationResult = any;
// Temporary stub for PlatformId and getPlatform
type PlatformId = string;
const getPlatform = (id: PlatformId) => ({ id, icon: '❓', displayName: id, status: 'unknown', color: '#888', language: 'Unknown' });
import { useState, useCallback, memo } from 'react';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Badge } from './ui/badge';
import { Badge } from '@/components/ui/badge';
import {
ArrowsLeftRight,
Copy,
@ -20,8 +11,12 @@ import {
} from '@phosphor-icons/react';
import { PlatformSelector } from './PlatformSelector';
import { LoadingSpinner } from './ui/loading-spinner';
// import { translateCode, TranslationRequest, TranslationResult } from '@/lib/translation-engine';
// import { PlatformId, getPlatform } from '@/lib/platforms';
import {
translateCode,
TranslationRequest,
TranslationResult,
} from '@/lib/translation-engine';
import { PlatformId, getPlatform } from '@/lib/platforms';
import { toast } from 'sonner';
interface TranslationPanelProps {
@ -227,7 +222,7 @@ export const TranslationPanel = memo(function TranslationPanel({
Warnings
</h4>
<ul className="text-xs text-muted-foreground space-y-1">
{result.warnings.map((warning: string, i: number) => (
{result.warnings.map((warning, i) => (
<li key={i}> {warning}</li>
))}
</ul>
@ -247,7 +242,7 @@ export const TranslationPanel = memo(function TranslationPanel({
<div className="text-center text-muted-foreground">
<ArrowsLeftRight size={48} className="mx-auto mb-4 opacity-50" />
<p className="text-sm">
Click &quot;Translate&quot; to convert your code
Click "Translate" to convert your code
</p>
<p className="text-xs mt-2">
{sourcePlatform.displayName} {targetPlatformInfo.displayName}
@ -271,4 +266,3 @@ export const TranslationPanel = memo(function TranslationPanel({
</div>
);
});
export default TranslationPanel;

View file

@ -8,7 +8,7 @@ import {
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Sparkle, Code, FileCode } from '@phosphor-icons/react';
import { usePersistentState } from '../lib/usePersistentState';
import { usePersistentState } from '@/lib/usePersistentState';
export function WelcomeDialog() {
const [hasSeenWelcome, setHasSeenWelcome] = usePersistentState('aethex-welcome-seen', 'false');

View file

@ -1,2 +0,0 @@
// TypeScript declaration for AethexStudio module
export * from "./aethex-studio";

View file

@ -1,356 +0,0 @@
"use client";
import { useState, useMemo } from "react";
import { toast } from "sonner";
import { Navbar } from "./navbar";
import FileNavigator from "./file-navigator";
import { MainView } from "./main-view";
import { BottomPanel } from "./bottom-panel";
import AiAssistant from "./ai-assistant";
import { StatusBar } from "./status-bar";
import {
initialFileTree,
File,
FolderNode,
FileNode,
} from "../../lib/aethex-data";
import { NewProjectModal } from "./new-project-modal";
import { ScriptTemplate, getTemplatesForPlatform } from "../../lib/templates";
import { cn } from "@/lib/utils";
import { CommandPalette, createDefaultCommands } from "../CommandPalette";
import { SettingsPanel } from "./settings-panel";
import { GitPanel } from "./git-panel";
import { ExportModal } from "./export-modal";
export function AethexStudio() {
const [openFiles, setOpenFiles] = useState<File[]>([]);
const [activeTab, setActiveTab] = useState<string>(openFiles[0]?.id || "");
const [fileTree, setFileTree] = useState<FolderNode>(initialFileTree);
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false);
const [isTemplatesOpen, setIsTemplatesOpen] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(true);
const [aiOpen, setAiOpen] = useState(true);
const [bottomPanelOpen, setBottomPanelOpen] = useState(true);
const [searchOpen, setSearchOpen] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false);
const [gitOpen, setGitOpen] = useState(false);
const [exportOpen, setExportOpen] = useState(false);
const [currentPlatform, setCurrentPlatform] = useState<"roblox" | "uefn" | "spatial" | "web">("roblox");
const handleQuickNewFile = () => {
const timestamp = Date.now();
const newFile: File = {
id: `untitled-${timestamp}`,
name: `untitled-${openFiles.length + 1}.lua`,
language: "lua",
content: `-- New Lua Script\n-- Created with AeThex Studio\n\nlocal module = {}\n\nfunction module.init()\n print("Hello, World!")\nend\n\nreturn module\n`,
};
setOpenFiles((prev) => [...prev, newFile]);
setActiveTab(newFile.id);
};
const handleTemplateSelect = (template: ScriptTemplate) => {
const timestamp = Date.now();
const ext = template.platform === 'web' ? 'ts' : 'lua';
const newFile: File = {
id: `${template.name.toLowerCase().replace(/\s+/g, '-')}-${timestamp}`,
name: `${template.name.replace(/\s+/g, '')}.${ext}`,
language: template.platform === 'web' ? 'typescript' : 'lua',
content: template.code,
};
setOpenFiles((prev) => [...prev, newFile]);
setActiveTab(newFile.id);
setIsTemplatesOpen(false);
};
const handleRun = () => {
// Show a toast/log in the console panel
console.log("[AeThex] Running project...");
toast.success("Project started!", {
description: "Check the Console panel for output",
});
};
const handleSave = () => {
const activeFile = openFiles.find(f => f.id === activeTab);
if (activeFile) {
// In a real app, this would save to backend/filesystem
console.log("[AeThex] Saving file:", activeFile.name);
toast.success(`Saved "${activeFile.name}"`);
} else {
toast.error("No file open to save");
}
};
const handleSettings = () => {
setSettingsOpen(true);
};
const handleGit = () => {
setGitOpen(true);
};
const handleExport = () => {
setExportOpen(true);
};
const handleOpenFile = (file: File) => {
if (!openFiles.find((f) => f.id === file.id)) {
setOpenFiles((prev) => [...prev, file]);
}
setActiveTab(file.id);
};
const handleCloseFile = (fileId: string) => {
const newOpenFiles = openFiles.filter((file) => file.id !== fileId);
setOpenFiles(newOpenFiles);
if (activeTab === fileId) {
if (newOpenFiles.length > 0) {
setActiveTab(newOpenFiles[newOpenFiles.length - 1].id);
} else {
setActiveTab("");
}
}
};
const handleCreateProject = (name: string) => {
const newFile: File = {
id: name + Date.now(),
name,
language: "lua",
content: "",
};
setOpenFiles([...openFiles, newFile]);
};
const handleCreateFile = (name: string) => {
const ext = name.split('.').pop()?.toLowerCase();
let language = 'text';
if (ext === 'lua' || ext === 'luau') language = 'lua';
else if (ext === 'ts' || ext === 'tsx') language = 'typescript';
else if (ext === 'js' || ext === 'jsx') language = 'javascript';
else if (ext === 'json') language = 'json';
const newFileNode: FileNode = {
type: 'file',
name,
language,
content: '',
};
// Add to file tree root (src folder)
setFileTree(prev => {
const srcFolder = prev.children.find(c => c.type === 'folder' && c.name === 'src') as FolderNode | undefined;
if (srcFolder) {
return {
...prev,
children: prev.children.map(child =>
child === srcFolder
? { ...srcFolder, children: [...srcFolder.children, newFileNode] }
: child
)
};
}
return {
...prev,
children: [...prev.children, newFileNode]
};
});
};
const handleCreateFolder = (name: string) => {
const newFolderNode: FolderNode = {
type: 'folder',
name,
children: [],
};
// Add to file tree root (src folder)
setFileTree(prev => {
const srcFolder = prev.children.find(c => c.type === 'folder' && c.name === 'src') as FolderNode | undefined;
if (srcFolder) {
return {
...prev,
children: prev.children.map(child =>
child === srcFolder
? { ...srcFolder, children: [...srcFolder.children, newFolderNode] }
: child
)
};
}
return {
...prev,
children: [...prev.children, newFolderNode]
};
});
};
// Command palette commands
const commands = useMemo(() => createDefaultCommands({
onNewProject: () => setIsNewProjectModalOpen(true),
onTemplates: () => setIsTemplatesOpen(true),
onPreview: handleRun,
onExport: () => {
const activeFile = openFiles.find(f => f.id === activeTab);
if (activeFile) {
const blob = new Blob([activeFile.content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = activeFile.name;
a.click();
URL.revokeObjectURL(url);
toast.success(`Exported "${activeFile.name}"`);
} else {
toast.error("No file open to export");
}
},
onCopy: () => {
const activeFile = openFiles.find(f => f.id === activeTab);
if (activeFile) {
navigator.clipboard.writeText(activeFile.content);
toast.success("Code copied to clipboard");
} else {
toast.error("No file open to copy");
}
},
}), [openFiles, activeTab]);
return (
<div className="flex h-full flex-col bg-[#0d0d10] text-foreground">
{/* Top navbar */}
<Navbar
onToggleSidebar={() => setSidebarOpen(!sidebarOpen)}
onToggleAI={() => setAiOpen(!aiOpen)}
onToggleBottomPanel={() => setBottomPanelOpen(!bottomPanelOpen)}
onNewFile={handleQuickNewFile}
onSearch={() => setSearchOpen(true)}
onRun={handleRun}
onSave={handleSave}
onSettings={handleSettings}
onGit={handleGit}
onExport={handleExport}
sidebarOpen={sidebarOpen}
aiOpen={aiOpen}
bottomPanelOpen={bottomPanelOpen}
/>
{/* Main content area */}
<div className="flex-1 flex overflow-hidden">
{/* Left sidebar - File Navigator */}
{sidebarOpen && (
<div className="w-[220px] flex-shrink-0 bg-[#12121a] border-r border-white/5 overflow-hidden">
<FileNavigator
fileTree={fileTree}
onOpenFile={handleOpenFile}
onCreateFile={handleCreateFile}
onCreateFolder={handleCreateFolder}
/>
</div>
)}
{/* Center - Editor + Bottom Panel */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Editor area */}
<div className={cn("flex-1 overflow-hidden", bottomPanelOpen ? "h-[70%]" : "h-full")}>
<MainView
openFiles={openFiles}
activeFileId={activeTab}
onFileSelect={setActiveTab}
onFileClose={handleCloseFile}
onNewFile={handleQuickNewFile}
onOpenTemplates={() => setIsTemplatesOpen(true)}
/>
</div>
{/* Bottom panel */}
{bottomPanelOpen && (
<div className="h-[200px] flex-shrink-0 bg-[#12121a] border-t border-white/5">
<BottomPanel />
</div>
)}
</div>
{/* Right sidebar - AI Assistant */}
{aiOpen && (
<div className="w-[320px] flex-shrink-0 bg-[#12121a] border-l border-white/5 overflow-hidden">
<AiAssistant />
</div>
)}
</div>
{/* Status bar */}
<StatusBar platform="roblox" language="Lua" />
{/* Command Palette */}
<CommandPalette
open={searchOpen}
onClose={() => setSearchOpen(false)}
commands={commands}
/>
{/* Modals */}
{isNewProjectModalOpen && (
<NewProjectModal
onCreate={handleCreateProject}
onClose={() => setIsNewProjectModalOpen(false)}
/>
)}
{/* Templates Modal */}
{isTemplatesOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="bg-[#12121a] border border-white/10 rounded-xl w-full max-w-2xl max-h-[80vh] overflow-hidden shadow-2xl">
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10">
<h2 className="text-lg font-semibold">Choose a Template</h2>
<button
onClick={() => setIsTemplatesOpen(false)}
className="text-muted-foreground hover:text-foreground transition-colors"
>
</button>
</div>
<div className="p-4 overflow-y-auto max-h-[60vh]">
<div className="grid grid-cols-2 gap-3">
{getTemplatesForPlatform('roblox').map((template) => (
<button
key={template.name}
onClick={() => handleTemplateSelect(template)}
className="text-left p-3 rounded-lg border border-white/5 bg-white/[0.02] hover:bg-white/5 hover:border-white/10 transition-all group"
>
<div className="flex items-center gap-2 mb-1">
<div className="h-6 w-6 rounded bg-red-500/20 flex items-center justify-center">
<span className="text-xs">🎮</span>
</div>
<span className="font-medium text-sm group-hover:text-foreground">{template.name}</span>
</div>
<p className="text-xs text-muted-foreground line-clamp-2">{template.description}</p>
<div className="mt-2 flex items-center gap-2">
<span className="text-[10px] px-1.5 py-0.5 rounded bg-red-500/20 text-red-400">Roblox</span>
<span className="text-[10px] text-muted-foreground">{template.category}</span>
</div>
</button>
))}
</div>
</div>
</div>
</div>
)}
{/* Settings Panel */}
<SettingsPanel open={settingsOpen} onClose={() => setSettingsOpen(false)} />
{/* Git Panel */}
<GitPanel open={gitOpen} onClose={() => setGitOpen(false)} />
{/* Export Modal */}
<ExportModal
open={exportOpen}
onClose={() => setExportOpen(false)}
platform={currentPlatform}
/>
</div>
);
}

View file

@ -1,241 +0,0 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Send,
Bot,
Loader2,
Copy,
Code,
Sparkles,
MessageSquarePlus,
FlaskConical,
BookText,
User,
Zap,
RefreshCw,
} from "lucide-react";
import { cn } from "@/lib/utils";
interface Message {
id: string;
role: "user" | "assistant";
content: string;
timestamp: Date;
}
const suggestedPrompts = [
{ icon: Code, text: "Generate a Roblox spawn system" },
{ icon: FlaskConical, text: "Debug my current code" },
{ icon: BookText, text: "Explain cross-platform patterns" },
{ icon: Sparkles, text: "Optimize my game loop" },
];
const AiAssistant = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [model, setModel] = useState("claude-3.5");
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages]);
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: Date.now().toString(),
role: "user",
content: input,
timestamp: new Date(),
};
setMessages((prev) => [...prev, userMessage]);
setInput("");
setIsLoading(true);
// Simulate AI response
setTimeout(() => {
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
role: "assistant",
content: `I'll help you with that! Here's a solution for "${input.slice(0, 50)}..."
\`\`\`lua
-- Example code snippet
local function example()
print("Hello from AeThex AI!")
end
\`\`\`
Would you like me to explain this further or make any modifications?`,
timestamp: new Date(),
};
setMessages((prev) => [...prev, aiMessage]);
setIsLoading(false);
}, 1500);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
return (
<div className="flex h-full flex-col">
{/* Header */}
<div className="flex items-center justify-between border-b border-white/5 px-3 py-2">
<div className="flex items-center gap-2">
<div className="h-6 w-6 rounded-lg bg-gradient-to-br from-purple-500 via-blue-500 to-cyan-400 flex items-center justify-center shadow-lg shadow-purple-500/20">
<Zap className="h-3 w-3 text-white" />
</div>
<span className="text-sm font-semibold bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">AI Assistant</span>
</div>
<div className="flex items-center gap-1">
<Select value={model} onValueChange={setModel}>
<SelectTrigger className="h-6 w-24 text-[10px] bg-white/5 border-white/10">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="claude-3.5">Claude 3.5</SelectItem>
<SelectItem value="gpt-4">GPT-4</SelectItem>
<SelectItem value="gemini">Gemini Pro</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-foreground hover:bg-white/5"
onClick={() => setMessages([])}
title="New conversation"
>
<MessageSquarePlus className="h-3.5 w-3.5" />
</Button>
</div>
</div>
{/* Messages */}
<ScrollArea className="flex-1 p-3" ref={scrollRef}>
{messages.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center py-8">
<div className="h-14 w-14 rounded-2xl bg-gradient-to-br from-purple-500/20 via-blue-500/20 to-cyan-500/20 flex items-center justify-center mb-4 border border-white/5">
<Bot className="h-7 w-7 text-purple-400" />
</div>
<h3 className="font-semibold text-foreground mb-1">How can I help?</h3>
<p className="text-xs text-muted-foreground mb-6 max-w-48">
Ask anything about your code, get suggestions, or generate new scripts.
</p>
<div className="grid grid-cols-1 gap-2 w-full">
{suggestedPrompts.map((prompt, i) => (
<button
key={i}
onClick={() => setInput(prompt.text)}
className="flex items-center gap-2.5 rounded-lg border border-white/5 bg-white/[0.02] p-2.5 text-xs text-muted-foreground hover:bg-white/5 hover:text-foreground hover:border-white/10 transition-all text-left group"
>
<div className="h-6 w-6 rounded-md bg-white/5 flex items-center justify-center group-hover:bg-white/10 transition-colors">
<prompt.icon className="h-3 w-3" />
</div>
<span className="truncate">{prompt.text}</span>
</button>
))}
</div>
</div>
) : (
<div className="space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={cn(
"flex gap-3",
message.role === "user" && "flex-row-reverse"
)}
>
<Avatar className="h-7 w-7 shrink-0">
<AvatarFallback
className={cn(
"text-xs",
message.role === "assistant"
? "bg-gradient-to-br from-blue-500 to-purple-600 text-white"
: "bg-accent"
)}
>
{message.role === "assistant" ? (
<Bot className="h-3.5 w-3.5" />
) : (
<User className="h-3.5 w-3.5" />
)}
</AvatarFallback>
</Avatar>
<div
className={cn(
"rounded-lg px-3 py-2 text-sm max-w-[85%]",
message.role === "assistant"
? "bg-accent/50"
: "bg-blue-600 text-white"
)}
>
<p className="whitespace-pre-wrap">{message.content}</p>
</div>
</div>
))}
{isLoading && (
<div className="flex gap-3">
<Avatar className="h-7 w-7 shrink-0">
<AvatarFallback className="bg-gradient-to-br from-blue-500 to-purple-600 text-white text-xs">
<Bot className="h-3.5 w-3.5" />
</AvatarFallback>
</Avatar>
<div className="rounded-lg bg-accent/50 px-3 py-2 text-sm">
<Loader2 className="h-4 w-4 animate-spin" />
</div>
</div>
)}
</div>
)}
</ScrollArea>
{/* Input */}
<div className="border-t border-white/5 p-3">
<div className="relative">
<Textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask anything about your code..."
className="min-h-[70px] resize-none pr-10 text-sm bg-white/5 border-white/5 placeholder:text-muted-foreground/50 focus:border-purple-500/50 focus:bg-white/10"
/>
<Button
size="icon"
className="absolute bottom-2 right-2 h-7 w-7 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 border-0"
onClick={handleSend}
disabled={!input.trim() || isLoading}
>
<Send className="h-3 w-3" />
</Button>
</div>
<p className="mt-2 text-[10px] text-muted-foreground/50 text-center">
AI responses may not always be accurate. Verify important code.
</p>
</div>
</div>
);
};
export default AiAssistant;

View file

@ -1,37 +0,0 @@
import React from 'react';
interface AIChatProps {
messages: Array<{ id: string; sender: string; text: string }>;
onSend: (text: string) => void;
}
export const AIChat: React.FC<AIChatProps> = ({ messages, onSend }) => {
const [input, setInput] = React.useState('');
const handleSend = () => {
if (input.trim()) {
onSend(input);
setInput('');
}
};
return (
<div className="ai-chat">
<div className="messages">
{messages.map((msg) => (
<div key={msg.id} className={`message ${msg.sender}`}>
<span>{msg.text}</span>
</div>
))}
</div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleSend(); }}
placeholder="Type a message..."
/>
<button onClick={handleSend}>Send</button>
</div>
);
};

View file

@ -1,21 +0,0 @@
import React from 'react';
interface AssetLibraryPanelProps {
assets: Array<{ id: string; name: string; type: string; url: string }>;
onAssetSelect: (id: string) => void;
}
export const AssetLibraryPanel: React.FC<AssetLibraryPanelProps> = ({ assets, onAssetSelect }) => {
return (
<div className="asset-library-panel">
<h2>Asset Library</h2>
<ul>
{assets.map((asset) => (
<li key={asset.id} onClick={() => onAssetSelect(asset.id)}>
<span>{asset.name}</span> <span className="type">[{asset.type}]</span>
</li>
))}
</ul>
</div>
);
};

View file

@ -1,204 +0,0 @@
"use client";
import { useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { consoleLogs as initialLogs } from "@/lib/aethex-data";
import {
ChevronRight,
Terminal as TerminalIcon,
AlertCircle,
AlertTriangle,
Info,
Trash2,
Filter,
Download,
Maximize2,
X
} from "lucide-react";
import { cn } from "@/lib/utils";
export function BottomPanel() {
const [activeTab, setActiveTab] = useState("console");
const [logs, setLogs] = useState(initialLogs);
const [filterType, setFilterType] = useState<string | null>(null);
const [isMaximized, setIsMaximized] = useState(false);
const handleClearLogs = () => {
setLogs([]);
};
const handleFilter = () => {
// Cycle through filter types: null -> info -> warn -> error -> null
if (filterType === null) setFilterType("info");
else if (filterType === "info") setFilterType("warn");
else if (filterType === "warn") setFilterType("error");
else setFilterType(null);
};
const filteredLogs = filterType ? logs.filter(log => log.type === filterType) : logs;
return (
<div className="flex h-full flex-col">
{/* Tab bar with actions */}
<div className="flex items-center justify-between border-b border-white/5 px-2">
<div className="flex items-center">
<button
onClick={() => setActiveTab("console")}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors border-b-2 -mb-px",
activeTab === "console"
? "text-foreground border-purple-500"
: "text-muted-foreground border-transparent hover:text-foreground"
)}
>
<Info className="h-3 w-3" />
Console
<span className="ml-1.5 bg-white/10 px-1.5 py-0.5 rounded text-[10px]">3</span>
</button>
<button
onClick={() => setActiveTab("terminal")}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors border-b-2 -mb-px",
activeTab === "terminal"
? "text-foreground border-purple-500"
: "text-muted-foreground border-transparent hover:text-foreground"
)}
>
<TerminalIcon className="h-3 w-3" />
Terminal
</button>
<button
onClick={() => setActiveTab("problems")}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors border-b-2 -mb-px",
activeTab === "problems"
? "text-foreground border-purple-500"
: "text-muted-foreground border-transparent hover:text-foreground"
)}
>
<AlertCircle className="h-3 w-3" />
Problems
<span className="ml-1.5 bg-yellow-500/20 text-yellow-400 px-1.5 py-0.5 rounded text-[10px]">2</span>
</button>
</div>
<div className="flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
className={cn(
"h-6 w-6 hover:bg-white/5",
filterType ? "text-purple-400" : "text-muted-foreground hover:text-foreground"
)}
onClick={handleFilter}
title={filterType ? `Filtering: ${filterType}` : "Filter logs"}
>
<Filter className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-foreground hover:bg-white/5"
onClick={handleClearLogs}
title="Clear console"
>
<Trash2 className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-foreground hover:bg-white/5"
onClick={() => setIsMaximized(!isMaximized)}
title={isMaximized ? "Restore" : "Maximize"}
>
<Maximize2 className="h-3 w-3" />
</Button>
</div>
</div>
{/* Console content */}
{activeTab === "console" && (
<ScrollArea className="flex-1">
<div className="p-2 font-mono text-xs space-y-0.5">
{filteredLogs.length === 0 ? (
<div className="flex items-center justify-center h-20 text-muted-foreground">
{logs.length === 0 ? "No console output" : `No ${filterType} logs`}
</div>
) : (
filteredLogs.map((log, index) => (
<div
key={index}
className={cn(
"flex items-start gap-2 px-2 py-1 rounded hover:bg-white/5 transition-colors",
log.type === "error" && "bg-red-500/5",
log.type === "warn" && "bg-yellow-500/5"
)}
>
<span className="text-muted-foreground/50 w-16 shrink-0 tabular-nums">
{log.timestamp}
</span>
<span
className={cn(
"w-14 shrink-0 font-medium",
log.platform === "Roblox" && "text-red-400",
log.platform === "Web" && "text-blue-400",
log.platform === "Mobile" && "text-emerald-400"
)}
>
[{log.platform}]
</span>
{log.type === "error" && <AlertCircle className="h-3 w-3 text-red-400 shrink-0 mt-0.5" />}
{log.type === "warn" && <AlertTriangle className="h-3 w-3 text-yellow-400 shrink-0 mt-0.5" />}
<p className={cn(
"flex-1",
log.type === "error" && "text-red-400",
log.type === "warn" && "text-yellow-400",
log.type === "log" && "text-foreground/80"
)}>
{log.message}
</p>
</div>
)))}
</div>
</ScrollArea>
)}
{/* Terminal content */}
{activeTab === "terminal" && (
<div className="flex-1 bg-[#0a0a0c] p-3 font-mono text-xs">
<div className="text-emerald-400 mb-2">
<span className="text-purple-400"></span> ~/aethex-project <span className="text-blue-400">git:(main)</span>
</div>
<div className="flex items-center gap-1 text-muted-foreground">
<span className="text-muted-foreground/50">$</span>
<span className="animate-pulse"></span>
</div>
</div>
)}
{/* Problems content */}
{activeTab === "problems" && (
<ScrollArea className="flex-1">
<div className="p-2 space-y-1">
<div className="flex items-start gap-2 px-2 py-1.5 rounded bg-yellow-500/5 hover:bg-yellow-500/10 transition-colors">
<AlertTriangle className="h-3.5 w-3.5 text-yellow-400 shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<p className="text-xs text-yellow-400">Unused variable 'tempData'</p>
<p className="text-[10px] text-muted-foreground">src/scripts/main.lua:24</p>
</div>
</div>
<div className="flex items-start gap-2 px-2 py-1.5 rounded bg-yellow-500/5 hover:bg-yellow-500/10 transition-colors">
<AlertTriangle className="h-3.5 w-3.5 text-yellow-400 shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<p className="text-xs text-yellow-400">Consider using 'local' for function declaration</p>
<p className="text-[10px] text-muted-foreground">src/scripts/player.lua:8</p>
</div>
</div>
</div>
</ScrollArea>
)}
</div>
);
}

View file

@ -1,21 +0,0 @@
import React from 'react';
interface CertificationPanelProps {
certifications: Array<{ id: string; name: string; status: string }>;
onCertClick: (id: string) => void;
}
export const CertificationPanel: React.FC<CertificationPanelProps> = ({ certifications, onCertClick }) => {
return (
<div className="certification-panel">
<h2>Certifications</h2>
<ul>
{certifications.map((cert) => (
<li key={cert.id} onClick={() => onCertClick(cert.id)}>
<span>{cert.name}</span> <span className={`status ${cert.status}`}>{cert.status}</span>
</li>
))}
</ul>
</div>
);
};

View file

@ -1,20 +0,0 @@
import React from 'react';
interface CheckboxProps {
checked: boolean;
onChange: (checked: boolean) => void;
label?: string;
}
export const Checkbox: React.FC<CheckboxProps> = ({ checked, onChange, label }) => {
return (
<label className="checkbox">
<input
type="checkbox"
checked={checked}
onChange={e => onChange(e.target.checked)}
/>
{label && <span>{label}</span>}
</label>
);
};

View file

@ -1,82 +0,0 @@
"use client";
import type { Dispatch, SetStateAction } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { X } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import type { File as OpenFileType } from "@/lib/aethex-data";
type CodeEditorProps = {
openFiles: OpenFileType[];
activeTab: string;
setActiveTab: Dispatch<SetStateAction<string>>;
onCloseFile: (fileId: string) => void;
};
export function CodeEditor({
openFiles,
activeTab,
setActiveTab,
onCloseFile,
}: CodeEditorProps) {
const handleCloseTab = (
e: React.MouseEvent<HTMLButtonElement>,
fileId: string
) => {
e.stopPropagation();
onCloseFile(fileId);
};
if (openFiles.length === 0) {
return (
<div className="flex h-full items-center justify-center bg-card text-muted-foreground">
<p>No files open. Select a file from the navigator.</p>
</div>
);
}
return (
<Tabs
value={activeTab}
onValueChange={setActiveTab}
className="flex h-full flex-col"
>
<TabsList className="m-0 flex h-auto justify-start rounded-none border-b bg-transparent p-0">
{openFiles.map((file) => (
<TabsTrigger
key={file.id}
value={file.id}
className="group relative h-10 rounded-none border-r border-t-2 border-t-transparent bg-card px-4 py-2 text-muted-foreground shadow-none data-[state=active]:border-t-primary data-[state=active]:bg-background data-[state=active]:text-foreground"
>
{file.name}
<Button
variant="ghost"
size="icon"
className="absolute right-1 top-1/2 h-5 w-5 -translate-y-1/2 opacity-0 group-hover:opacity-100"
onClick={(e) => handleCloseTab(e, file.id)}
>
<X className="h-3 w-3" />
</Button>
</TabsTrigger>
))}
</TabsList>
{openFiles.map((file) => (
<TabsContent
key={file.id}
value={file.id}
className="m-0 flex-1 overflow-hidden"
>
<ScrollArea className="h-full">
<pre className="p-4 font-code text-sm">
<code
dangerouslySetInnerHTML={{ __html: file.content }}
></code>
</pre>
</ScrollArea>
</TabsContent>
))}
</Tabs>
);
}

View file

@ -1,20 +0,0 @@
import React from 'react';
interface ConsolePanelProps {
logs: string[];
onClear: () => void;
}
export const ConsolePanel: React.FC<ConsolePanelProps> = ({ logs, onClear }) => {
return (
<div className="console-panel">
<h2>Console</h2>
<button onClick={onClear}>Clear</button>
<pre className="logs">
{logs.map((log, idx) => (
<div key={idx}>{log}</div>
))}
</pre>
</div>
);
};

View file

@ -1,16 +0,0 @@
import React from 'react';
interface CrossPlatformPreviewProps {
url: string;
onClose: () => void;
}
export const CrossPlatformPreview: React.FC<CrossPlatformPreviewProps> = ({ url, onClose }) => {
return (
<div className="cross-platform-preview">
<h2>Cross-Platform Preview</h2>
<iframe src={url} title="Cross-Platform Preview" width="100%" height="500px" frameBorder="0" />
<button onClick={onClose}>Close</button>
</div>
);
};

View file

@ -1,369 +0,0 @@
"use client";
import Image from "next/image";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
platformCode,
crossPlatformState,
} from "@/lib/aethex-data";
import { PlaceHolderImages } from "@/lib/placeholder-images";
import { MobileIcon, RobloxIcon, WebIcon } from "./icons";
import {
AlertCircle,
CheckCircle2,
Bot,
Loader2,
ServerCrash,
ChevronsUpDown,
} from "lucide-react";
import { Button } from "../ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { ScrollArea } from "../ui/scroll-area";
import { useState } from "react";
import {
aiSuggestedSyncConflictResolution,
AISuggestedSyncConflictResolutionOutput,
} from "@/ai/flows/ai-suggested-sync-conflict-resolution";
export function CrossPlatformView() {
const robloxViewport = PlaceHolderImages.find((p) => p.id === "roblox-vp");
const webViewport = PlaceHolderImages.find((p) => p.id === "web-vp");
const mobileViewport = PlaceHolderImages.find((p) => p.id === "mobile-vp");
return (
<ResizablePanelGroup orientation="vertical" className="h-full w-full">
<ResizablePanel defaultSize={50} minSize={30}>
<div className="grid h-full grid-cols-3 gap-2 p-2">
{robloxViewport && (
<Viewport
platform="Roblox"
icon={<RobloxIcon />}
imageUrl={robloxViewport.imageUrl}
imageHint={robloxViewport.imageHint}
/>
)}
{webViewport && (
<Viewport
platform="Web"
icon={<WebIcon />}
imageUrl={webViewport.imageUrl}
imageHint={webViewport.imageHint}
/>
)}
{mobileViewport && (
<Viewport
platform="Mobile"
icon={<MobileIcon />}
imageUrl={mobileViewport.imageUrl}
imageHint={mobileViewport.imageHint}
/>
)}
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50} minSize={30}>
<div className="grid h-full grid-cols-3 gap-2 p-2">
<div className="col-span-2">
<PlatformCodeEditor />
</div>
<div className="flex flex-col">
<StateInspector />
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
);
}
function Viewport({
platform,
icon,
imageUrl,
imageHint,
}: {
platform: string;
icon: React.ReactNode;
imageUrl: string;
imageHint: string;
}) {
return (
<Card className="flex flex-col">
<CardHeader className="flex flex-row items-center justify-between p-3">
<div className="flex items-center gap-2">
{icon}
<CardTitle className="text-base font-headline">{platform}</CardTitle>
</div>
<div className="flex items-center gap-1.5 text-green-400">
<CheckCircle2 className="h-3 w-3" />
<span className="text-xs">Synced</span>
</div>
</CardHeader>
<CardContent className="flex-1 p-0">
<div className="relative h-full w-full">
<Image
src={imageUrl}
alt={`${platform} viewport`}
fill
className="object-cover"
data-ai-hint={imageHint}
/>
</div>
</CardContent>
</Card>
);
}
function PlatformCodeEditor() {
return (
<Card className="h-full">
<Tabs defaultValue="lua" className="flex h-full flex-col">
<TabsList className="m-0 flex h-auto justify-start rounded-none border-b bg-card p-0">
{Object.entries(platformCode).map(([lang, { name }]) => (
<TabsTrigger
key={lang}
value={lang}
className="relative h-10 rounded-none border-r border-t-2 border-t-transparent bg-card px-4 py-2 text-muted-foreground shadow-none data-[state=active]:border-t-accent data-[state=active]:bg-background data-[state=active]:text-foreground"
>
{name}
</TabsTrigger>
))}
</TabsList>
{Object.entries(platformCode).map(([lang, { code }]) => (
<TabsContent
key={lang}
value={lang}
className="m-0 flex-1 overflow-hidden"
>
<ScrollArea className="h-full">
<pre className="p-4 font-code text-sm">
<code>{code}</code>
</pre>
</ScrollArea>
</TabsContent>
))}
</Tabs>
</Card>
);
}
function StateInspector() {
return (
<Card className="flex-1">
<CardHeader className="p-3">
<CardTitle className="text-base font-headline">
State Inspector
</CardTitle>
<CardDescription className="text-xs">
Real-time variable synchronization.
</CardDescription>
</CardHeader>
<CardContent className="p-0">
<ScrollArea className="h-[calc(100%-70px)]">
<Table>
<TableHeader>
<TableRow>
<TableHead className="pl-3">Variable</TableHead>
<TableHead>Value</TableHead>
<TableHead className="pr-3 text-right">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{crossPlatformState.map((item) => (
<StateTableRow key={item.variable} item={item} />
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
);
}
function StateTableRow({
item,
}: {
item: (typeof crossPlatformState)[0];
}) {
const [isOpen, setIsOpen] = useState(false);
const [suggestion, setSuggestion] =
useState<AISuggestedSyncConflictResolutionOutput | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchSuggestion = async () => {
if (isLoading) return;
setIsLoading(true);
setError(null);
setSuggestion(null);
try {
const result = await aiSuggestedSyncConflictResolution({
robloxCode: platformCode.lua.code,
webCode: platformCode.javascript.code,
mobileCode: platformCode.typescript.code,
sharedState: JSON.stringify(
Object.fromEntries(
crossPlatformState.map((i) => [i.variable, i.web])
),
null,
2
),
});
setSuggestion(result);
} catch (e) {
setError("Failed to get AI suggestion. Please try again.");
console.error(e);
} finally {
setIsLoading(false);
}
};
const renderStatus = () => {
switch (item.status) {
case "synced":
return (
<div className="flex items-center justify-end gap-1.5 text-green-400">
<CheckCircle2 className="h-3 w-3" />
<span className="text-xs">Synced</span>
</div>
);
case "syncing":
return (
<div className="flex items-center justify-end gap-1.5 text-yellow-400">
<Loader2 className="h-3 w-3 animate-spin" />
<span className="text-xs">Syncing</span>
</div>
);
case "conflict":
return (
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={fetchSuggestion}
disabled={isLoading}
className="h-auto p-1 text-red-500 hover:bg-red-500/10 hover:text-red-500"
>
<AlertCircle className="h-3 w-3" />
<span className="ml-1.5 text-xs">Conflict</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="max-w-2xl">
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2 font-headline">
<Bot /> AI Conflict Resolution
</AlertDialogTitle>
{isLoading && (
<div className="flex items-center justify-center p-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)}
{error && (
<div className="flex flex-col items-center justify-center p-12 text-center">
<ServerCrash className="h-8 w-8 text-destructive" />
<p className="mt-4 text-destructive">{error}</p>
</div>
)}
{suggestion && (
<AlertDialogDescription>
{suggestion.explanation}
</AlertDialogDescription>
)}
</AlertDialogHeader>
{suggestion?.suggestedSolutions &&
suggestion.suggestedSolutions.length > 0 && (
<div className="my-4 rounded-md border bg-muted/50 p-4">
<h4 className="mb-2 font-semibold">Suggested Solutions:</h4>
<ul className="list-disc space-y-2 pl-5 font-code text-xs">
{suggestion.suggestedSolutions.map((solution, i) => (
<li key={i}>{solution}</li>
))}
</ul>
</div>
)}
{suggestion && (
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Apply Suggestion</AlertDialogAction>
</AlertDialogFooter>
)}
</AlertDialogContent>
</AlertDialog>
);
}
};
return (
<TableRow>
<TableCell className="pl-3 font-code text-xs font-medium">
{item.variable}
</TableCell>
<TableCell>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-auto w-full justify-between p-1.5 font-code text-xs"
>
<span className="truncate">{JSON.stringify(item.web)}</span>
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-3 font-code text-xs">
<div className="grid grid-cols-[auto_1fr] gap-x-2">
<span className="text-red-500">Roblox:</span>
<span className="text-purple-400">
{JSON.stringify(item.roblox)}
</span>
<span className="text-blue-500">Web:</span>
<span className="text-purple-400">
{JSON.stringify(item.web)}
</span>
<span className="text-green-500">Mobile:</span>
<span className="text-purple-400">
{JSON.stringify(item.mobile)}
</span>
</div>
</PopoverContent>
</Popover>
</TableCell>
<TableCell className="pr-3 text-right">{renderStatus()}</TableCell>
</TableRow>
);
}

View file

@ -1,107 +0,0 @@
"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Gamepad2, Plus } from "lucide-react";
import { WorkspaceCard } from "./workspace-card";
import { WorkspaceCardSkeleton } from "./workspace-card-skeleton";
import { workspaces as initialWorkspaces } from "@/lib/workspaces";
import { NewProjectModal } from "./new-project-modal";
import { MobileIcon, RobloxIcon, WebIcon } from "./icons";
type Workspace = typeof initialWorkspaces[0];
export function DashboardPage() {
const [workspaces, setWorkspaces] =
useState<Workspace[]>(initialWorkspaces);
const [loading, setLoading] = useState(true);
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 1500);
return () => clearTimeout(timer);
}, []);
const handleCreateProject = (name: string) => {
// This is a mock implementation. In a real app, this would involve
// an API call to create a new project in the backend.
const newWorkspace: Workspace = {
id: `proj-${Date.now()}`,
name,
lastModified: "Just now",
platforms: ["roblox"],
thumbnailUrlId: "workspace-thumb-4",
thumbnailImageHint: "futuristic city",
};
setWorkspaces((prev) => [newWorkspace, ...prev]);
setIsNewProjectModalOpen(false);
};
const renderContent = () => {
if (loading) {
return (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{Array.from({ length: 3 }).map((_, i) => (
<WorkspaceCardSkeleton key={i} />
))}
</div>
);
}
if (workspaces.length === 0) {
return (
<div className="text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
<Gamepad2 className="h-6 w-6 text-primary" />
</div>
<h3 className="mt-4 text-lg font-semibold text-foreground">
No projects yet
</h3>
<p className="mt-1 text-sm text-muted-foreground">
Get started by creating a new project.
</p>
<div className="mt-6">
<Button onClick={() => setIsNewProjectModalOpen(true)}>
<Plus className="-ml-0.5 mr-1.5 h-5 w-5" />
New Project
</Button>
</div>
</div>
);
}
return (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{workspaces.map((ws) => (
<WorkspaceCard key={ws.id} workspace={ws} />
))}
</div>
);
};
return (
<>
<div className="min-h-screen bg-background">
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<header className="mb-8 flex items-center justify-between">
<h1 className="font-headline text-3xl font-bold text-foreground">
My Workspaces
</h1>
<Button onClick={() => setIsNewProjectModalOpen(true)}>
<Plus className="-ml-1 mr-2" /> New Workspace
</Button>
</header>
<main>{renderContent()}</main>
</div>
</div>
{isNewProjectModalOpen && (
<NewProjectModal
onCreate={handleCreateProject}
onClose={() => setIsNewProjectModalOpen(false)}
/>
)}
</>
);
}

View file

@ -1,22 +0,0 @@
import React from 'react';
interface DesktopAppPanelProps {
apps: Array<{ id: string; name: string; icon: string }>;
onAppLaunch: (id: string) => void;
}
export const DesktopAppPanel: React.FC<DesktopAppPanelProps> = ({ apps, onAppLaunch }) => {
return (
<div className="desktop-app-panel">
<h2>Desktop Apps</h2>
<ul>
{apps.map((app) => (
<li key={app.id} onClick={() => onAppLaunch(app.id)}>
<img src={app.icon} alt={app.name} className="icon" />
<span>{app.name}</span>
</li>
))}
</ul>
</div>
);
};

View file

@ -1,22 +0,0 @@
import React from 'react';
interface EducationPanelProps {
lessons: Array<{ id: string; title: string; completed: boolean }>;
onLessonSelect: (id: string) => void;
}
export const EducationPanel: React.FC<EducationPanelProps> = ({ lessons, onLessonSelect }) => {
return (
<div className="education-panel">
<h2>Education</h2>
<ul>
{lessons.map((lesson) => (
<li key={lesson.id} onClick={() => onLessonSelect(lesson.id)} className={lesson.completed ? 'completed' : ''}>
<span>{lesson.title}</span>
{lesson.completed && <span className="check"></span>}
</li>
))}
</ul>
</div>
);
};

View file

@ -1,20 +0,0 @@
import React from 'react';
interface EnterpriseAnalyticsPanelProps {
data: Array<{ id: string; metric: string; value: number }>;
}
export const EnterpriseAnalyticsPanel: React.FC<EnterpriseAnalyticsPanelProps> = ({ data }) => {
return (
<div className="enterprise-analytics-panel">
<h2>Enterprise Analytics</h2>
<ul>
{data.map((item) => (
<li key={item.id}>
<span>{item.metric}:</span> <span>{item.value}</span>
</li>
))}
</ul>
</div>
);
};

View file

@ -1,211 +0,0 @@
"use client";
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Checkbox } from "@/components/ui/checkbox";
import { Progress } from "@/components/ui/progress";
import {
Download,
Folder,
FileCode,
Package,
Check,
Loader2,
} from "lucide-react";
import { cn } from "@/lib/utils";
interface ExportModalProps {
open: boolean;
onClose: () => void;
platform: "roblox" | "uefn" | "spatial" | "web";
}
const exportFormats = {
roblox: [
{ id: "rbxlx", name: "Roblox Place (.rbxlx)", description: "Full place file with all assets" },
{ id: "rbxmx", name: "Roblox Model (.rbxmx)", description: "Model file for importing" },
{ id: "lua", name: "Lua Scripts (.lua)", description: "Export scripts only" },
],
uefn: [
{ id: "uproject", name: "UEFN Project", description: "Full Unreal project structure" },
{ id: "verse", name: "Verse Scripts", description: "Export Verse code only" },
{ id: "pak", name: "Package (.pak)", description: "Compiled game package" },
],
spatial: [
{ id: "spatial", name: "Spatial Package", description: "Ready for Spatial.io upload" },
{ id: "unity", name: "Unity Export", description: "Unity asset format" },
{ id: "gltf", name: "glTF Models", description: "3D models only" },
],
web: [
{ id: "html", name: "Static HTML", description: "Single HTML file with embedded code" },
{ id: "zip", name: "Project ZIP", description: "Full project archive" },
{ id: "npm", name: "NPM Package", description: "Ready for npm publish" },
],
};
const platformColors = {
roblox: "text-red-400",
uefn: "text-purple-400",
spatial: "text-emerald-400",
web: "text-blue-400",
};
export function ExportModal({ open, onClose, platform }: ExportModalProps) {
const [selectedFormat, setSelectedFormat] = useState(exportFormats[platform][0].id);
const [exportName, setExportName] = useState("my-project");
const [includeAssets, setIncludeAssets] = useState(true);
const [minify, setMinify] = useState(false);
const [isExporting, setIsExporting] = useState(false);
const [exportProgress, setExportProgress] = useState(0);
const [exportComplete, setExportComplete] = useState(false);
const handleExport = async () => {
setIsExporting(true);
setExportProgress(0);
setExportComplete(false);
// Simulate export progress
for (let i = 0; i <= 100; i += 10) {
await new Promise(resolve => setTimeout(resolve, 200));
setExportProgress(i);
}
setIsExporting(false);
setExportComplete(true);
// Trigger download
const content = `-- Exported from AeThex Studio\n-- Platform: ${platform}\n-- Format: ${selectedFormat}\n\nprint("Hello from AeThex!")`;
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${exportName}.${selectedFormat === 'lua' ? 'lua' : 'zip'}`;
a.click();
URL.revokeObjectURL(url);
};
const resetAndClose = () => {
setExportComplete(false);
setExportProgress(0);
onClose();
};
return (
<Dialog open={open} onOpenChange={resetAndClose}>
<DialogContent className="max-w-lg p-0 gap-0 bg-[#12121a] border-white/10">
<DialogHeader className="px-6 py-4 border-b border-white/10">
<DialogTitle className="flex items-center gap-2 text-lg">
<Download className="h-5 w-5" />
Export Project
</DialogTitle>
</DialogHeader>
<div className="p-6 space-y-6">
{/* Platform indicator */}
<div className={cn("flex items-center gap-2 text-sm", platformColors[platform])}>
<Package className="h-4 w-4" />
<span className="capitalize font-medium">{platform} Export</span>
</div>
{/* Export name */}
<div className="space-y-2">
<Label>Export Name</Label>
<Input
value={exportName}
onChange={(e) => setExportName(e.target.value)}
placeholder="my-project"
className="bg-white/5 border-white/10"
/>
</div>
{/* Format selection */}
<div className="space-y-3">
<Label>Export Format</Label>
<RadioGroup value={selectedFormat} onValueChange={setSelectedFormat}>
{exportFormats[platform].map((format) => (
<div
key={format.id}
className={cn(
"flex items-start gap-3 p-3 rounded-lg border transition-colors cursor-pointer",
selectedFormat === format.id
? "border-purple-500 bg-purple-500/10"
: "border-white/10 hover:border-white/20"
)}
onClick={() => setSelectedFormat(format.id)}
>
<RadioGroupItem value={format.id} className="mt-0.5" />
<div>
<div className="font-medium text-sm">{format.name}</div>
<div className="text-xs text-muted-foreground">{format.description}</div>
</div>
</div>
))}
</RadioGroup>
</div>
{/* Options */}
<div className="space-y-3">
<Label>Options</Label>
<div className="space-y-2">
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={includeAssets}
onCheckedChange={(v) => setIncludeAssets(!!v)}
/>
<span className="text-sm">Include assets (images, sounds, models)</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={minify}
onCheckedChange={(v) => setMinify(!!v)}
/>
<span className="text-sm">Minify code for production</span>
</label>
</div>
</div>
{/* Progress */}
{(isExporting || exportComplete) && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span>{exportComplete ? "Export complete!" : "Exporting..."}</span>
<span>{exportProgress}%</span>
</div>
<Progress value={exportProgress} className="h-2" />
</div>
)}
</div>
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-white/10">
<Button variant="ghost" onClick={resetAndClose}>Cancel</Button>
<Button
onClick={handleExport}
disabled={isExporting || !exportName.trim()}
className="bg-purple-600 hover:bg-purple-500 gap-2"
>
{isExporting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Exporting...
</>
) : exportComplete ? (
<>
<Check className="h-4 w-4" />
Downloaded
</>
) : (
<>
<Download className="h-4 w-4" />
Export
</>
)}
</Button>
</div>
</DialogContent>
</Dialog>
);
}

View file

@ -1,325 +0,0 @@
"use client";
import React, { useState } from "react";
import {
File,
Folder,
FolderOpen,
ChevronRight,
ChevronDown,
FolderPlus,
FilePlus,
Search,
MoreHorizontal,
Code,
FileCode,
FileJson,
FileText,
Image,
X,
Check,
} from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { FileNode, FolderNode, File as OpenFileType } from "@/lib/aethex-data";
import { cn } from "@/lib/utils";
type FileNavigatorProps = {
onOpenFile: (file: OpenFileType) => void;
fileTree: FolderNode;
onCreateFile?: (name: string, parentPath?: string) => void;
onCreateFolder?: (name: string, parentPath?: string) => void;
};
const getFileIcon = (filename: string) => {
const ext = filename.split('.').pop()?.toLowerCase();
switch (ext) {
case 'lua':
case 'luau':
return <FileCode className="h-4 w-4 text-blue-400" />;
case 'ts':
case 'tsx':
return <FileCode className="h-4 w-4 text-blue-500" />;
case 'js':
case 'jsx':
return <FileCode className="h-4 w-4 text-yellow-400" />;
case 'json':
return <FileJson className="h-4 w-4 text-yellow-500" />;
case 'md':
return <FileText className="h-4 w-4 text-gray-400" />;
case 'png':
case 'jpg':
case 'svg':
return <Image className="h-4 w-4 text-purple-400" />;
default:
return <File className="h-4 w-4 text-muted-foreground" />;
}
};
interface TreeItemProps {
node: FileNode | FolderNode;
depth: number;
onOpenFile: (file: OpenFileType) => void;
}
function TreeItem({ node, depth, onOpenFile }: TreeItemProps) {
const [isOpen, setIsOpen] = useState(depth < 2);
const isFolder = node.type === 'folder';
if (isFolder) {
const folderNode = node as FolderNode;
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger asChild>
<button
className={cn(
"flex w-full items-center gap-1.5 rounded-sm px-2 py-1 text-sm hover:bg-accent/50 transition-colors",
"text-muted-foreground hover:text-foreground"
)}
style={{ paddingLeft: `${depth * 12 + 8}px` }}
>
{isOpen ? (
<ChevronDown className="h-3.5 w-3.5 shrink-0" />
) : (
<ChevronRight className="h-3.5 w-3.5 shrink-0" />
)}
{isOpen ? (
<FolderOpen className="h-4 w-4 text-amber-400 shrink-0" />
) : (
<Folder className="h-4 w-4 text-amber-400 shrink-0" />
)}
<span className="truncate">{folderNode.name}</span>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
{folderNode.children.map((child, index) => (
<TreeItem
key={child.name + index}
node={child}
depth={depth + 1}
onOpenFile={onOpenFile}
/>
))}
</CollapsibleContent>
</Collapsible>
);
}
const fileNode = node as FileNode;
return (
<button
className={cn(
"flex w-full items-center gap-1.5 rounded-sm px-2 py-1 text-sm hover:bg-accent/50 transition-colors",
"text-muted-foreground hover:text-foreground"
)}
style={{ paddingLeft: `${depth * 12 + 24}px` }}
onClick={() =>
onOpenFile({
id: fileNode.name,
name: fileNode.name,
language: fileNode.language || "text",
content: fileNode.content || "",
})
}
>
{getFileIcon(fileNode.name)}
<span className="truncate">{fileNode.name}</span>
</button>
);
}
const FileNavigator: React.FC<FileNavigatorProps> = ({ fileTree, onOpenFile, onCreateFile, onCreateFolder }) => {
const [searchQuery, setSearchQuery] = useState("");
const [isCreatingFile, setIsCreatingFile] = useState(false);
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
const [newItemName, setNewItemName] = useState("");
const handleCreateFile = () => {
if (newItemName.trim()) {
if (onCreateFile) {
onCreateFile(newItemName.trim());
}
// Also immediately open the new file
const ext = newItemName.split('.').pop()?.toLowerCase();
let language = 'text';
if (ext === 'lua' || ext === 'luau') language = 'lua';
else if (ext === 'ts' || ext === 'tsx') language = 'typescript';
else if (ext === 'js' || ext === 'jsx') language = 'javascript';
else if (ext === 'json') language = 'json';
else if (ext === 'md') language = 'markdown';
onOpenFile({
id: newItemName.trim() + '-' + Date.now(),
name: newItemName.trim(),
language,
content: getDefaultContent(newItemName.trim()),
});
setNewItemName("");
setIsCreatingFile(false);
}
};
const handleCreateFolder = () => {
if (newItemName.trim() && onCreateFolder) {
onCreateFolder(newItemName.trim());
setNewItemName("");
setIsCreatingFolder(false);
}
};
const getDefaultContent = (filename: string): string => {
const ext = filename.split('.').pop()?.toLowerCase();
switch (ext) {
case 'lua':
case 'luau':
return `-- ${filename}\n-- Created with AeThex Studio\n\nlocal module = {}\n\nfunction module.init()\n print("Hello from ${filename}!")\nend\n\nreturn module\n`;
case 'ts':
case 'tsx':
return `// ${filename}\n// Created with AeThex Studio\n\nexport function init() {\n console.log("Hello from ${filename}!");\n}\n`;
case 'js':
case 'jsx':
return `// ${filename}\n// Created with AeThex Studio\n\nfunction init() {\n console.log("Hello from ${filename}!");\n}\n\nmodule.exports = { init };\n`;
case 'json':
return `{\n "name": "${filename.replace('.json', '')}",\n "version": "1.0.0"\n}\n`;
case 'md':
return `# ${filename.replace('.md', '')}\n\nCreated with AeThex Studio.\n`;
default:
return `// ${filename}\n`;
}
};
const cancelCreate = () => {
setIsCreatingFile(false);
setIsCreatingFolder(false);
setNewItemName("");
};
return (
<div className="flex h-full flex-col">
{/* Header */}
<div className="flex items-center justify-between px-3 py-2.5 border-b border-white/5">
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded bg-gradient-to-br from-amber-500/20 to-orange-500/20 flex items-center justify-center">
<Folder className="h-3 w-3 text-amber-400" />
</div>
<span className="text-xs font-medium text-foreground">
my-project
</span>
</div>
<div className="flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-foreground hover:bg-white/5"
onClick={() => {
setIsCreatingFile(true);
setIsCreatingFolder(false);
setNewItemName("");
}}
title="New File"
>
<FilePlus className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-foreground hover:bg-white/5"
onClick={() => {
setIsCreatingFolder(true);
setIsCreatingFile(false);
setNewItemName("");
}}
title="New Folder"
>
<FolderPlus className="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground hover:text-foreground hover:bg-white/5">
<MoreHorizontal className="h-3.5 w-3.5" />
</Button>
</div>
</div>
{/* New File/Folder Input */}
{(isCreatingFile || isCreatingFolder) && (
<div className="border-b border-white/5 p-2 bg-white/[0.02]">
<div className="flex items-center gap-1">
{isCreatingFile ? (
<File className="h-4 w-4 text-blue-400 shrink-0" />
) : (
<Folder className="h-4 w-4 text-amber-400 shrink-0" />
)}
<Input
autoFocus
placeholder={isCreatingFile ? "filename.lua" : "folder-name"}
value={newItemName}
onChange={(e) => setNewItemName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
isCreatingFile ? handleCreateFile() : handleCreateFolder();
} else if (e.key === 'Escape') {
cancelCreate();
}
}}
className="h-6 text-xs flex-1 bg-white/5 border-white/10 focus:border-purple-500/50"
/>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/10"
onClick={isCreatingFile ? handleCreateFile : handleCreateFolder}
>
<Check className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-red-400 hover:text-red-300 hover:bg-red-500/10"
onClick={cancelCreate}
>
<X className="h-3.5 w-3.5" />
</Button>
</div>
<p className="text-[10px] text-muted-foreground/70 mt-1 ml-5">
Enter to create · Escape to cancel
</p>
</div>
)}
{/* Search */}
<div className="px-2 py-1.5">
<div className="relative">
<Search className="absolute left-2.5 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground/50" />
<Input
placeholder="Search files..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-7 pl-7 text-xs bg-white/5 border-white/5 placeholder:text-muted-foreground/40 focus:border-purple-500/50 focus:bg-white/10"
/>
</div>
</div>
{/* File Tree */}
<ScrollArea className="flex-1">
<div className="pb-4 px-1">
<TreeItem node={fileTree} depth={0} onOpenFile={onOpenFile} />
</div>
</ScrollArea>
{/* Footer */}
<div className="border-t border-white/5 px-3 py-2">
<div className="text-[10px] text-muted-foreground/60 flex items-center gap-1.5">
<div className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
<span>Connected to workspace</span>
</div>
</div>
</div>
);
};
export default FileNavigator;

View file

@ -1,5 +0,0 @@
import React from "react";
export function FileTabs() {
return <div className="file-tabs">File Tabs</div>;
}

View file

@ -1,5 +0,0 @@
import React from "react";
export function FileTree() {
return <div className="file-tree">File Tree</div>;
}

View file

@ -1,14 +0,0 @@
import React from 'react';
interface GamePreviewPanelProps {
gameUrl: string;
}
export const GamePreviewPanel: React.FC<GamePreviewPanelProps> = ({ gameUrl }) => {
return (
<div className="game-preview-panel">
<h2>Game Preview</h2>
<iframe src={gameUrl} title="Game Preview" width="100%" height="500px" frameBorder="0" />
</div>
);
};

View file

@ -1,219 +0,0 @@
"use client";
import { useState } from "react";
import { toast } from "sonner";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
GitBranch,
GitCommit,
GitPullRequest,
GitMerge,
Plus,
Check,
X,
RefreshCw,
Upload,
Download,
FileCode,
FilePlus,
FileMinus,
FileEdit,
} from "lucide-react";
import { cn } from "@/lib/utils";
interface GitPanelProps {
open: boolean;
onClose: () => void;
}
const mockChanges = [
{ file: "src/main.lua", status: "modified" as const },
{ file: "src/utils/helpers.lua", status: "added" as const },
{ file: "src/old-module.lua", status: "deleted" as const },
];
const mockBranches = [
{ name: "main", current: true },
{ name: "feature/new-ui", current: false },
{ name: "fix/player-spawn", current: false },
];
const mockCommits = [
{ hash: "a1b2c3d", message: "Add player spawn system", author: "You", time: "2 hours ago" },
{ hash: "e4f5g6h", message: "Initial project setup", author: "You", time: "5 hours ago" },
{ hash: "i7j8k9l", message: "Configure build system", author: "You", time: "1 day ago" },
];
export function GitPanel({ open, onClose }: GitPanelProps) {
const [commitMessage, setCommitMessage] = useState("");
const [selectedBranch, setSelectedBranch] = useState("main");
const [stagedFiles, setStagedFiles] = useState<string[]>([]);
const toggleStage = (file: string) => {
setStagedFiles(prev =>
prev.includes(file) ? prev.filter(f => f !== file) : [...prev, file]
);
};
const stageAll = () => {
setStagedFiles(mockChanges.map(c => c.file));
};
const handleCommit = () => {
if (commitMessage.trim() && stagedFiles.length > 0) {
toast.success(`Committed ${stagedFiles.length} file${stagedFiles.length > 1 ? 's' : ''}`, {
description: commitMessage,
});
setCommitMessage("");
setStagedFiles([]);
}
};
const getStatusIcon = (status: "modified" | "added" | "deleted") => {
switch (status) {
case "modified":
return <FileEdit className="h-4 w-4 text-yellow-400" />;
case "added":
return <FilePlus className="h-4 w-4 text-emerald-400" />;
case "deleted":
return <FileMinus className="h-4 w-4 text-red-400" />;
}
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-2xl h-[600px] p-0 gap-0 bg-[#12121a] border-white/10">
<DialogHeader className="px-6 py-4 border-b border-white/10">
<DialogTitle className="flex items-center gap-2 text-lg">
<GitBranch className="h-5 w-5" />
Source Control
</DialogTitle>
</DialogHeader>
<div className="flex flex-1 overflow-hidden">
{/* Main content */}
<div className="flex-1 flex flex-col">
{/* Branch selector */}
<div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-muted-foreground" />
<select
value={selectedBranch}
onChange={(e) => setSelectedBranch(e.target.value)}
className="bg-transparent text-sm font-medium focus:outline-none cursor-pointer"
>
{mockBranches.map(b => (
<option key={b.name} value={b.name}>{b.name}</option>
))}
</select>
</div>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" className="h-7 w-7" title="Create branch">
<Plus className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-7 w-7" title="Refresh">
<RefreshCw className="h-4 w-4" />
</Button>
</div>
</div>
{/* Changes */}
<div className="flex-1 overflow-hidden flex flex-col">
<div className="px-4 py-2 flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground uppercase">Changes ({mockChanges.length})</span>
<Button variant="ghost" size="sm" className="h-6 text-xs" onClick={stageAll}>
Stage All
</Button>
</div>
<ScrollArea className="flex-1 px-4">
<div className="space-y-1">
{mockChanges.map((change) => (
<div
key={change.file}
className={cn(
"flex items-center gap-2 px-2 py-1.5 rounded text-sm hover:bg-white/5 cursor-pointer transition-colors",
stagedFiles.includes(change.file) && "bg-purple-500/10"
)}
onClick={() => toggleStage(change.file)}
>
<div className={cn(
"h-4 w-4 rounded border flex items-center justify-center transition-colors",
stagedFiles.includes(change.file)
? "bg-purple-500 border-purple-500"
: "border-white/20"
)}>
{stagedFiles.includes(change.file) && <Check className="h-3 w-3 text-white" />}
</div>
{getStatusIcon(change.status)}
<span className="flex-1 truncate">{change.file}</span>
</div>
))}
</div>
</ScrollArea>
{/* Commit message */}
<div className="p-4 border-t border-white/10">
<Input
placeholder="Commit message..."
value={commitMessage}
onChange={(e) => setCommitMessage(e.target.value)}
className="mb-2 bg-white/5 border-white/10"
/>
<div className="flex gap-2">
<Button
className="flex-1 bg-purple-600 hover:bg-purple-500"
disabled={!commitMessage.trim() || stagedFiles.length === 0}
onClick={handleCommit}
>
<GitCommit className="h-4 w-4 mr-2" />
Commit ({stagedFiles.length})
</Button>
</div>
</div>
</div>
</div>
{/* Sidebar - Recent commits */}
<div className="w-64 border-l border-white/10 flex flex-col">
<div className="px-4 py-3 border-b border-white/10">
<span className="text-xs font-medium text-muted-foreground uppercase">Recent Commits</span>
</div>
<ScrollArea className="flex-1 p-2">
<div className="space-y-2">
{mockCommits.map((commit) => (
<div
key={commit.hash}
className="p-2 rounded hover:bg-white/5 cursor-pointer transition-colors"
>
<div className="flex items-center gap-2 mb-1">
<GitCommit className="h-3 w-3 text-muted-foreground" />
<code className="text-xs text-purple-400">{commit.hash}</code>
</div>
<p className="text-sm truncate">{commit.message}</p>
<p className="text-xs text-muted-foreground">{commit.time}</p>
</div>
))}
</div>
</ScrollArea>
{/* Push/Pull */}
<div className="p-2 border-t border-white/10 space-y-2">
<Button variant="outline" className="w-full h-8 text-xs gap-2">
<Download className="h-3 w-3" />
Pull
</Button>
<Button variant="outline" className="w-full h-8 text-xs gap-2">
<Upload className="h-3 w-3" />
Push
</Button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}

View file

@ -1,90 +0,0 @@
import type { SVGProps } from "react";
export function AethexLogo(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M14.5 13.03a3 3 0 1 0-3.5-3.53" />
<path d="M12 2a10 10 0 1 0 10 10" />
</svg>
);
}
export function RobloxIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-red-500"
{...props}
>
<path d="m11.9 2.7-8.2 3.4 3.4 8.2 8.2-3.4Z" />
<path d="m13.4 7.2-5.7 2.4" />
<path d="m19.2 8.5-8.2 3.4-3.4-8.2 8.2-3.4Z" />
</svg>
);
}
export function WebIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-blue-500"
{...props}
>
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
);
}
export function MobileIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-green-500"
{...props}
>
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
<path d="M12 18h.01" />
</svg>
);
}
export function GoogleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px" {...props}>
<path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/>
<path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/>
<path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/>
<path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.574l6.19,5.238C39.99,36.596,44,30.85,44,24C44,22.659,43.862,21.35,43.611,20.083z"/>
</svg>
)
}

View file

@ -1 +0,0 @@
export { AethexStudio } from "./aethex-studio";

View file

@ -1 +0,0 @@
export { AethexStudio } from "./aethex-studio";

View file

@ -1,19 +0,0 @@
import React from 'react';
interface InputProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export const Input: React.FC<InputProps> = ({ value, onChange, placeholder }) => {
return (
<input
className="input"
type="text"
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
/>
);
};

View file

@ -1,69 +0,0 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { AethexLogo, GoogleIcon } from "./icons";
import { Button } from "@/components/ui/button";
import { Github } from "lucide-react";
import { PlaceHolderImages } from "../../lib/placeholder-images";
export function LoginPage() {
const loginIllustration = PlaceHolderImages.find(
(p: { id: string }) => p.id === "login-illustration"
);
return (
<div className="flex min-h-screen w-full bg-background">
<div className="flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
<div className="mx-auto w-full max-w-sm lg:w-96">
<div>
<AethexLogo className="h-10 w-auto text-primary" />
<h1 className="mt-6 font-headline text-3xl font-bold tracking-tight text-foreground">
Welcome to AeThex Studio
</h1>
<p className="mt-2 text-sm text-muted-foreground">
The Next-Generation Cross-Platform IDE.
</p>
</div>
<div className="mt-8">
<div className="space-y-3">
<Link href="/dashboard" passHref>
<Button
size="lg"
className="w-full bg-gradient-to-r from-primary via-purple-500 to-fuchsia-500 text-primary-foreground transition-all hover:opacity-90"
>
Sign in with AeThex Passport
</Button>
</Link>
<Link href="/dashboard" passHref>
<Button size="lg" variant="outline" className="w-full">
<GoogleIcon className="mr-3 h-5 w-5" />
Sign in with Google
</Button>
</Link>
<Link href="/dashboard" passHref>
<Button size="lg" variant="outline" className="w-full">
<Github className="mr-3 h-5 w-5" />
Sign in with GitHub
</Button>
</Link>
</div>
</div>
</div>
</div>
<div className="relative hidden w-0 flex-1 lg:block">
{loginIllustration && (
<Image
className="absolute inset-0 h-full w-full object-cover"
src={loginIllustration.imageUrl}
alt="Cross-platform game development illustration"
data-ai-hint={loginIllustration.imageHint}
fill
priority
/>
)}
</div>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show more