🌈 Add Light Trinity system (RedLight + GreenLight + YellowLight)

Complete deployment of unified Light Trinity system:

🔴 RedLight: Template & brand system (18 HTML templates)
💚 GreenLight: Project & collaboration (14 layers, 103 templates)
💛 YellowLight: Infrastructure & deployment
🌈 Trinity: Unified compliance & testing

Includes:
- 12 documentation files
- 8 shell scripts
- 18 HTML brand templates
- Trinity compliance workflow

Built by: Cece + Alexa
Date: December 23, 2025
Source: blackroad-os/blackroad-os-infra
🌸
This commit is contained in:
Alexa Louise
2025-12-23 15:47:25 -06:00
parent 40150e4d58
commit f9ec2879ba
47 changed files with 43897 additions and 0 deletions

View File

@@ -0,0 +1,577 @@
# 🔴 REDLIGHT TEMPLATE SYSTEM
## Visual Templates for Worlds, Websites, and Experiences
### Version 1.0 — December 23, 2025
**The visual layer of BlackRoad OS.**
**GreenLight is project management. YellowLight is infrastructure. RedLight is creation.** 🔴
---
## **OVERVIEW**
RedLight is the **template library** for all BlackRoad OS visual experiences:
- **3D Worlds** (interactive planets, metaverse, games)
- **Websites** (landing pages, dashboards, apps)
- **Animations** (motion systems, transitions, effects)
- **Design Systems** (Schematiq, brand assets, components)
Every RedLight template is:
- **Self-contained** (single HTML file with embedded CSS/JS)
- **Three.js powered** (for 3D worlds and animations)
- **BlackRoad branded** (gradient colors: #FF9D00#FF6B00#FF0066#D600AA#7700FF#0066FF)
- **Deploy-ready** (Cloudflare Pages, GitHub Pages, or static hosting)
---
## **TEMPLATE CATEGORIES**
### **1. WORLD TEMPLATES** 🌍
Interactive 3D environments built with Three.js
| Template | Description | Features |
|----------|-------------|----------|
| `blackroad-world-template.html` | Base 3D world starter | Sphere, orbit controls, stats |
| `blackroad-earth.html` | Global network visualization | Cities, connections, glow effects |
| `blackroad-earth-biomes.html` | Living Earth with climate zones | Biomes, weather, day/night cycle |
| `blackroad-earth-game.html` | Interactive Earth game | Click cities, missions, score |
| `blackroad-earth-real.html` | Photorealistic Earth | High-res textures, atmosphere |
| `earth-replica.html` | Simple Earth replica | Minimal, clean, starter |
| `blackroad-living-planet.html` | Animated living planet | Particle systems, evolution |
| `blackroad-living-earth.html` | Living Earth v2 | Enhanced biomes, ecosystems |
| `blackroad-living-world.html` | Complete living world | Full simulation, AI agents |
| `blackroad-world-v2.html` | World template v2 | Improved performance, features |
| `blackroad-3d-world.html` | Basic 3D world | Geometric shapes, lighting |
| `blackroad-metaverse.html` | Metaverse environment | Multi-user, avatars, spaces |
| `blackroad-game.html` | Game template | Physics, controls, gameplay |
### **2. ANIMATION TEMPLATES** ✨
Motion systems and visual effects
| Template | Description | Features |
|----------|-------------|----------|
| `blackroad-animation.html` | Animation system | Keyframes, easing, sequences |
| `blackroad-motion.html` | Motion graphics | Particles, trails, effects |
| `blackroad-ultimate.html` | Ultimate animation suite | All effects combined |
| `schematiq-animation.html` | Schematiq motion system | Design system animations |
### **3. WEBSITE TEMPLATES** 🌐
Landing pages and web experiences
| Template | Description | Features |
|----------|-------------|----------|
| `schematiq-page.html` | Schematiq design system | Component library, docs |
---
## **TEMPLATE STRUCTURE**
Every RedLight template follows this pattern:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BlackRoad OS — [Template Name]</title>
<style>
/* BlackRoad brand styles */
/* Gradient colors: #FF9D00 → #FF6B00 → #FF0066 → #D600AA → #7700FF → #0066FF */
</style>
</head>
<body>
<!-- Info panel (top-left) -->
<div id="info">
<h1>Title</h1>
<p>Description</p>
</div>
<!-- Stats panel (top-right) -->
<div id="stats">
<!-- Real-time stats -->
</div>
<!-- Controls (bottom-right) -->
<div id="controls">
<!-- Interactive buttons -->
</div>
<!-- Three.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Template logic
</script>
</body>
</html>
```
---
## **BRAND COLORS**
All RedLight templates use the **BlackRoad gradient palette**:
```css
/* Primary gradient */
background: linear-gradient(90deg,
#FF9D00, /* Amber */
#FF6B00, /* Orange */
#FF0066, /* Pink */
#FF006B, /* Magenta */
#D600AA, /* Purple */
#7700FF, /* Violet */
#0066FF /* Blue */
);
```
**Usage:**
- Text gradients: `-webkit-background-clip: text; -webkit-text-fill-color: transparent;`
- Glow effects: `box-shadow: 0 0 20px rgba(255, 29, 108, 0.5);`
- Particles: Random colors from palette
- UI accents: `#FF1D6C` (primary pink)
---
## **DEPLOYMENT**
### **Cloudflare Pages**
```bash
# Deploy template
wrangler pages deploy blackroad-earth.html --project-name=earth
# Custom domain
wrangler pages domain add earth.blackroad.io
```
### **GitHub Pages**
```bash
# Commit template
git add blackroad-earth.html
git commit -m "Add Earth template"
git push
# Deploy
# Settings → Pages → Source: main branch → /
# URL: https://blackroad-os.github.io/blackroad-earth.html
```
### **Railway**
```bash
# Create project
railway init
# Deploy
railway up
```
---
## **TEMPLATE METADATA**
Each template includes metadata for indexing:
```javascript
const TEMPLATE_META = {
id: 'blackroad-earth',
name: 'BlackRoad Earth',
category: 'world',
tags: ['3d', 'earth', 'globe', 'network'],
version: '1.0.0',
author: 'BlackRoad OS',
license: 'MIT',
dependencies: ['three.js@r128'],
colors: ['#FF9D00', '#FF6B00', '#FF0066', '#D600AA', '#7700FF', '#0066FF'],
features: [
'Orbit controls',
'City markers',
'Network connections',
'Glow effects',
'Day/night cycle'
]
};
```
---
## **INTEGRATION WITH GREENLIGHT**
RedLight templates integrate with GreenLight project management:
```bash
# Log template deployment
source ~/memory-greenlight-templates.sh
gl_deploy \
"earth.blackroad.io" \
"https://earth.blackroad.io" \
"RedLight Earth template deployed" \
"👉" \
"🎨"
# Log template creation
gl_feature \
"RedLight Mars template" \
"Interactive Mars globe with rover missions" \
"🍖" \
"⭐"
```
---
## **TEMPLATE CATEGORIES IN GREENLIGHT**
| Emoji | Category | Code | Description |
|-------|----------|------|-------------|
| 🌍 | WORLD | `world` | 3D interactive worlds |
| 🌐 | WEBSITE | `website` | Landing pages, dashboards |
| ✨ | ANIMATION | `animation` | Motion graphics, effects |
| 🎨 | DESIGN | `design` | Design systems, components |
| 🎮 | GAME | `game` | Interactive games |
| 📱 | APP | `app` | Web applications |
| 🖼️ | VISUAL | `visual` | Visual effects, shaders |
---
## **REDLIGHT MEMORY TEMPLATES**
### Template: Create template
```bash
rl_template_create() {
local template_name="$1"
local category="$2" # world, website, animation, design
local description="$3"
local category_emoji=""
case "$category" in
world) category_emoji="🌍" ;;
website) category_emoji="🌐" ;;
animation) category_emoji="✨" ;;
design) category_emoji="🎨" ;;
game) category_emoji="🎮" ;;
*) category_emoji="🔴" ;;
esac
~/memory-system.sh log "created" "$template_name" \
"[🔴${category_emoji}👉📌] RedLight template: $description"
}
```
### Template: Deploy template
```bash
rl_template_deploy() {
local template_name="$1"
local url="$2"
local platform="${3:-cloudflare}" # cloudflare, github, railway
local platform_emoji=""
case "$platform" in
cloudflare) platform_emoji="☁️" ;;
github) platform_emoji="🐙" ;;
railway) platform_emoji="🚂" ;;
*) platform_emoji="🌐" ;;
esac
~/memory-system.sh log "deployed" "$template_name" \
"[🔴🚀${platform_emoji}✅] Template deployed: $url"
}
```
### Template: Update template
```bash
rl_template_update() {
local template_name="$1"
local changes="$2"
~/memory-system.sh log "updated" "$template_name" \
"[🔴🔄👉📌] Template updated: $changes"
}
```
---
## **TEMPLATE LIBRARY STRUCTURE**
```
redlight-templates/
├── worlds/
│ ├── blackroad-earth.html
│ ├── blackroad-earth-biomes.html
│ ├── blackroad-earth-game.html
│ ├── blackroad-living-planet.html
│ ├── blackroad-metaverse.html
│ └── blackroad-world-template.html
├── animations/
│ ├── blackroad-animation.html
│ ├── blackroad-motion.html
│ └── schematiq-animation.html
├── websites/
│ └── schematiq-page.html
├── games/
│ └── blackroad-game.html
├── components/
│ ├── three-js-setup.js
│ ├── orbit-controls.js
│ ├── particle-system.js
│ └── glow-effects.js
└── assets/
├── textures/
│ ├── earth-8k.jpg
│ ├── earth-normal.jpg
│ └── earth-clouds.png
├── fonts/
│ └── inter.woff2
└── icons/
└── blackroad-logo.svg
```
---
## **NATS SUBJECT PATTERNS**
```
redlight.{category}.{template_id}.{action}
Examples:
redlight.world.earth.deployed
redlight.animation.motion.created
redlight.website.schematiq.updated
redlight.game.blackroad.published
```
---
## **DATABASE SCHEMA**
```sql
CREATE TYPE rl_category AS ENUM (
'world', 'website', 'animation', 'design', 'game', 'app', 'visual'
);
CREATE TYPE rl_status AS ENUM (
'draft', 'wip', 'review', 'published', 'archived', 'deprecated'
);
CREATE TABLE redlight_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
category rl_category NOT NULL,
description TEXT,
file_path TEXT NOT NULL,
version VARCHAR(50) DEFAULT '1.0.0',
status rl_status DEFAULT 'draft',
tags TEXT[],
dependencies JSONB,
metadata JSONB,
deployed_url TEXT,
preview_url TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deployed_at TIMESTAMP
);
CREATE TABLE redlight_deployments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID REFERENCES redlight_templates(id),
url TEXT NOT NULL,
platform VARCHAR(100), -- cloudflare, github, railway
environment VARCHAR(50), -- staging, production
version VARCHAR(50),
status VARCHAR(50), -- deploying, active, failed, retired
deployed_at TIMESTAMP DEFAULT NOW(),
metadata JSONB
);
CREATE TABLE redlight_analytics (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID REFERENCES redlight_templates(id),
views INTEGER DEFAULT 0,
interactions INTEGER DEFAULT 0,
avg_session_duration INTERVAL,
bounce_rate DECIMAL(5,2),
recorded_at TIMESTAMP DEFAULT NOW()
);
```
---
## **CLI COMMANDS**
```bash
# Create template management script
~/redlight-templates.sh create <name> <category> <description>
~/redlight-templates.sh list [category]
~/redlight-templates.sh deploy <template> <platform> [environment]
~/redlight-templates.sh update <template> <changes>
~/redlight-templates.sh status [template]
~/redlight-templates.sh analytics <template>
~/redlight-templates.sh preview <template>
```
---
## **EXAMPLES**
### Create new world template
```bash
# Create template file
cp redlight-templates/worlds/blackroad-world-template.html \
redlight-templates/worlds/blackroad-mars.html
# Log creation
rl_template_create "blackroad-mars" "world" \
"Interactive Mars globe with rover missions"
# Update GreenLight
gl_feature "RedLight Mars template" \
"Interactive Mars exploration experience" \
"🍖" "⭐"
```
### Deploy template
```bash
# Deploy to Cloudflare Pages
wrangler pages deploy redlight-templates/worlds/blackroad-earth.html \
--project-name=earth-blackroad
# Log deployment
rl_template_deploy "blackroad-earth" \
"https://earth.blackroad.io" \
"cloudflare"
# Update GreenLight
gl_deploy "earth.blackroad.io" \
"https://earth.blackroad.io" \
"RedLight Earth template live" \
"👉" "🎨"
```
### Update template
```bash
# Make changes to template
# (edit blackroad-earth.html)
# Log update
rl_template_update "blackroad-earth" \
"Added city labels and enhanced glow effects"
# Update GreenLight
gl_wip "blackroad-earth template" \
"Enhanced city labels and glow effects" \
"🌸" "👉"
```
---
## **TEMPLATE FEATURES TAXONOMY**
### 3D Features
- ✅ Orbit controls
- ✅ Zoom controls
- ✅ Pan controls
- ✅ Auto-rotate
- ✅ Camera animations
- ✅ Click interactions
- ✅ Hover effects
- ✅ Touch support
- ✅ VR support
### Visual Features
- ✅ Particle systems
- ✅ Glow effects
- ✅ Shaders
- ✅ Textures
- ✅ Normal maps
- ✅ Bump maps
- ✅ Environment maps
- ✅ Post-processing
- ✅ Bloom
- ✅ Fog
### Interactive Features
- ✅ Click handlers
- ✅ Keyboard controls
- ✅ Gamepad support
- ✅ Voice commands
- ✅ Gesture recognition
- ✅ Multiplayer
- ✅ Real-time sync
- ✅ Chat
- ✅ Avatars
### Data Features
- ✅ API integration
- ✅ WebSocket streams
- ✅ Real-time updates
- ✅ Database sync
- ✅ Analytics tracking
- ✅ Error logging
- ✅ Performance monitoring
---
## **PERFORMANCE TARGETS**
| Metric | Target | Excellent |
|--------|--------|-----------|
| Load time | < 3s | < 1s |
| FPS | > 30 | > 60 |
| Memory | < 500MB | < 200MB |
| Bundle size | < 2MB | < 500KB |
| Time to interactive | < 5s | < 2s |
---
## **BROWSER SUPPORT**
- ✅ Chrome 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ Edge 90+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
- ⚠️ IE11 (degraded, 2D fallback)
---
## **ACCESSIBILITY**
All RedLight templates include:
- ✅ Keyboard navigation
- ✅ Screen reader support
- ✅ High contrast mode
- ✅ Reduced motion mode
- ✅ ARIA labels
- ✅ Focus indicators
- ✅ Alt text for images
---
## **LICENSE**
All RedLight templates are:
- **Open Source** (MIT License)
- **Free to use** (commercial & personal)
- **Customizable** (modify freely)
- **Attribution appreciated** (not required)
---
## **THE TRINITY**
**🟢 GreenLight** = Project Management (tasks, workflows, coordination)
**🟡 YellowLight** = Infrastructure (repos, connectors, deployments)
**🔴 RedLight** = Templates (worlds, websites, experiences)
Together: **The complete BlackRoad OS visual language.** 🛣️
---
**Created:** December 23, 2025
**Author:** Cece + Alexa
**Version:** 1.0.0
**Status:** 🎯 CANONICAL

View File

@@ -0,0 +1,650 @@
#!/bin/bash
# RedLight Memory Templates
# Standardized logging for template management with BlackRoad memory system
set -e
MEMORY_SYSTEM="$HOME/memory-system.sh"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
# Helper to log with RedLight tags
rl_log() {
local rl_tags="$1"
local action="$2"
local entity="$3"
local details="$4"
# Prepend RedLight tags to details
local full_details="[${rl_tags}] ${details}"
$MEMORY_SYSTEM log "$action" "$entity" "$full_details"
}
# ═══════════════════════════════════════════════════════
# TEMPLATE MANAGEMENT
# ═══════════════════════════════════════════════════════
# Template: Create template
rl_template_create() {
local template_name="$1"
local category="$2" # world, website, animation, design, game, app, visual
local description="$3"
local category_emoji=""
case "$category" in
world) category_emoji="🌍" ;;
website) category_emoji="🌐" ;;
animation) category_emoji="✨" ;;
design) category_emoji="🎨" ;;
game) category_emoji="🎮" ;;
app) category_emoji="📱" ;;
visual) category_emoji="🖼️" ;;
*) category_emoji="🔴" ;;
esac
rl_log "🔴${category_emoji}👉📌" \
"created" \
"$template_name" \
"RedLight template: $description"
}
# Template: Update template
rl_template_update() {
local template_name="$1"
local changes="$2"
local category="${3:-world}"
local category_emoji=""
case "$category" in
world) category_emoji="🌍" ;;
website) category_emoji="🌐" ;;
animation) category_emoji="✨" ;;
*) category_emoji="🔴" ;;
esac
rl_log "🔴🔄${category_emoji}👉" \
"updated" \
"$template_name" \
"Template updated: $changes"
}
# Template: Delete template
rl_template_delete() {
local template_name="$1"
local reason="${2:-deprecated}"
rl_log "🔴❌👉📌" \
"deleted" \
"$template_name" \
"Template removed: $reason"
}
# Template: Copy template
rl_template_copy() {
local source_template="$1"
local new_template="$2"
local purpose="$3"
rl_log "🔴📋👉📌" \
"copied" \
"$new_template" \
"Copied from $source_template: $purpose"
}
# Template: Archive template
rl_template_archive() {
local template_name="$1"
local reason="${2:-outdated}"
rl_log "🔴📦👉📌" \
"archived" \
"$template_name" \
"Template archived: $reason"
}
# ═══════════════════════════════════════════════════════
# DEPLOYMENT
# ═══════════════════════════════════════════════════════
# Template: Deploy template
rl_template_deploy() {
local template_name="$1"
local url="$2"
local platform="${3:-cloudflare}" # cloudflare, github, railway, vercel
local platform_emoji=""
case "$platform" in
cloudflare) platform_emoji="☁️" ;;
github) platform_emoji="🐙" ;;
railway) platform_emoji="🚂" ;;
vercel) platform_emoji="▲" ;;
netlify) platform_emoji="🦋" ;;
*) platform_emoji="🌐" ;;
esac
rl_log "🔴🚀${platform_emoji}" \
"deployed" \
"$template_name" \
"Template deployed: $url"
}
# Template: Deployment failed
rl_deploy_failed() {
local template_name="$1"
local platform="$2"
local error="${3:-unknown error}"
rl_log "🔴❌🚨🔥" \
"deploy_failed" \
"$template_name" \
"Deployment failed on $platform: $error"
}
# Template: Deployment rollback
rl_deploy_rollback() {
local template_name="$1"
local from_version="$2"
local to_version="$3"
rl_log "🔴🔙⚠️📌" \
"rollback" \
"$template_name" \
"Rolled back from v$from_version to v$to_version"
}
# ═══════════════════════════════════════════════════════
# VERSIONING
# ═══════════════════════════════════════════════════════
# Template: Version release
rl_version_release() {
local template_name="$1"
local version="$2"
local changes="$3"
rl_log "🔴🏷️✅📌" \
"version_release" \
"$template_name" \
"Released v$version: $changes"
}
# Template: Version tag
rl_version_tag() {
local template_name="$1"
local tag="$2"
local commit="$3"
rl_log "🔴🔖👉📌" \
"version_tag" \
"$template_name" \
"Tagged $tag at commit $commit"
}
# ═══════════════════════════════════════════════════════
# TESTING
# ═══════════════════════════════════════════════════════
# Template: Test passed
rl_test_passed() {
local template_name="$1"
local test_type="${2:-visual}" # visual, performance, accessibility
local details="${3:-all tests passed}"
local test_emoji=""
case "$test_type" in
visual) test_emoji="👀" ;;
performance) test_emoji="⚡" ;;
accessibility) test_emoji="♿" ;;
integration) test_emoji="🔗" ;;
*) test_emoji="✅" ;;
esac
rl_log "🔴${test_emoji}✅👉" \
"test_passed" \
"$template_name" \
"$test_type test: $details"
}
# Template: Test failed
rl_test_failed() {
local template_name="$1"
local test_type="$2"
local error="$3"
rl_log "🔴❌🚨🔥" \
"test_failed" \
"$template_name" \
"$test_type test failed: $error"
}
# ═══════════════════════════════════════════════════════
# ANALYTICS
# ═══════════════════════════════════════════════════════
# Template: Analytics snapshot
rl_analytics_snapshot() {
local template_name="$1"
local views="$2"
local interactions="$3"
local avg_session="${4:-unknown}"
rl_log "🔴📊👉📌" \
"analytics" \
"$template_name" \
"Views: $views, Interactions: $interactions, Avg session: $avg_session"
}
# Template: Performance metrics
rl_performance_metrics() {
local template_name="$1"
local fps="$2"
local load_time="$3"
local memory_mb="${4:-unknown}"
rl_log "🔴⚡📊👉" \
"performance" \
"$template_name" \
"FPS: $fps, Load: ${load_time}s, Memory: ${memory_mb}MB"
}
# ═══════════════════════════════════════════════════════
# FEATURES
# ═══════════════════════════════════════════════════════
# Template: Feature added
rl_feature_added() {
local template_name="$1"
local feature="$2"
local description="$3"
rl_log "🔴✨👉⭐" \
"feature_added" \
"$template_name" \
"New feature: $feature - $description"
}
# Template: Bug fixed
rl_bug_fixed() {
local template_name="$1"
local bug="$2"
local fix="$3"
rl_log "🔴🐛✅👉" \
"bug_fixed" \
"$template_name" \
"Bug fixed: $bug$fix"
}
# ═══════════════════════════════════════════════════════
# COLLABORATION
# ═══════════════════════════════════════════════════════
# Template: Template shared
rl_template_shared() {
local template_name="$1"
local with_who="$2"
local platform="${3:-link}"
rl_log "🔴🤝👉📌" \
"shared" \
"$template_name" \
"Shared with $with_who via $platform"
}
# Template: Feedback received
rl_feedback_received() {
local template_name="$1"
local from_who="$2"
local feedback="$3"
rl_log "🔴💬👉📌" \
"feedback" \
"$template_name" \
"Feedback from $from_who: $feedback"
}
# ═══════════════════════════════════════════════════════
# WORLD-SPECIFIC TEMPLATES
# ═══════════════════════════════════════════════════════
# Template: World created
rl_world_create() {
local world_name="$1"
local type="${2:-planet}" # planet, city, environment, metaverse
local features="$3"
rl_log "🔴🌍🆕📌" \
"world_created" \
"$world_name" \
"Type: $type, Features: $features"
}
# Template: Biome added
rl_biome_add() {
local world_name="$1"
local biome_name="$2"
local properties="$3"
rl_log "🔴🌍🌱👉" \
"biome_added" \
"$world_name" \
"New biome: $biome_name ($properties)"
}
# Template: Entity spawned
rl_entity_spawn() {
local world_name="$1"
local entity_type="$2" # particle, object, agent, city
local count="${3:-1}"
rl_log "🔴🌍✨👉" \
"entity_spawned" \
"$world_name" \
"Spawned $count × $entity_type"
}
# ═══════════════════════════════════════════════════════
# ANIMATION-SPECIFIC TEMPLATES
# ═══════════════════════════════════════════════════════
# Template: Animation created
rl_animation_create() {
local animation_name="$1"
local type="${2:-motion}" # motion, particle, shader, morph
local duration="${3:-unknown}"
rl_log "🔴✨🆕📌" \
"animation_created" \
"$animation_name" \
"Type: $type, Duration: $duration"
}
# Template: Effect applied
rl_effect_apply() {
local template_name="$1"
local effect_type="$2" # glow, bloom, blur, distortion
local intensity="${3:-medium}"
rl_log "🔴✨👉📌" \
"effect_applied" \
"$template_name" \
"Applied $effect_type effect (intensity: $intensity)"
}
# ═══════════════════════════════════════════════════════
# DESIGN SYSTEM TEMPLATES
# ═══════════════════════════════════════════════════════
# Template: Component created
rl_component_create() {
local component_name="$1"
local type="${2:-ui}" # ui, layout, utility
local variants="$3"
rl_log "🔴🎨🆕📌" \
"component_created" \
"$component_name" \
"Type: $type, Variants: $variants"
}
# Template: Theme updated
rl_theme_update() {
local theme_name="$1"
local changes="$2"
rl_log "🔴🎨🔄📌" \
"theme_updated" \
"$theme_name" \
"Theme changes: $changes"
}
# ═══════════════════════════════════════════════════════
# INTERACTIVE TEMPLATES
# ═══════════════════════════════════════════════════════
# Template: Interaction added
rl_interaction_add() {
local template_name="$1"
local interaction_type="$2" # click, hover, drag, voice, gesture
local action="$3"
local interaction_emoji=""
case "$interaction_type" in
click) interaction_emoji="🖱️" ;;
hover) interaction_emoji="👆" ;;
drag) interaction_emoji="✋" ;;
voice) interaction_emoji="🎤" ;;
gesture) interaction_emoji="👋" ;;
*) interaction_emoji="⚡" ;;
esac
rl_log "🔴${interaction_emoji}👉📌" \
"interaction_added" \
"$template_name" \
"$interaction_type$action"
}
# Template: User session
rl_user_session() {
local template_name="$1"
local session_duration="$2"
local interactions="$3"
rl_log "🔴👤📊👉" \
"user_session" \
"$template_name" \
"Session: ${session_duration}s, Interactions: $interactions"
}
# ═══════════════════════════════════════════════════════
# INTEGRATION WITH GREENLIGHT
# ═══════════════════════════════════════════════════════
# Helper: Create GreenLight task from RedLight template
rl_create_gl_task() {
local template_name="$1"
local task_description="$2"
local priority="${3:-📌}"
source "$HOME/memory-greenlight-templates.sh"
gl_feature \
"RedLight: $template_name" \
"$task_description" \
"🍖" \
"$priority"
}
# Helper: Update GreenLight on deployment
rl_notify_gl_deploy() {
local template_name="$1"
local url="$2"
local platform="${3:-cloudflare}"
source "$HOME/memory-greenlight-templates.sh"
gl_deploy \
"$template_name" \
"$url" \
"RedLight template deployed via $platform" \
"👉" \
"🎨"
}
# ═══════════════════════════════════════════════════════
# SHOW HELP
# ═══════════════════════════════════════════════════════
show_help() {
cat <<'EOF'
RedLight Memory Templates
USAGE:
source memory-redlight-templates.sh
rl_<template> [args...]
TEMPLATE MANAGEMENT:
rl_template_create <name> <category> <description>
Create new template (world/website/animation/design/game/app/visual)
rl_template_update <name> <changes> [category]
Update existing template
rl_template_delete <name> [reason]
Delete template
rl_template_copy <source> <new_name> <purpose>
Copy template
rl_template_archive <name> [reason]
Archive template
DEPLOYMENT:
rl_template_deploy <name> <url> [platform]
Deploy template (cloudflare/github/railway/vercel)
rl_deploy_failed <name> <platform> [error]
Log deployment failure
rl_deploy_rollback <name> <from_version> <to_version>
Rollback deployment
VERSIONING:
rl_version_release <name> <version> <changes>
Release new version
rl_version_tag <name> <tag> <commit>
Tag version
TESTING:
rl_test_passed <name> [test_type] [details]
Log successful test (visual/performance/accessibility)
rl_test_failed <name> <test_type> <error>
Log test failure
ANALYTICS:
rl_analytics_snapshot <name> <views> <interactions> [avg_session]
Record analytics snapshot
rl_performance_metrics <name> <fps> <load_time> [memory_mb]
Record performance metrics
FEATURES:
rl_feature_added <name> <feature> <description>
Log new feature
rl_bug_fixed <name> <bug> <fix>
Log bug fix
COLLABORATION:
rl_template_shared <name> <with_who> [platform]
Log template sharing
rl_feedback_received <name> <from_who> <feedback>
Log feedback
WORLD-SPECIFIC:
rl_world_create <name> [type] <features>
Create 3D world (planet/city/environment/metaverse)
rl_biome_add <world_name> <biome_name> <properties>
Add biome to world
rl_entity_spawn <world_name> <entity_type> [count]
Spawn entities in world
ANIMATION-SPECIFIC:
rl_animation_create <name> [type] [duration]
Create animation (motion/particle/shader/morph)
rl_effect_apply <name> <effect_type> [intensity]
Apply visual effect (glow/bloom/blur/distortion)
DESIGN SYSTEM:
rl_component_create <name> [type] <variants>
Create design component (ui/layout/utility)
rl_theme_update <name> <changes>
Update theme
INTERACTIVE:
rl_interaction_add <name> <type> <action>
Add interaction (click/hover/drag/voice/gesture)
rl_user_session <name> <duration> <interactions>
Log user session
GREENLIGHT INTEGRATION:
rl_create_gl_task <name> <description> [priority]
Create GreenLight task from template
rl_notify_gl_deploy <name> <url> [platform]
Notify GreenLight of deployment
EXAMPLES:
# Create Earth template
rl_template_create "blackroad-earth" "world" \
"Interactive Earth globe with city markers"
# Deploy to Cloudflare
rl_template_deploy "blackroad-earth" \
"https://earth.blackroad.io" \
"cloudflare"
# Add biome
rl_biome_add "blackroad-earth" "tropical-rainforest" \
"High humidity, dense vegetation, 25-30°C"
# Record performance
rl_performance_metrics "blackroad-earth" "60" "1.2" "180"
# Create GreenLight task
rl_create_gl_task "blackroad-mars" \
"Create interactive Mars template with rover missions" \
"⭐"
CATEGORIES:
🌍 world - 3D interactive worlds
🌐 website - Landing pages, dashboards
✨ animation - Motion graphics, effects
🎨 design - Design systems, components
🎮 game - Interactive games
📱 app - Web applications
🖼️ visual - Visual effects, shaders
EOF
}
# Main command handler
case "${1:-help}" in
help|--help|-h)
show_help
;;
*)
# If sourced, functions are available
# If executed directly, show help
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
show_help
fi
;;
esac

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,897 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BlackRoad OS — 3D World</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
}
#canvas-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
canvas {
display: block;
}
/* UI Overlay */
.ui-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
/* Logo */
.logo {
position: fixed;
top: 34px;
left: 34px;
display: flex;
align-items: center;
gap: 13px;
pointer-events: auto;
z-index: 101;
}
.logo-mark {
width: 44px;
height: 44px;
}
.logo-mark svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 0 20px rgba(255, 29, 108, 0.5));
}
.road-dashes {
animation: spin 10s linear infinite;
transform-origin: 50px 50px;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.logo-text {
font-size: 15px;
font-weight: 500;
color: #fff;
letter-spacing: 0.02em;
}
/* Title */
.hero-title {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
pointer-events: none;
opacity: 0;
animation: fadeIn 2s ease 1s forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.hero-title h1 {
font-size: clamp(48px, 12vw, 144px);
font-weight: 600;
letter-spacing: -0.03em;
color: #fff;
line-height: 1;
margin-bottom: 21px;
text-shadow: 0 0 80px rgba(255, 29, 108, 0.5);
}
.hero-title h1 span {
background: linear-gradient(135deg, #F5A623 0%, #FF1D6C 38.2%, #9C27B0 61.8%, #2979FF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-title p {
font-size: 21px;
color: rgba(255, 255, 255, 0.6);
max-width: 500px;
margin: 0 auto;
}
/* Controls hint */
.controls {
position: fixed;
bottom: 34px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 34px;
pointer-events: none;
opacity: 0;
animation: fadeIn 2s ease 2s forwards;
}
.control-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.1em;
}
.control-key {
padding: 6px 10px;
background: rgba(255, 255, 255, 0.1);
border-radius: 6px;
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
}
/* Stats */
.stats {
position: fixed;
top: 34px;
right: 34px;
text-align: right;
pointer-events: none;
opacity: 0;
animation: fadeIn 2s ease 1.5s forwards;
}
.stat {
margin-bottom: 13px;
}
.stat-value {
font-size: 34px;
font-weight: 600;
color: #fff;
line-height: 1;
background: linear-gradient(135deg, #F5A623, #FF1D6C);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* Loading */
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 1s ease, visibility 1s ease;
}
.loading.hidden {
opacity: 0;
visibility: hidden;
}
.loading-logo {
width: 120px;
height: 120px;
margin-bottom: 34px;
}
.loading-logo svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 0 40px rgba(255, 29, 108, 0.6));
}
.loading-bar {
width: 200px;
height: 2px;
background: rgba(255, 255, 255, 0.1);
border-radius: 1px;
overflow: hidden;
}
.loading-progress {
height: 100%;
background: linear-gradient(90deg, #F5A623, #FF1D6C, #9C27B0, #2979FF);
width: 0%;
transition: width 0.3s ease;
}
.loading-text {
margin-top: 21px;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.2em;
}
</style>
</head>
<body>
<!-- Loading Screen -->
<div class="loading" id="loading">
<div class="loading-logo">
<svg viewBox="0 0 100 100" fill="none">
<circle cx="50" cy="50" r="44" stroke="#FF1D6C" stroke-width="6"/>
<g class="road-dashes">
<rect x="47" y="4" width="6" height="12" fill="#000" rx="2"/>
<rect x="47" y="84" width="6" height="12" fill="#000" rx="2"/>
<rect x="84" y="47" width="12" height="6" fill="#000" rx="2"/>
<rect x="4" y="47" width="12" height="6" fill="#000" rx="2"/>
<rect x="75" y="18" width="6" height="10" fill="#000" rx="2" transform="rotate(45 78 23)"/>
<rect x="19" y="72" width="6" height="10" fill="#000" rx="2" transform="rotate(45 22 77)"/>
<rect x="72" y="72" width="6" height="10" fill="#000" rx="2" transform="rotate(-45 75 77)"/>
<rect x="22" y="18" width="6" height="10" fill="#000" rx="2" transform="rotate(-45 25 23)"/>
</g>
<path d="M50 10C27.9 10 10 27.9 10 50H90C90 27.9 72.1 10 50 10Z" fill="#F5A623"/>
<path d="M10 50C10 72.1 27.9 90 50 90C72.1 90 90 72.1 90 50H10Z" fill="#2979FF"/>
<circle cx="50" cy="50" r="14" fill="#000"/>
</svg>
</div>
<div class="loading-bar">
<div class="loading-progress" id="loadingProgress"></div>
</div>
<div class="loading-text">Entering the road</div>
</div>
<!-- 3D Canvas -->
<div id="canvas-container"></div>
<!-- UI Overlay -->
<div class="ui-overlay">
<!-- Logo -->
<div class="logo">
<div class="logo-mark">
<svg viewBox="0 0 100 100" fill="none">
<circle cx="50" cy="50" r="44" stroke="#FF1D6C" stroke-width="6"/>
<g class="road-dashes">
<rect x="47" y="4" width="6" height="12" fill="#000" rx="2"/>
<rect x="47" y="84" width="6" height="12" fill="#000" rx="2"/>
<rect x="84" y="47" width="12" height="6" fill="#000" rx="2"/>
<rect x="4" y="47" width="12" height="6" fill="#000" rx="2"/>
<rect x="75" y="18" width="6" height="10" fill="#000" rx="2" transform="rotate(45 78 23)"/>
<rect x="19" y="72" width="6" height="10" fill="#000" rx="2" transform="rotate(45 22 77)"/>
<rect x="72" y="72" width="6" height="10" fill="#000" rx="2" transform="rotate(-45 75 77)"/>
<rect x="22" y="18" width="6" height="10" fill="#000" rx="2" transform="rotate(-45 25 23)"/>
</g>
<path d="M50 10C27.9 10 10 27.9 10 50H90C90 27.9 72.1 10 50 10Z" fill="#F5A623"/>
<path d="M10 50C10 72.1 27.9 90 50 90C72.1 90 90 72.1 90 50H10Z" fill="#2979FF"/>
<circle cx="50" cy="50" r="14" fill="#000"/>
</svg>
</div>
<span class="logo-text">BlackRoad OS</span>
</div>
<!-- Title -->
<div class="hero-title">
<h1>Enter the <span>Road</span></h1>
<p>Navigate the infinite highway of possibility</p>
</div>
<!-- Stats -->
<div class="stats">
<div class="stat">
<div class="stat-value" id="speedStat">0</div>
<div class="stat-label">Speed</div>
</div>
<div class="stat">
<div class="stat-value" id="distanceStat">0</div>
<div class="stat-label">Distance</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="control-item">
<span class="control-key">W/↑</span>
<span>Accelerate</span>
</div>
<div class="control-item">
<span class="control-key">S/↓</span>
<span>Brake</span>
</div>
<div class="control-item">
<span class="control-key">A/D</span>
<span>Steer</span>
</div>
<div class="control-item">
<span class="control-key">Mouse</span>
<span>Look</span>
</div>
</div>
</div>
<!-- Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Brand Colors
const COLORS = {
black: 0x000000,
white: 0xffffff,
amber: 0xF5A623,
orange: 0xF26522,
hotPink: 0xFF1D6C,
magenta: 0xE91E63,
electricBlue: 0x2979FF,
skyBlue: 0x448AFF,
violet: 0x9C27B0,
deepPurple: 0x5E35B1
};
// Scene Setup
let scene, camera, renderer;
let road, roadLines = [];
let buildings = [];
let particles = [];
let floatingOrbs = [];
let centralLogo;
let speed = 0;
let distance = 0;
let targetSpeed = 2;
let steerAngle = 0;
let time = 0;
// Mouse
let mouseX = 0, mouseY = 0;
let targetMouseX = 0, targetMouseY = 0;
// Keys
const keys = {
forward: false,
backward: false,
left: false,
right: false
};
// Initialize
function init() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(COLORS.black);
scene.fog = new THREE.FogExp2(COLORS.black, 0.015);
// Camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 3, 10);
camera.lookAt(0, 2, -100);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Lights
createLights();
// Create World
createRoad();
createBuildings();
createParticles();
createFloatingOrbs();
createCentralLogo();
createStars();
// Events
window.addEventListener('resize', onResize);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
// Start
simulateLoading();
}
function createLights() {
// Ambient
const ambient = new THREE.AmbientLight(0x111111);
scene.add(ambient);
// Main directional light
const mainLight = new THREE.DirectionalLight(0xffffff, 0.5);
mainLight.position.set(0, 50, 50);
mainLight.castShadow = true;
scene.add(mainLight);
// Pink accent light
const pinkLight = new THREE.PointLight(COLORS.hotPink, 2, 100);
pinkLight.position.set(-20, 10, -30);
scene.add(pinkLight);
// Blue accent light
const blueLight = new THREE.PointLight(COLORS.electricBlue, 2, 100);
blueLight.position.set(20, 10, -50);
scene.add(blueLight);
// Amber accent light
const amberLight = new THREE.PointLight(COLORS.amber, 1.5, 80);
amberLight.position.set(0, 20, -20);
scene.add(amberLight);
}
function createRoad() {
// Main road surface
const roadGeometry = new THREE.PlaneGeometry(20, 2000, 1, 100);
const roadMaterial = new THREE.MeshStandardMaterial({
color: 0x111111,
roughness: 0.9,
metalness: 0.1
});
road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.y = 0;
road.position.z = -900;
road.receiveShadow = true;
scene.add(road);
// Road lines (dashes)
const lineMaterial = new THREE.MeshBasicMaterial({ color: COLORS.hotPink });
for (let i = 0; i < 200; i++) {
const lineGeometry = new THREE.BoxGeometry(0.3, 0.05, 4);
const line = new THREE.Mesh(lineGeometry, lineMaterial);
line.position.set(0, 0.03, -i * 10);
roadLines.push(line);
scene.add(line);
}
// Side lines
const sideLineMaterial = new THREE.MeshBasicMaterial({ color: COLORS.amber });
for (let side of [-1, 1]) {
for (let i = 0; i < 200; i++) {
const sideLineGeometry = new THREE.BoxGeometry(0.2, 0.05, 8);
const sideLine = new THREE.Mesh(sideLineGeometry, sideLineMaterial);
sideLine.position.set(side * 9, 0.03, -i * 10);
roadLines.push(sideLine);
scene.add(sideLine);
}
}
}
function createBuildings() {
const buildingColors = [COLORS.hotPink, COLORS.electricBlue, COLORS.violet, COLORS.amber, COLORS.deepPurple];
for (let side of [-1, 1]) {
for (let i = 0; i < 50; i++) {
const width = 5 + Math.random() * 15;
const height = 20 + Math.random() * 80;
const depth = 5 + Math.random() * 15;
const geometry = new THREE.BoxGeometry(width, height, depth);
// Wireframe style building
const edges = new THREE.EdgesGeometry(geometry);
const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
const lineMaterial = new THREE.LineBasicMaterial({
color: color,
transparent: true,
opacity: 0.6
});
const wireframe = new THREE.LineSegments(edges, lineMaterial);
wireframe.position.set(
side * (15 + Math.random() * 30),
height / 2,
-i * 40 - Math.random() * 20
);
buildings.push({
mesh: wireframe,
originalZ: wireframe.position.z,
speed: 0.5 + Math.random() * 0.5
});
scene.add(wireframe);
// Add glowing windows
if (Math.random() > 0.5) {
const windowGeometry = new THREE.PlaneGeometry(width * 0.8, height * 0.8);
const windowMaterial = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.1,
side: THREE.DoubleSide
});
const windowMesh = new THREE.Mesh(windowGeometry, windowMaterial);
windowMesh.position.copy(wireframe.position);
windowMesh.position.x += side * (width / 2 + 0.1);
windowMesh.rotation.y = Math.PI / 2;
scene.add(windowMesh);
}
}
}
}
function createParticles() {
const particleColors = [COLORS.hotPink, COLORS.amber, COLORS.electricBlue, COLORS.violet];
for (let i = 0; i < 500; i++) {
const geometry = new THREE.SphereGeometry(0.05 + Math.random() * 0.1, 8, 8);
const material = new THREE.MeshBasicMaterial({
color: particleColors[Math.floor(Math.random() * particleColors.length)],
transparent: true,
opacity: 0.8
});
const particle = new THREE.Mesh(geometry, material);
particle.position.set(
(Math.random() - 0.5) * 100,
Math.random() * 50,
(Math.random() - 0.5) * 200 - 100
);
particles.push({
mesh: particle,
originalY: particle.position.y,
speed: 0.5 + Math.random() * 2,
phase: Math.random() * Math.PI * 2
});
scene.add(particle);
}
}
function createFloatingOrbs() {
const orbColors = [
{ color: COLORS.hotPink, emissive: COLORS.hotPink },
{ color: COLORS.electricBlue, emissive: COLORS.electricBlue },
{ color: COLORS.amber, emissive: COLORS.amber },
{ color: COLORS.violet, emissive: COLORS.violet }
];
for (let i = 0; i < 20; i++) {
const size = 1 + Math.random() * 3;
const geometry = new THREE.SphereGeometry(size, 32, 32);
const colorSet = orbColors[Math.floor(Math.random() * orbColors.length)];
const material = new THREE.MeshStandardMaterial({
color: colorSet.color,
emissive: colorSet.emissive,
emissiveIntensity: 0.5,
transparent: true,
opacity: 0.6,
roughness: 0.2,
metalness: 0.8
});
const orb = new THREE.Mesh(geometry, material);
orb.position.set(
(Math.random() - 0.5) * 80,
5 + Math.random() * 30,
-50 - Math.random() * 150
);
floatingOrbs.push({
mesh: orb,
originalPos: orb.position.clone(),
phase: Math.random() * Math.PI * 2,
speed: 0.3 + Math.random() * 0.7,
amplitude: 2 + Math.random() * 5
});
scene.add(orb);
}
}
function createCentralLogo() {
// Create the BlackRoad logo as a 3D object
const group = new THREE.Group();
// Outer ring (the road)
const ringGeometry = new THREE.TorusGeometry(5, 0.5, 16, 100);
const ringMaterial = new THREE.MeshStandardMaterial({
color: COLORS.hotPink,
emissive: COLORS.hotPink,
emissiveIntensity: 0.5,
roughness: 0.3,
metalness: 0.7
});
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
group.add(ring);
// Road dashes on the ring
for (let i = 0; i < 16; i++) {
const angle = (i / 16) * Math.PI * 2;
const dashGeometry = new THREE.BoxGeometry(0.3, 0.8, 0.3);
const dashMaterial = new THREE.MeshBasicMaterial({ color: COLORS.black });
const dash = new THREE.Mesh(dashGeometry, dashMaterial);
dash.position.set(
Math.cos(angle) * 5,
Math.sin(angle) * 5,
0.3
);
dash.rotation.z = angle;
group.add(dash);
}
// Top half (amber)
const topGeometry = new THREE.SphereGeometry(4, 32, 32, 0, Math.PI * 2, 0, Math.PI / 2);
const topMaterial = new THREE.MeshStandardMaterial({
color: COLORS.amber,
emissive: COLORS.amber,
emissiveIntensity: 0.3,
roughness: 0.4,
metalness: 0.6,
side: THREE.DoubleSide
});
const topHalf = new THREE.Mesh(topGeometry, topMaterial);
topHalf.rotation.x = Math.PI / 2;
group.add(topHalf);
// Bottom half (blue)
const bottomGeometry = new THREE.SphereGeometry(4, 32, 32, 0, Math.PI * 2, Math.PI / 2, Math.PI / 2);
const bottomMaterial = new THREE.MeshStandardMaterial({
color: COLORS.electricBlue,
emissive: COLORS.electricBlue,
emissiveIntensity: 0.3,
roughness: 0.4,
metalness: 0.6,
side: THREE.DoubleSide
});
const bottomHalf = new THREE.Mesh(bottomGeometry, bottomMaterial);
bottomHalf.rotation.x = Math.PI / 2;
group.add(bottomHalf);
// Pupil
const pupilGeometry = new THREE.SphereGeometry(1.5, 32, 32);
const pupilMaterial = new THREE.MeshStandardMaterial({
color: COLORS.black,
roughness: 0.1,
metalness: 0.9
});
const pupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
pupil.position.z = 2;
group.add(pupil);
// Highlight
const highlightGeometry = new THREE.SphereGeometry(0.4, 16, 16);
const highlightMaterial = new THREE.MeshBasicMaterial({ color: COLORS.white });
const highlight = new THREE.Mesh(highlightGeometry, highlightMaterial);
highlight.position.set(-0.5, 0.5, 3);
group.add(highlight);
group.position.set(0, 15, -80);
group.scale.set(2, 2, 2);
centralLogo = group;
scene.add(group);
}
function createStars() {
const starGeometry = new THREE.BufferGeometry();
const starCount = 2000;
const positions = new Float32Array(starCount * 3);
const colors = new Float32Array(starCount * 3);
const starColors = [
new THREE.Color(COLORS.white),
new THREE.Color(COLORS.hotPink),
new THREE.Color(COLORS.electricBlue),
new THREE.Color(COLORS.amber)
];
for (let i = 0; i < starCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 500;
positions[i * 3 + 1] = 50 + Math.random() * 200;
positions[i * 3 + 2] = (Math.random() - 0.5) * 500 - 200;
const color = starColors[Math.floor(Math.random() * starColors.length)];
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const starMaterial = new THREE.PointsMaterial({
size: 0.5,
vertexColors: true,
transparent: true,
opacity: 0.8
});
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
}
function simulateLoading() {
let progress = 0;
const progressBar = document.getElementById('loadingProgress');
const loadingScreen = document.getElementById('loading');
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
setTimeout(() => {
loadingScreen.classList.add('hidden');
animate();
}, 500);
}
progressBar.style.width = progress + '%';
}, 100);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(e) {
targetMouseX = (e.clientX / window.innerWidth - 0.5) * 2;
targetMouseY = (e.clientY / window.innerHeight - 0.5) * 2;
}
function onKeyDown(e) {
switch(e.key.toLowerCase()) {
case 'w':
case 'arrowup':
keys.forward = true;
break;
case 's':
case 'arrowdown':
keys.backward = true;
break;
case 'a':
case 'arrowleft':
keys.left = true;
break;
case 'd':
case 'arrowright':
keys.right = true;
break;
}
}
function onKeyUp(e) {
switch(e.key.toLowerCase()) {
case 'w':
case 'arrowup':
keys.forward = false;
break;
case 's':
case 'arrowdown':
keys.backward = false;
break;
case 'a':
case 'arrowleft':
keys.left = false;
break;
case 'd':
case 'arrowright':
keys.right = false;
break;
}
}
function animate() {
requestAnimationFrame(animate);
time += 0.016;
// Update speed based on input
if (keys.forward) targetSpeed = Math.min(targetSpeed + 0.1, 8);
if (keys.backward) targetSpeed = Math.max(targetSpeed - 0.15, 0.5);
if (!keys.forward && !keys.backward) {
targetSpeed = Math.max(targetSpeed - 0.02, 2);
}
speed += (targetSpeed - speed) * 0.05;
distance += speed * 0.1;
// Steering
if (keys.left) steerAngle = Math.min(steerAngle + 0.02, 0.3);
if (keys.right) steerAngle = Math.max(steerAngle - 0.02, -0.3);
if (!keys.left && !keys.right) steerAngle *= 0.95;
// Mouse smoothing
mouseX += (targetMouseX - mouseX) * 0.05;
mouseY += (targetMouseY - mouseY) * 0.05;
// Camera movement
camera.position.x = steerAngle * 10 + mouseX * 3;
camera.position.y = 3 + mouseY * 2;
camera.rotation.y = steerAngle * 0.5 + mouseX * 0.1;
camera.rotation.x = mouseY * 0.1;
// Move road lines
roadLines.forEach(line => {
line.position.z += speed;
if (line.position.z > 20) {
line.position.z -= 2000;
}
});
// Move buildings
buildings.forEach(building => {
building.mesh.position.z += speed * building.speed;
if (building.mesh.position.z > 50) {
building.mesh.position.z -= 2000;
}
});
// Animate particles
particles.forEach(p => {
p.mesh.position.z += speed * 0.5;
p.mesh.position.y = p.originalY + Math.sin(time * p.speed + p.phase) * 2;
if (p.mesh.position.z > 50) {
p.mesh.position.z -= 300;
}
});
// Animate floating orbs
floatingOrbs.forEach(orb => {
orb.mesh.position.y = orb.originalPos.y + Math.sin(time * orb.speed + orb.phase) * orb.amplitude;
orb.mesh.position.x = orb.originalPos.x + Math.cos(time * orb.speed * 0.5 + orb.phase) * orb.amplitude * 0.5;
orb.mesh.rotation.y += 0.01;
orb.mesh.position.z += speed * 0.3;
if (orb.mesh.position.z > 50) {
orb.mesh.position.z -= 250;
}
});
// Animate central logo
if (centralLogo) {
centralLogo.rotation.y += 0.005;
centralLogo.rotation.x = Math.sin(time * 0.5) * 0.1;
centralLogo.position.y = 15 + Math.sin(time * 0.3) * 2;
// Keep logo in view
centralLogo.position.z += speed * 0.1;
if (centralLogo.position.z > -30) {
centralLogo.position.z = -150;
}
}
// Update UI
document.getElementById('speedStat').textContent = Math.floor(speed * 50);
document.getElementById('distanceStat').textContent = Math.floor(distance);
renderer.render(scene, camera);
}
// Start
init();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,964 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BlackRoad Site Architecture — Visual Map</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0a0a0a;
--surface: #141414;
--surface-2: #1a1a1a;
--border: #2a2a2a;
--text: #e5e5e5;
--text-muted: #888;
--accent: #ff3d00;
--accent-dim: rgba(255, 61, 0, 0.2);
--teal: #00bfa5;
--purple: #8b5cf6;
--gold: #f59e0b;
--blue: #3b82f6;
--pink: #ec4899;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--bg);
color: var(--text);
font-family: 'DM Sans', sans-serif;
line-height: 1.6;
padding: 2rem;
min-height: 100vh;
}
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
font-weight: 700;
}
h1 span {
color: var(--accent);
}
.subtitle {
color: var(--text-muted);
margin-bottom: 3rem;
font-size: 1.1rem;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.tab {
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-muted);
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem;
transition: all 0.2s;
}
.tab:hover {
border-color: var(--accent);
color: var(--text);
}
.tab.active {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
/* Domain Cards */
.domain-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 1.5rem;
margin-bottom: 3rem;
}
.domain-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
transition: all 0.3s;
}
.domain-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.domain-card.tier-1 { border-left: 3px solid var(--accent); }
.domain-card.tier-2 { border-left: 3px solid var(--teal); }
.domain-card.tier-3 { border-left: 3px solid var(--purple); }
.domain-card.tier-4 { border-left: 3px solid var(--gold); }
.domain-card.tier-5 { border-left: 3px solid var(--blue); }
.domain-card.tier-6 { border-left: 3px solid var(--pink); }
.domain-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.domain-name {
font-family: 'JetBrains Mono', monospace;
font-size: 1.1rem;
font-weight: 600;
color: var(--text);
}
.domain-tier {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.tier-1 .domain-tier { background: var(--accent-dim); color: var(--accent); }
.tier-2 .domain-tier { background: rgba(0, 191, 165, 0.2); color: var(--teal); }
.tier-3 .domain-tier { background: rgba(139, 92, 246, 0.2); color: var(--purple); }
.tier-4 .domain-tier { background: rgba(245, 158, 11, 0.2); color: var(--gold); }
.tier-5 .domain-tier { background: rgba(59, 130, 246, 0.2); color: var(--blue); }
.tier-6 .domain-tier { background: rgba(236, 72, 153, 0.2); color: var(--pink); }
.domain-purpose {
color: var(--text-muted);
font-size: 0.9rem;
margin-bottom: 1rem;
}
.domain-pages {
list-style: none;
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
}
.domain-pages li {
padding: 0.4rem 0;
border-bottom: 1px solid var(--border);
color: var(--text-muted);
display: flex;
align-items: center;
gap: 0.5rem;
}
.domain-pages li:last-child {
border-bottom: none;
}
.domain-pages li::before {
content: '→';
color: var(--accent);
}
.domain-user {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
font-size: 0.8rem;
color: var(--text-muted);
}
.domain-user strong {
color: var(--text);
}
/* Flow Diagrams */
.flow-container {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
}
.flow-title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.flow-description {
color: var(--text-muted);
margin-bottom: 1.5rem;
font-size: 0.9rem;
}
.flow-steps {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
.flow-step {
background: var(--surface-2);
border: 1px solid var(--border);
padding: 0.75rem 1rem;
border-radius: 4px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
}
.flow-step.highlight {
border-color: var(--accent);
background: var(--accent-dim);
}
.flow-arrow {
color: var(--accent);
font-size: 1.25rem;
}
.flow-action {
color: var(--text-muted);
font-size: 0.75rem;
font-style: italic;
}
/* Org/Repo Mapping */
.mapping-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.mapping-table th {
text-align: left;
padding: 1rem;
background: var(--surface-2);
border-bottom: 2px solid var(--accent);
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
}
.mapping-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.mapping-table tr:hover {
background: var(--surface);
}
.mapping-table code {
background: var(--surface-2);
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
}
/* Priority Section */
.priority-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
}
.priority-phase {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
}
.priority-phase h3 {
font-size: 0.85rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.priority-phase h4 {
font-size: 1.1rem;
margin-bottom: 1rem;
color: var(--accent);
}
.priority-list {
list-style: none;
}
.priority-list li {
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.priority-list li:last-child {
border-bottom: none;
}
.priority-num {
background: var(--accent);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: 600;
}
/* Legend */
.legend {
display: flex;
gap: 2rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: var(--text-muted);
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 3px;
}
.legend-dot.t1 { background: var(--accent); }
.legend-dot.t2 { background: var(--teal); }
.legend-dot.t3 { background: var(--purple); }
.legend-dot.t4 { background: var(--gold); }
.legend-dot.t5 { background: var(--blue); }
.legend-dot.t6 { background: var(--pink); }
@media (max-width: 1024px) {
.priority-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.domain-grid {
grid-template-columns: 1fr;
}
.priority-grid {
grid-template-columns: 1fr;
}
.flow-steps {
flex-direction: column;
align-items: flex-start;
}
.flow-arrow {
transform: rotate(90deg);
margin: 0.5rem 0;
}
}
</style>
</head>
<body>
<h1>Black<span>Road</span> Site Architecture</h1>
<p class="subtitle">Complete page-by-page flow mapping for @blackboxprogramming's ecosystem</p>
<div class="tabs">
<button class="tab active" data-tab="domains">Domains</button>
<button class="tab" data-tab="flows">User Flows</button>
<button class="tab" data-tab="orgs">GitHub Orgs</button>
<button class="tab" data-tab="repos">Repos</button>
<button class="tab" data-tab="priority">Priority</button>
</div>
<!-- DOMAINS TAB -->
<div class="content-section active" id="domains">
<div class="legend">
<div class="legend-item"><div class="legend-dot t1"></div> Tier 1: Consumer Hub</div>
<div class="legend-item"><div class="legend-dot t2"></div> Tier 2: B2B Services</div>
<div class="legend-item"><div class="legend-dot t3"></div> Tier 3: Developer Portal</div>
<div class="legend-item"><div class="legend-dot t4"></div> Tier 4: Blockchain</div>
<div class="legend-item"><div class="legend-dot t5"></div> Tier 5: AI Ecosystem</div>
<div class="legend-item"><div class="legend-dot t6"></div> Tier 6: Personal Brand</div>
</div>
<div class="domain-grid">
<!-- Tier 1 -->
<div class="domain-card tier-1">
<div class="domain-header">
<span class="domain-name">blackroad.io</span>
<span class="domain-tier">Main Hub</span>
</div>
<p class="domain-purpose">The single entry point. "If you don't know where to start, start here."</p>
<ul class="domain-pages">
<li>/ — Homepage (pain points, solution, CTA)</li>
<li>/create — Creator Portal entry</li>
<li>/create/video — RoadView Studio</li>
<li>/create/code — Lucidia IDE</li>
<li>/create/games — RoadEngine</li>
<li>/build — Business Portal</li>
<li>/learn — Education Portal</li>
<li>/learn/research — Research Hub</li>
<li>/tools/prism — Prism Console</li>
<li>/pricing — All products</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Everyone — creators, developers, learners, businesses</div>
</div>
<!-- Tier 2 -->
<div class="domain-card tier-2">
<div class="domain-header">
<span class="domain-name">blackroadinc.us</span>
<span class="domain-tier">B2B</span>
</div>
<p class="domain-purpose">Professional services, consulting, and the Ops Starter productized service.</p>
<ul class="domain-pages">
<li>/ — B2B landing page</li>
<li>/services — Service overview</li>
<li>/services/ops-starter — $500 productized service</li>
<li>/services/consulting — Custom consulting</li>
<li>/services/enterprise — Enterprise partnerships</li>
<li>/case-studies — Client work</li>
<li>/book — Calendly embed</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Businesses, enterprises, developers with messy repos</div>
</div>
<!-- Tier 3 -->
<div class="domain-card tier-3">
<div class="domain-header">
<span class="domain-name">blackroad.systems</span>
<span class="domain-tier">Developer</span>
</div>
<p class="domain-purpose">Documentation, API reference, SDK guides, OS architecture.</p>
<ul class="domain-pages">
<li>/ — Developer landing</li>
<li>/docs — Documentation root</li>
<li>/docs/getting-started — Quick start</li>
<li>/docs/architecture — System design</li>
<li>/docs/agents — Agent documentation</li>
<li>/docs/packs — Pack system</li>
<li>/api — Interactive API explorer</li>
<li>/sdk — SDK downloads</li>
<li>/changelog — Release notes</li>
<li>/status — System status</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Developers, integrators, contributors</div>
</div>
<!-- Tier 4 -->
<div class="domain-card tier-4">
<div class="domain-header">
<span class="domain-name">blackroad.network</span>
<span class="domain-tier">Blockchain</span>
</div>
<p class="domain-purpose">RoadChain infrastructure, RoadCoin, validator nodes.</p>
<ul class="domain-pages">
<li>/ — Web3 landing</li>
<li>/roadchain — Chain documentation</li>
<li>/roadcoin — Token info</li>
<li>/validators — Run a node</li>
<li>/wallet — Wallet connection</li>
<li>/explorer — Block explorer</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Web3 users, validators, crypto enthusiasts</div>
</div>
<!-- Tier 5 -->
<div class="domain-card tier-5">
<div class="domain-header">
<span class="domain-name">lucidia.earth</span>
<span class="domain-tier">AI Ecosystem</span>
</div>
<p class="domain-purpose">The Lucidia AI system — 1,000 unique agents project.</p>
<ul class="domain-pages">
<li>/ — Agent ecosystem landing</li>
<li>/agents — Agent directory</li>
<li>/architecture — Technical deep dive</li>
<li>/community — Community hub</li>
<li>/contribute — Contribution guide</li>
</ul>
<div class="domain-user"><strong>Users:</strong> AI researchers, agent developers, enthusiasts</div>
</div>
<div class="domain-card tier-5">
<div class="domain-header">
<span class="domain-name">lucidia.studio</span>
<span class="domain-tier">Creative AI</span>
</div>
<p class="domain-purpose">Creative tools powered by Lucidia agents.</p>
<ul class="domain-pages">
<li>/ — Studio landing</li>
<li>/tools — Available creative tools</li>
<li>/gallery — Created works</li>
</ul>
<div class="domain-user"><strong>Users:</strong> AI artists, creative technologists</div>
</div>
<!-- Tier 6 -->
<div class="domain-card tier-6">
<div class="domain-header">
<span class="domain-name">blackroad.me</span>
<span class="domain-tier">Personal</span>
</div>
<p class="domain-purpose">Alexa's personal thought leadership and media page.</p>
<ul class="domain-pages">
<li>/ — Personal landing</li>
<li>/about — Bio & background</li>
<li>/writing — Blog posts, papers</li>
<li>/speaking — Speaking topics</li>
<li>/press — Media coverage</li>
<li>/contact — Get in touch</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Media, investors, collaborators</div>
</div>
<!-- Quantum domains -->
<div class="domain-card tier-3">
<div class="domain-header">
<span class="domain-name">blackroadquantum.com</span>
<span class="domain-tier">Research</span>
</div>
<p class="domain-purpose">Quantum computing research, theoretical frameworks.</p>
<ul class="domain-pages">
<li>/ — Research landing</li>
<li>/papers — Published research</li>
<li>/frameworks — Mathematical frameworks</li>
<li>/simulations — Quantum sims</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Researchers, academics, quantum enthusiasts</div>
</div>
<div class="domain-card tier-5">
<div class="domain-header">
<span class="domain-name">aliceqi.com</span>
<span class="domain-tier">Edge AI</span>
</div>
<p class="domain-purpose">Alice (Pi 400) agent interface for edge AI.</p>
<ul class="domain-pages">
<li>/ — Alice landing</li>
<li>/setup — Hardware setup guide</li>
<li>/api — Local API docs</li>
</ul>
<div class="domain-user"><strong>Users:</strong> Hardware hackers, edge AI developers</div>
</div>
</div>
</div>
<!-- FLOWS TAB -->
<div class="content-section" id="flows">
<div class="flow-container">
<h3 class="flow-title">Flow 1: New Visitor → Creator</h3>
<p class="flow-description">A creator discovers BlackRoad and starts making content</p>
<div class="flow-steps">
<div class="flow-step highlight">blackroad.io/</div>
<span class="flow-arrow"></span>
<span class="flow-action">clicks "Start Creating"</span>
<span class="flow-arrow"></span>
<div class="flow-step">/create</div>
<span class="flow-arrow"></span>
<span class="flow-action">selects "Video"</span>
<span class="flow-arrow"></span>
<div class="flow-step">/create/video</div>
<span class="flow-arrow"></span>
<span class="flow-action">clicks "Try Free"</span>
<span class="flow-arrow"></span>
<div class="flow-step">/login</div>
<span class="flow-arrow"></span>
<div class="flow-step highlight">/studio/video (app)</div>
</div>
</div>
<div class="flow-container">
<h3 class="flow-title">Flow 2: Developer → Ops Starter Client</h3>
<p class="flow-description">A developer with messy repos finds the productized service</p>
<div class="flow-steps">
<div class="flow-step highlight">blackroad.io/</div>
<span class="flow-arrow"></span>
<span class="flow-action">sees "Ops Starter"</span>
<span class="flow-arrow"></span>
<div class="flow-step">blackroadinc.us/</div>
<span class="flow-arrow"></span>
<div class="flow-step">/services/ops-starter</div>
<span class="flow-arrow"></span>
<span class="flow-action">sends email</span>
<span class="flow-arrow"></span>
<div class="flow-step highlight">Intake → Sprint → Delivery</div>
</div>
</div>
<div class="flow-container">
<h3 class="flow-title">Flow 3: Researcher → Research Hub</h3>
<p class="flow-description">Someone interested in the theoretical work and frameworks</p>
<div class="flow-steps">
<div class="flow-step highlight">blackroad.io/</div>
<span class="flow-arrow"></span>
<span class="flow-action">clicks "Learn"</span>
<span class="flow-arrow"></span>
<div class="flow-step">/learn</div>
<span class="flow-arrow"></span>
<div class="flow-step">/learn/research</div>
<span class="flow-arrow"></span>
<span class="flow-action">downloads report</span>
<span class="flow-arrow"></span>
<div class="flow-step highlight">API access request</div>
</div>
</div>
<div class="flow-container">
<h3 class="flow-title">Flow 4: Developer → OS Documentation</h3>
<p class="flow-description">A developer wants to understand and contribute to the OS</p>
<div class="flow-steps">
<div class="flow-step">blackroad.io/</div>
<span class="flow-arrow"></span>
<span class="flow-action">clicks "Docs"</span>
<span class="flow-arrow"></span>
<div class="flow-step highlight">blackroad.systems/</div>
<span class="flow-arrow"></span>
<div class="flow-step">/docs/architecture</div>
<span class="flow-arrow"></span>
<div class="flow-step">/docs/agents</div>
<span class="flow-arrow"></span>
<div class="flow-step highlight">github.com/BlackRoad-OS</div>
</div>
</div>
<div class="flow-container">
<h3 class="flow-title">Flow 5: Enterprise → Consulting</h3>
<p class="flow-description">Enterprise discovers BlackRoad and books a call</p>
<div class="flow-steps">
<div class="flow-step highlight">blackroadinc.us/</div>
<span class="flow-arrow"></span>
<div class="flow-step">/services</div>
<span class="flow-arrow"></span>
<span class="flow-action">clicks "Book a Call"</span>
<span class="flow-arrow"></span>
<div class="flow-step">/book</div>
<span class="flow-arrow"></span>
<div class="flow-step highlight">Discovery call → Proposal</div>
</div>
</div>
<div class="flow-container">
<h3 class="flow-title">Flow 6: AI Enthusiast → Lucidia Ecosystem</h3>
<p class="flow-description">Someone interested in the 1,000 agents project</p>
<div class="flow-steps">
<div class="flow-step">blackroad.io/create/code</div>
<span class="flow-arrow"></span>
<span class="flow-action">sees Lucidia</span>
<span class="flow-arrow"></span>
<div class="flow-step highlight">lucidia.earth/</div>
<span class="flow-arrow"></span>
<div class="flow-step">/agents</div>
<span class="flow-arrow"></span>
<div class="flow-step">/architecture</div>
<span class="flow-arrow"></span>
<div class="flow-step highlight">/contribute</div>
</div>
</div>
</div>
<!-- ORGS TAB -->
<div class="content-section" id="orgs">
<table class="mapping-table">
<thead>
<tr>
<th>GitHub Org</th>
<th>Primary Domain</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>BlackRoad-AI</code></td>
<td>blackroad.io</td>
<td>Main consumer product repos</td>
</tr>
<tr>
<td><code>BlackRoad-OS</code></td>
<td>blackroad.systems</td>
<td>Core OS infrastructure & documentation</td>
</tr>
<tr>
<td><code>BlackRoad-Ventures</code></td>
<td>blackroadinc.us</td>
<td>Business services & consulting</td>
</tr>
<tr>
<td><code>BlackRoad-Cloud</code></td>
<td>blackroad.network</td>
<td>Blockchain infrastructure</td>
</tr>
<tr>
<td><code>BlackRoad-Education</code></td>
<td>blackroad.io/learn</td>
<td>Education products & content</td>
</tr>
<tr>
<td><code>BlackRoad-Media</code></td>
<td>blackroad.io/create/video</td>
<td>Video tools & RoadView</td>
</tr>
<tr>
<td><code>BlackRoad-Interactive</code></td>
<td>blackroad.io/create/games</td>
<td>Game engine & RoadEngine</td>
</tr>
<tr>
<td><code>BlackRoad-Studio</code></td>
<td>lucidia.studio</td>
<td>Creative tools</td>
</tr>
<tr>
<td><code>BlackRoad-Labs</code></td>
<td>blackroadquantum.com</td>
<td>Quantum & research</td>
</tr>
<tr>
<td><code>BlackRoad-Foundation</code></td>
<td>blackroad.io/learn/research</td>
<td>Research publishing</td>
</tr>
<tr>
<td><code>BlackRoad-Security</code></td>
<td>blackroad.network</td>
<td>Security infrastructure</td>
</tr>
<tr>
<td><code>BlackRoad-Hardware</code></td>
<td>lucidia.earth, aliceqi.com</td>
<td>Edge & hardware AI</td>
</tr>
<tr>
<td><code>BlackRoad-Gov</code></td>
<td>(future)</td>
<td>Government contracts</td>
</tr>
<tr>
<td><code>BlackRoad-Archive</code></td>
<td>(internal)</td>
<td>Historical preservation</td>
</tr>
<tr>
<td><code>Blackbox-Enterprises</code></td>
<td>(holding)</td>
<td>Parent entity</td>
</tr>
</tbody>
</table>
</div>
<!-- REPOS TAB -->
<div class="content-section" id="repos">
<table class="mapping-table">
<thead>
<tr>
<th>Repo</th>
<th>Powers Feature</th>
<th>On Domain</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>blackroad-os-web</code></td>
<td>Frontend application</td>
<td>blackroad.io</td>
</tr>
<tr>
<td><code>blackroad-os-api-gateway</code></td>
<td>All API calls</td>
<td>blackroad.systems/api</td>
</tr>
<tr>
<td><code>blackroad-os-prism-console</code></td>
<td>Prism dashboard</td>
<td>blackroad.io/tools/prism</td>
</tr>
<tr>
<td><code>blackroad-os-agents</code></td>
<td>Agent runtime</td>
<td>All agent features</td>
</tr>
<tr>
<td><code>blackroad-os-core</code></td>
<td>Core logic</td>
<td>Everything</td>
</tr>
<tr>
<td><code>blackroad-os-brand</code></td>
<td>Design system</td>
<td>All domains</td>
</tr>
<tr>
<td><code>blackroad-os-docs</code></td>
<td>Documentation</td>
<td>blackroad.systems/docs</td>
</tr>
<tr>
<td><code>blackroad-os-infra</code></td>
<td>Deployment config</td>
<td>Internal</td>
</tr>
<tr>
<td><code>blackroad-os-operator</code></td>
<td>Ops playbooks</td>
<td>blackroadinc.us</td>
</tr>
<tr>
<td><code>blackroad-os-pack-research-lab</code></td>
<td>Research tools</td>
<td>blackroad.io/learn/research</td>
</tr>
<tr>
<td><code>blackroad-os-pack-education</code></td>
<td>Learning features</td>
<td>blackroad.io/learn</td>
</tr>
<tr>
<td><code>blackroad-os-pack-creator-studio</code></td>
<td>Creator tools</td>
<td>blackroad.io/create</td>
</tr>
<tr>
<td><code>blackroad-os-pack-finance</code></td>
<td>Financial tools</td>
<td>blackroadinc.us</td>
</tr>
<tr>
<td><code>blackroad-os-pack-legal</code></td>
<td>Legal automation</td>
<td>blackroad.io/build/legal</td>
</tr>
<tr>
<td><code>blackroad-os-pack-infra-devops</code></td>
<td>DevOps tools</td>
<td>blackroad.systems</td>
</tr>
<tr>
<td><code>blackroad-os-beacon</code></td>
<td>Monitoring</td>
<td>Status pages</td>
</tr>
<tr>
<td><code>blackroad-os-home</code></td>
<td>Landing pages</td>
<td>blackroad.io</td>
</tr>
<tr>
<td><code>blackroad-os-demo</code></td>
<td>Demo instances</td>
<td>Demos</td>
</tr>
<tr>
<td><code>blackroad-os-research</code></td>
<td>Research code</td>
<td>blackroad.io/learn/research</td>
</tr>
<tr>
<td><code>blackroad-os-api</code></td>
<td>API definitions</td>
<td>blackroad.systems/api</td>
</tr>
</tbody>
</table>
</div>
<!-- PRIORITY TAB -->
<div class="content-section" id="priority">
<div class="priority-grid">
<div class="priority-phase">
<h3>Phase 1</h3>
<h4>Revenue Generation (Now)</h4>
<ul class="priority-list">
<li><span class="priority-num">1</span> blackroad.io/ — Main landing ✓</li>
<li><span class="priority-num">2</span> blackroadinc.us/ops-starter</li>
<li><span class="priority-num">3</span> blackroad.io/create</li>
</ul>
</div>
<div class="priority-phase">
<h3>Phase 2</h3>
<h4>Developer Credibility (Week 2-3)</h4>
<ul class="priority-list">
<li><span class="priority-num">4</span> blackroad.systems/</li>
<li><span class="priority-num">5</span> blackroad.systems/docs</li>
<li><span class="priority-num">6</span> blackroad.io/tools/prism</li>
</ul>
</div>
<div class="priority-phase">
<h3>Phase 3</h3>
<h4>Full Product Suite (Week 4-6)</h4>
<ul class="priority-list">
<li><span class="priority-num">7</span> /create/video — RoadView</li>
<li><span class="priority-num">8</span> /create/code — Lucidia</li>
<li><span class="priority-num">9</span> /learn — Education</li>
<li><span class="priority-num">10</span> /learn/research</li>
</ul>
</div>
<div class="priority-phase">
<h3>Phase 4</h3>
<h4>Ecosystem Expansion (Month 2)</h4>
<ul class="priority-list">
<li><span class="priority-num">11</span> lucidia.earth/</li>
<li><span class="priority-num">12</span> blackroad.network/</li>
<li><span class="priority-num">13</span> blackroad.me/</li>
</ul>
</div>
</div>
</div>
<script>
// Tab switching
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
// Remove active from all tabs
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.content-section').forEach(s => s.classList.remove('active'));
// Add active to clicked tab
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,800 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BlackRoad OS — Living Earth</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #000;
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
}
#canvas-container { position: fixed; inset: 0; }
.logo {
position: fixed;
top: 20px;
left: 20px;
display: flex;
align-items: center;
gap: 10px;
background: rgba(0,0,0,0.75);
padding: 10px 18px;
border-radius: 50px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.1);
z-index: 100;
}
.logo-icon {
width: 28px; height: 28px;
background: linear-gradient(135deg, #FF1D6C, #F5A623);
border-radius: 50%;
position: relative;
}
.logo-icon::after {
content: '';
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 10px; height: 10px;
background: #000;
border-radius: 50%;
}
.logo-text { color: #fff; font-weight: 600; font-size: 14px; }
.stats-bar {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 20px;
background: rgba(0,0,0,0.75);
padding: 10px 24px;
border-radius: 50px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.1);
z-index: 100;
}
.stat { display: flex; align-items: center; gap: 6px; }
.stat-icon { font-size: 15px; }
.stat-value { font-size: 13px; font-weight: 600; color: #fff; }
.biome-panel {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.75);
padding: 14px 18px;
border-radius: 16px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.1);
z-index: 100;
max-height: 70vh;
overflow-y: auto;
}
.biome-title {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: rgba(255,255,255,0.4);
margin-bottom: 10px;
}
.biome-list { display: flex; flex-direction: column; gap: 4px; }
.biome-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: rgba(255,255,255,0.8);
}
.biome-dot { width: 8px; height: 8px; border-radius: 50%; }
.biome-count { margin-left: auto; color: rgba(255,255,255,0.5); font-size: 10px; }
.info-panel {
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(0,0,0,0.75);
padding: 16px 20px;
border-radius: 16px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.1);
z-index: 100;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
max-width: 300px;
}
.info-panel.visible { opacity: 1; transform: translateY(0); }
.info-biome { font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 4px; }
.info-name { font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 8px; }
.info-desc { font-size: 12px; color: rgba(255,255,255,0.6); line-height: 1.4; }
.info-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.info-stat { text-align: center; }
.info-stat-icon { font-size: 14px; }
.info-stat-value { font-size: 13px; font-weight: 600; color: #fff; }
.info-stat-label { font-size: 8px; color: rgba(255,255,255,0.4); text-transform: uppercase; }
.controls {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 6px;
z-index: 100;
}
.ctrl-btn {
width: 42px; height: 42px;
border: none; border-radius: 50%;
background: rgba(0,0,0,0.75);
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.1);
color: #fff; font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.ctrl-btn:hover { background: rgba(255,29,108,0.4); transform: scale(1.1); }
.ctrl-btn.active { background: #FF1D6C; border-color: #FF1D6C; }
.fps {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.6);
padding: 6px 14px;
border-radius: 20px;
font-size: 11px;
color: rgba(255,255,255,0.6);
z-index: 100;
}
.loading {
position: fixed;
inset: 0;
background: #000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 0.6s ease;
}
.loading.hidden { opacity: 0; pointer-events: none; }
.loading-spinner {
width: 60px; height: 60px;
border: 3px solid rgba(255,255,255,0.1);
border-top-color: #FF1D6C;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-text { margin-top: 20px; color: rgba(255,255,255,0.7); font-size: 14px; }
</style>
</head>
<body>
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<div class="loading-text">Loading biomes...</div>
</div>
<div id="canvas-container"></div>
<div class="logo">
<div class="logo-icon"></div>
<span class="logo-text">BlackRoad Earth</span>
</div>
<div class="stats-bar">
<div class="stat"><span class="stat-icon">🌳</span><span class="stat-value" id="treeCount">0</span></div>
<div class="stat"><span class="stat-icon">🏠</span><span class="stat-value" id="houseCount">0</span></div>
<div class="stat"><span class="stat-icon">🐾</span><span class="stat-value" id="animalCount">0</span></div>
<div class="stat"><span class="stat-icon">🤖</span><span class="stat-value" id="agentCount">0</span></div>
</div>
<div class="biome-panel">
<div class="biome-title">Biomes</div>
<div class="biome-list" id="biomeList"></div>
</div>
<div class="info-panel" id="infoPanel">
<div class="info-biome" id="infoBiome">Tropical Rainforest</div>
<div class="info-name" id="infoName">Amazon Basin</div>
<div class="info-desc" id="infoDesc">Description...</div>
<div class="info-stats">
<div class="info-stat">
<div class="info-stat-icon">🌳</div>
<div class="info-stat-value" id="infoTrees">0</div>
<div class="info-stat-label">Trees</div>
</div>
<div class="info-stat">
<div class="info-stat-icon">🐾</div>
<div class="info-stat-value" id="infoAnimals">0</div>
<div class="info-stat-label">Animals</div>
</div>
<div class="info-stat">
<div class="info-stat-icon">🌡️</div>
<div class="info-stat-value" id="infoTemp"></div>
<div class="info-stat-label">Temp</div>
</div>
</div>
</div>
<div class="controls">
<button class="ctrl-btn active" id="btnRotate" title="Auto Rotate">🔄</button>
<button class="ctrl-btn active" id="btnClouds" title="Clouds">☁️</button>
<button class="ctrl-btn active" id="btnLife" title="Life">🌿</button>
<button class="ctrl-btn" id="btnNight" title="Night">🌙</button>
</div>
<div class="fps" id="fps">60 FPS</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ============ CONFIG ============
const EARTH_RADIUS = 100;
const QUALITY = {
treesPerRegion: 15, // Reduced from 40
animalsPerRegion: 5, // Reduced from 15
agentsPerRegion: 2, // Reduced from 3
housesPerRegion: 3, // Reduced from 8
animateEveryNthFrame: 2 // Only animate every 2nd frame
};
const TEXTURES = {
earth: 'https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg',
bump: 'https://unpkg.com/three-globe/example/img/earth-topology.png',
clouds: 'https://unpkg.com/three-globe/example/img/earth-clouds.png',
night: 'https://unpkg.com/three-globe/example/img/earth-night.jpg'
};
// Simplified biomes
const BIOMES = {
TROPICAL_RAINFOREST: { name: "Tropical Rainforest", color: 0x1a5a1a, icon: "🌴", temp: "27°C", treeColor: 0x0d5c0d },
TEMPERATE_FOREST: { name: "Temperate Forest", color: 0x2d5a27, icon: "🌲", temp: "12°C", treeColor: 0x228b22 },
BOREAL_TAIGA: { name: "Boreal Taiga", color: 0x1a4a2a, icon: "🌲", temp: "-5°C", treeColor: 0x0a3a0a },
TUNDRA: { name: "Tundra", color: 0x8fbc8f, icon: "❄️", temp: "-20°C", treeColor: 0x556b2f },
DESERT_HOT: { name: "Hot Desert", color: 0xc2b280, icon: "🏜️", temp: "38°C", treeColor: 0x228b22 },
SAVANNA: { name: "Savanna", color: 0xbdb76b, icon: "🦁", temp: "25°C", treeColor: 0x6b8e23 },
GRASSLAND: { name: "Grassland", color: 0x9acd32, icon: "🌾", temp: "15°C", treeColor: 0x7cba3d },
MOUNTAIN: { name: "Mountain", color: 0x696969, icon: "⛰️", temp: "-8°C", treeColor: 0x1a4a1a },
ICE_SHEET: { name: "Polar Ice", color: 0xf0f8ff, icon: "🧊", temp: "-50°C", treeColor: 0xffffff },
WETLAND: { name: "Wetland", color: 0x2f4f4f, icon: "🐊", temp: "22°C", treeColor: 0x2f4f2f }
};
// Reduced regions - key locations only
const BIOME_REGIONS = [
// Rainforests
{ biome: 'TROPICAL_RAINFOREST', name: "Amazon Basin", lat: -3, lng: -60, radius: 18, desc: "World's largest rainforest" },
{ biome: 'TROPICAL_RAINFOREST', name: "Congo Rainforest", lat: 0, lng: 22, radius: 12, desc: "Africa's largest rainforest" },
{ biome: 'TROPICAL_RAINFOREST', name: "Southeast Asia", lat: 2, lng: 110, radius: 10, desc: "Borneo and Sumatra forests" },
// Temperate
{ biome: 'TEMPERATE_FOREST', name: "Eastern USA", lat: 38, lng: -80, radius: 12, desc: "Appalachian forests" },
{ biome: 'TEMPERATE_FOREST', name: "Western Europe", lat: 48, lng: 8, radius: 10, desc: "European woodlands" },
{ biome: 'TEMPERATE_FOREST', name: "East Asia", lat: 36, lng: 138, radius: 8, desc: "Japanese and Korean forests" },
// Boreal
{ biome: 'BOREAL_TAIGA', name: "Canadian Boreal", lat: 58, lng: -100, radius: 22, desc: "World's largest intact forest" },
{ biome: 'BOREAL_TAIGA', name: "Siberian Taiga", lat: 62, lng: 100, radius: 25, desc: "Largest terrestrial biome" },
// Tundra
{ biome: 'TUNDRA', name: "Arctic Tundra", lat: 72, lng: -100, radius: 18, desc: "Frozen treeless plains" },
{ biome: 'TUNDRA', name: "Siberian Tundra", lat: 72, lng: 130, radius: 16, desc: "Northern Russia permafrost" },
// Deserts
{ biome: 'DESERT_HOT', name: "Sahara Desert", lat: 24, lng: 10, radius: 25, desc: "World's largest hot desert" },
{ biome: 'DESERT_HOT', name: "Arabian Desert", lat: 23, lng: 50, radius: 12, desc: "Arabian Peninsula" },
{ biome: 'DESERT_HOT', name: "Australian Outback", lat: -24, lng: 135, radius: 18, desc: "Red heart of Australia" },
// Savanna
{ biome: 'SAVANNA', name: "Serengeti", lat: -3, lng: 35, radius: 10, desc: "Great migration lands" },
{ biome: 'SAVANNA', name: "Brazilian Cerrado", lat: -15, lng: -48, radius: 12, desc: "South American savanna" },
// Grassland
{ biome: 'GRASSLAND', name: "Great Plains", lat: 42, lng: -100, radius: 15, desc: "North American prairie" },
{ biome: 'GRASSLAND', name: "Eurasian Steppe", lat: 48, lng: 65, radius: 20, desc: "Vast grasslands" },
// Mountains
{ biome: 'MOUNTAIN', name: "Himalayas", lat: 28, lng: 85, radius: 12, desc: "World's highest peaks" },
{ biome: 'MOUNTAIN', name: "Rocky Mountains", lat: 45, lng: -110, radius: 10, desc: "North American spine" },
{ biome: 'MOUNTAIN', name: "Andes", lat: -15, lng: -72, radius: 10, desc: "Longest mountain range" },
{ biome: 'MOUNTAIN', name: "European Alps", lat: 46, lng: 10, radius: 6, desc: "Central European peaks" },
// Ice
{ biome: 'ICE_SHEET', name: "Antarctica", lat: -82, lng: 0, radius: 28, desc: "Frozen continent" },
{ biome: 'ICE_SHEET', name: "Greenland", lat: 75, lng: -42, radius: 15, desc: "Arctic ice sheet" },
// Wetlands
{ biome: 'WETLAND', name: "Pantanal", lat: -18, lng: -57, radius: 8, desc: "World's largest wetland" },
{ biome: 'WETLAND', name: "Everglades", lat: 26, lng: -81, radius: 5, desc: "River of grass" }
];
// ============ GLOBALS ============
let scene, camera, renderer;
let earth, clouds, nightLights;
let lifeGroup;
let regionData = [];
let counts = { trees: 0, houses: 0, animals: 0, agents: 0 };
let frameCount = 0;
let lastTime = performance.now();
let fps = 60;
let autoRotate = true;
let showClouds = true;
let showLife = true;
let isNight = false;
let isDragging = false;
let prevMouse = { x: 0, y: 0 };
let spherical = { theta: 0, phi: Math.PI / 3, radius: 250 };
// ============ INIT ============
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000008);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
updateCamera();
renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); // Cap pixel ratio
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Load and build
loadTextures(() => {
createScene();
populateBiomes();
buildUI();
document.getElementById('loading').classList.add('hidden');
animate();
});
// Events
window.addEventListener('resize', onResize);
renderer.domElement.addEventListener('mousedown', e => { isDragging = true; prevMouse = { x: e.clientX, y: e.clientY }; });
renderer.domElement.addEventListener('mousemove', onDrag);
renderer.domElement.addEventListener('mouseup', () => isDragging = false);
renderer.domElement.addEventListener('mouseleave', () => isDragging = false);
renderer.domElement.addEventListener('wheel', e => {
spherical.radius = Math.max(140, Math.min(400, spherical.radius + e.deltaY * 0.15));
updateCamera();
}, { passive: true });
renderer.domElement.addEventListener('click', onClick);
// Touch
renderer.domElement.addEventListener('touchstart', e => {
if (e.touches.length === 1) { isDragging = true; prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY }; }
}, { passive: true });
renderer.domElement.addEventListener('touchmove', e => {
if (!isDragging || e.touches.length !== 1) return;
const dx = e.touches[0].clientX - prevMouse.x;
const dy = e.touches[0].clientY - prevMouse.y;
spherical.theta -= dx * 0.005;
spherical.phi = Math.max(0.3, Math.min(Math.PI - 0.3, spherical.phi + dy * 0.005));
updateCamera();
prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}, { passive: true });
renderer.domElement.addEventListener('touchend', () => isDragging = false);
// Buttons
document.getElementById('btnRotate').onclick = () => { autoRotate = !autoRotate; document.getElementById('btnRotate').classList.toggle('active', autoRotate); };
document.getElementById('btnClouds').onclick = () => { showClouds = !showClouds; clouds.visible = showClouds; document.getElementById('btnClouds').classList.toggle('active', showClouds); };
document.getElementById('btnLife').onclick = () => { showLife = !showLife; lifeGroup.visible = showLife; document.getElementById('btnLife').classList.toggle('active', showLife); };
document.getElementById('btnNight').onclick = () => {
isNight = !isNight;
nightLights.material.opacity = isNight ? 0.8 : 0;
window.sunLight.intensity = isNight ? 0.15 : 1.2;
document.getElementById('btnNight').classList.toggle('active', isNight);
};
}
function loadTextures(callback) {
const loader = new THREE.TextureLoader();
let loaded = 0;
const total = 4;
window.tex = {};
Object.entries(TEXTURES).forEach(([key, url]) => {
loader.load(url, tex => {
window.tex[key] = tex;
loaded++;
if (loaded === total) callback();
});
});
}
function createScene() {
// Stars - simple points
const starGeo = new THREE.BufferGeometry();
const starPos = new Float32Array(3000 * 3);
for (let i = 0; i < 3000; i++) {
const r = 600 + Math.random() * 800;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(Math.random() * 2 - 1);
starPos[i*3] = r * Math.sin(phi) * Math.cos(theta);
starPos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
starPos[i*3+2] = r * Math.cos(phi);
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
scene.add(new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 0.8 })));
// Lights
window.sunLight = new THREE.DirectionalLight(0xffffff, 1.2);
window.sunLight.position.set(300, 150, 300);
scene.add(window.sunLight);
scene.add(new THREE.AmbientLight(0x404060, 0.35));
// Earth
const earthGeo = new THREE.SphereGeometry(EARTH_RADIUS, 64, 64); // Reduced segments
earth = new THREE.Mesh(earthGeo, new THREE.MeshPhongMaterial({
map: window.tex.earth,
bumpMap: window.tex.bump,
bumpScale: 0.3,
shininess: 3
}));
scene.add(earth);
// Night lights
nightLights = new THREE.Mesh(earthGeo.clone(), new THREE.MeshBasicMaterial({
map: window.tex.night,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: 0
}));
nightLights.scale.setScalar(1.002);
scene.add(nightLights);
// Clouds
clouds = new THREE.Mesh(
new THREE.SphereGeometry(EARTH_RADIUS + 1, 48, 48),
new THREE.MeshPhongMaterial({ map: window.tex.clouds, transparent: true, opacity: 0.25, depthWrite: false })
);
scene.add(clouds);
// Atmosphere
scene.add(new THREE.Mesh(
new THREE.SphereGeometry(EARTH_RADIUS + 8, 32, 32),
new THREE.ShaderMaterial({
vertexShader: `varying vec3 vN; void main() { vN = normalize(normalMatrix * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
fragmentShader: `varying vec3 vN; void main() { float i = pow(0.6 - dot(vN, vec3(0,0,1)), 2.0); gl_FragColor = vec4(0.3, 0.6, 1.0, i * 0.35); }`,
blending: THREE.AdditiveBlending,
side: THREE.BackSide,
transparent: true,
depthWrite: false
})
));
// Life container
lifeGroup = new THREE.Group();
scene.add(lifeGroup);
}
// ============ POPULATE ============
function populateBiomes() {
BIOME_REGIONS.forEach(region => {
const biome = BIOMES[region.biome];
const scale = region.radius / 12;
const treesCount = Math.max(1, Math.floor(QUALITY.treesPerRegion * scale * (biome.name.includes('Desert') || biome.name.includes('Ice') ? 0.1 : 1)));
const housesCount = Math.max(0, Math.floor(QUALITY.housesPerRegion * scale * (biome.name.includes('Ice') ? 0.1 : 1)));
const animalsCount = Math.max(1, Math.floor(QUALITY.animalsPerRegion * scale));
const agentsCount = Math.floor(QUALITY.agentsPerRegion * scale);
const regionCounts = { trees: 0, animals: 0, houses: 0, agents: 0 };
// Trees - simple merged geometry approach
for (let i = 0; i < treesCount; i++) {
const tree = createSimpleTree(biome);
placeOnGlobe(tree, region);
lifeGroup.add(tree);
regionCounts.trees++;
counts.trees++;
}
// Houses
for (let i = 0; i < housesCount; i++) {
const house = createSimpleHouse(biome);
placeOnGlobe(house, region);
lifeGroup.add(house);
regionCounts.houses++;
counts.houses++;
}
// Animals - very simple
for (let i = 0; i < animalsCount; i++) {
const animal = createSimpleAnimal(biome);
placeOnGlobe(animal, region);
animal.userData.region = region;
lifeGroup.add(animal);
regionCounts.animals++;
counts.animals++;
}
// Agents
for (let i = 0; i < agentsCount; i++) {
const agent = createSimpleAgent();
placeOnGlobe(agent, region, 1.5);
agent.userData.region = region;
lifeGroup.add(agent);
regionCounts.agents++;
counts.agents++;
}
regionData.push({ ...region, biome, counts: regionCounts });
});
// Update UI counts
document.getElementById('treeCount').textContent = counts.trees;
document.getElementById('houseCount').textContent = counts.houses;
document.getElementById('animalCount').textContent = counts.animals;
document.getElementById('agentCount').textContent = counts.agents;
}
function placeOnGlobe(obj, region, heightOffset = 0) {
const angle = Math.random() * Math.PI * 2;
const dist = Math.random() * region.radius * 0.75;
const lat = region.lat + Math.cos(angle) * dist * 0.5;
const lng = region.lng + Math.sin(angle) * dist * 0.7;
const phi = (90 - lat) * Math.PI / 180;
const theta = (lng + 180) * Math.PI / 180;
const r = EARTH_RADIUS + 0.2 + heightOffset;
obj.position.set(
r * Math.sin(phi) * Math.cos(theta),
r * Math.cos(phi),
r * Math.sin(phi) * Math.sin(theta)
);
const normal = obj.position.clone().normalize();
obj.lookAt(obj.position.clone().add(normal));
obj.rotateX(Math.PI / 2);
obj.rotateY(Math.random() * Math.PI * 2);
obj.userData.lat = lat;
obj.userData.lng = lng;
}
// ============ SIMPLE CREATORS ============
function createSimpleTree(biome) {
const group = new THREE.Group();
if (biome.name.includes('Desert')) {
// Cactus
const mat = new THREE.MeshLambertMaterial({ color: 0x228b22 });
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 0.8, 5), mat);
trunk.position.y = 0.4;
group.add(trunk);
} else if (biome.name.includes('Ice') || biome.name.includes('Tundra')) {
// Shrub
const mat = new THREE.MeshLambertMaterial({ color: 0x556b2f });
const bush = new THREE.Mesh(new THREE.SphereGeometry(0.2, 4, 4), mat);
bush.position.y = 0.15;
group.add(bush);
} else if (biome.name.includes('Savanna')) {
// Acacia
const trunkMat = new THREE.MeshLambertMaterial({ color: 0x4a3728 });
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.08, 1, 5), trunkMat);
trunk.position.y = 0.5;
group.add(trunk);
const canopy = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.6, 0.15, 6), new THREE.MeshLambertMaterial({ color: biome.treeColor }));
canopy.position.y = 1.1;
group.add(canopy);
} else if (biome.name.includes('Boreal') || biome.name.includes('Mountain')) {
// Pine
const trunkMat = new THREE.MeshLambertMaterial({ color: 0x4a3728 });
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.08, 0.6, 5), trunkMat);
trunk.position.y = 0.3;
group.add(trunk);
const cone = new THREE.Mesh(new THREE.ConeGeometry(0.35, 1, 5), new THREE.MeshLambertMaterial({ color: biome.treeColor }));
cone.position.y = 0.9;
group.add(cone);
} else {
// Regular tree
const trunkMat = new THREE.MeshLambertMaterial({ color: 0x4a3728 });
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.1, 0.6, 5), trunkMat);
trunk.position.y = 0.3;
group.add(trunk);
const leaves = new THREE.Mesh(new THREE.IcosahedronGeometry(0.5, 0), new THREE.MeshLambertMaterial({ color: biome.treeColor }));
leaves.position.y = 0.8;
group.add(leaves);
}
group.scale.setScalar(0.4 + Math.random() * 0.2);
return group;
}
function createSimpleHouse(biome) {
const group = new THREE.Group();
const wallColor = biome.name.includes('Desert') ? 0xf5deb3 : 0xfaf8f5;
const roofColor = biome.name.includes('Desert') ? 0xdaa520 : 0xcc4444;
const walls = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.4, 0.5), new THREE.MeshLambertMaterial({ color: wallColor }));
walls.position.y = 0.2;
group.add(walls);
const roof = new THREE.Mesh(new THREE.ConeGeometry(0.45, 0.3, 4), new THREE.MeshLambertMaterial({ color: roofColor }));
roof.position.y = 0.55;
roof.rotation.y = Math.PI / 4;
group.add(roof);
group.scale.setScalar(0.5);
return group;
}
function createSimpleAnimal(biome) {
const group = new THREE.Group();
const color = biome.name.includes('Ice') ? 0xffffff :
biome.name.includes('Savanna') ? 0xd4a574 :
0x8b7355;
const mat = new THREE.MeshLambertMaterial({ color });
const body = new THREE.Mesh(new THREE.SphereGeometry(0.12, 6, 6), mat);
body.scale.set(1, 0.7, 1.2);
group.add(body);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.08, 6, 6), mat);
head.position.set(0, 0.05, 0.12);
group.add(head);
group.scale.setScalar(0.5);
group.userData.hop = Math.random() * Math.PI * 2;
return group;
}
function createSimpleAgent() {
const group = new THREE.Group();
const colors = [0xff1d6c, 0x2979ff, 0xf5a623, 0x9c27b0];
const color = colors[Math.floor(Math.random() * colors.length)];
const body = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 0.25, 6), new THREE.MeshLambertMaterial({ color }));
body.position.y = 0.12;
group.add(body);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 8), new THREE.MeshLambertMaterial({ color: 0xffffff }));
head.position.y = 0.32;
group.add(head);
group.scale.setScalar(0.6);
group.userData.hover = Math.random() * Math.PI * 2;
return group;
}
// ============ UI ============
function buildUI() {
const list = document.getElementById('biomeList');
const biomeCounts = {};
BIOME_REGIONS.forEach(r => { biomeCounts[r.biome] = (biomeCounts[r.biome] || 0) + 1; });
Object.entries(BIOMES).forEach(([key, biome]) => {
if (!biomeCounts[key]) return;
const item = document.createElement('div');
item.className = 'biome-item';
item.innerHTML = `<div class="biome-dot" style="background:#${biome.color.toString(16).padStart(6,'0')}"></div>
<span>${biome.icon} ${biome.name}</span>
<span class="biome-count">${biomeCounts[key]}</span>`;
list.appendChild(item);
});
}
function showInfo(region) {
document.getElementById('infoBiome').textContent = region.biome.icon + ' ' + region.biome.name;
document.getElementById('infoBiome').style.color = '#' + region.biome.color.toString(16).padStart(6, '0');
document.getElementById('infoName').textContent = region.name;
document.getElementById('infoDesc').textContent = region.desc;
document.getElementById('infoTrees').textContent = region.counts.trees;
document.getElementById('infoAnimals').textContent = region.counts.animals;
document.getElementById('infoTemp').textContent = region.biome.temp;
document.getElementById('infoPanel').classList.add('visible');
clearTimeout(window.infoTimeout);
window.infoTimeout = setTimeout(() => document.getElementById('infoPanel').classList.remove('visible'), 4000);
}
// ============ EVENTS ============
function updateCamera() {
camera.position.x = spherical.radius * Math.sin(spherical.phi) * Math.sin(spherical.theta);
camera.position.y = spherical.radius * Math.cos(spherical.phi);
camera.position.z = spherical.radius * Math.sin(spherical.phi) * Math.cos(spherical.theta);
camera.lookAt(0, 0, 0);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDrag(e) {
if (!isDragging) return;
const dx = e.clientX - prevMouse.x;
const dy = e.clientY - prevMouse.y;
spherical.theta -= dx * 0.005;
spherical.phi = Math.max(0.3, Math.min(Math.PI - 0.3, spherical.phi + dy * 0.005));
updateCamera();
prevMouse = { x: e.clientX, y: e.clientY };
}
function onClick(e) {
const mouse = new THREE.Vector2(
(e.clientX / window.innerWidth) * 2 - 1,
-(e.clientY / window.innerHeight) * 2 + 1
);
const ray = new THREE.Raycaster();
ray.setFromCamera(mouse, camera);
const hits = ray.intersectObject(earth);
if (hits.length > 0) {
const p = hits[0].point.normalize().multiplyScalar(EARTH_RADIUS);
const lat = 90 - Math.acos(p.y / EARTH_RADIUS) * 180 / Math.PI;
const lng = Math.atan2(p.z, p.x) * 180 / Math.PI - 180;
let nearest = null, minDist = Infinity;
regionData.forEach(r => {
const d = Math.sqrt((lat - r.lat) ** 2 + (lng - r.lng) ** 2);
if (d < r.radius && d < minDist) { minDist = d; nearest = r; }
});
if (nearest) showInfo(nearest);
}
}
// ============ ANIMATION ============
function animate() {
requestAnimationFrame(animate);
frameCount++;
// FPS counter
if (frameCount % 30 === 0) {
const now = performance.now();
fps = Math.round(30000 / (now - lastTime));
lastTime = now;
document.getElementById('fps').textContent = fps + ' FPS';
}
// Rotate earth slowly
earth.rotation.y += 0.0002;
nightLights.rotation.y = earth.rotation.y;
clouds.rotation.y += 0.00025;
lifeGroup.rotation.y = earth.rotation.y;
// Auto rotate camera
if (autoRotate && !isDragging) {
spherical.theta += 0.0008;
updateCamera();
}
// Animate life only every Nth frame
if (frameCount % QUALITY.animateEveryNthFrame === 0) {
lifeGroup.children.forEach(obj => {
if (obj.userData.hop !== undefined) {
obj.userData.hop += 0.1;
}
if (obj.userData.hover !== undefined) {
obj.userData.hover += 0.05;
// Slight bobbing
obj.position.y += Math.sin(obj.userData.hover) * 0.002;
}
});
}
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,955 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BlackRoad Earth | Street Level</title>
<script src="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css" rel="stylesheet" />
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Exo+2:wght@300;400;600&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #000;
font-family: 'Exo 2', sans-serif;
color: #fff;
}
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.maplibregl-ctrl-attrib {
display: none !important;
}
.ui-overlay {
position: fixed;
z-index: 100;
pointer-events: none;
}
.ui-overlay > * { pointer-events: auto; }
.header {
top: 0;
left: 0;
right: 0;
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(to bottom, rgba(0,0,0,0.9) 0%, transparent 100%);
}
.logo {
font-family: 'Orbitron', sans-serif;
font-size: 20px;
font-weight: 900;
letter-spacing: 3px;
background: linear-gradient(135deg, #00d4ff 0%, #7b2ff7 50%, #ff6b35 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-sub {
font-size: 9px;
letter-spacing: 2px;
opacity: 0.5;
margin-top: 2px;
}
.stats {
display: flex;
gap: 24px;
}
.stat {
text-align: center;
}
.stat-value {
font-family: 'Orbitron', sans-serif;
font-size: 16px;
font-weight: 700;
color: #00d4ff;
}
.stat-value.highlight {
color: #ff6b35;
}
.stat-label {
font-size: 8px;
letter-spacing: 2px;
text-transform: uppercase;
opacity: 0.5;
margin-top: 2px;
}
.search-container {
top: 75px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
}
.search-input {
width: 350px;
padding: 14px 24px;
background: rgba(10, 15, 30, 0.95);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 30px;
color: #fff;
font-family: 'Exo 2', sans-serif;
font-size: 14px;
outline: none;
backdrop-filter: blur(10px);
}
.search-input::placeholder {
color: rgba(255,255,255,0.4);
}
.search-input:focus {
border-color: #00d4ff;
box-shadow: 0 0 30px rgba(0, 212, 255, 0.3);
}
.search-btn {
padding: 14px 24px;
background: linear-gradient(135deg, #00d4ff, #7b2ff7);
border: none;
border-radius: 30px;
color: #fff;
font-family: 'Orbitron', sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 2px;
cursor: pointer;
transition: all 0.3s;
}
.search-btn:hover {
transform: scale(1.05);
box-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
}
.info-panel {
top: 140px;
right: 20px;
width: 280px;
padding: 20px;
background: rgba(10, 15, 30, 0.95);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 16px;
backdrop-filter: blur(20px);
}
.info-section {
margin-bottom: 16px;
}
.info-section:last-child { margin-bottom: 0; }
.info-label {
font-size: 9px;
letter-spacing: 2px;
text-transform: uppercase;
opacity: 0.5;
margin-bottom: 6px;
}
.info-value {
font-family: 'Orbitron', sans-serif;
font-size: 13px;
color: #00d4ff;
}
.info-value.large {
font-size: 18px;
}
.zoom-panel {
top: 140px;
left: 20px;
width: 220px;
padding: 20px;
background: rgba(10, 15, 30, 0.95);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 16px;
backdrop-filter: blur(20px);
}
.zoom-title {
font-family: 'Orbitron', sans-serif;
font-size: 10px;
letter-spacing: 2px;
opacity: 0.6;
margin-bottom: 16px;
}
.zoom-bar {
height: 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
}
.zoom-fill {
height: 100%;
background: linear-gradient(90deg, #00d4ff, #7b2ff7, #ff6b35);
border-radius: 4px;
transition: width 0.2s;
}
.zoom-labels {
display: flex;
justify-content: space-between;
font-size: 9px;
opacity: 0.4;
margin-bottom: 16px;
}
.scale-list {
border-top: 1px solid rgba(255,255,255,0.1);
padding-top: 16px;
}
.scale-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
font-size: 11px;
opacity: 0.4;
transition: all 0.2s;
}
.scale-item.active {
opacity: 1;
color: #00d4ff;
}
.scale-icon {
width: 20px;
text-align: center;
}
.layer-panel {
bottom: 100px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 4px;
padding: 6px;
background: rgba(10, 15, 30, 0.95);
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 25px;
backdrop-filter: blur(20px);
}
.layer-btn {
padding: 10px 18px;
background: transparent;
border: none;
color: rgba(255,255,255,0.5);
font-family: 'Exo 2', sans-serif;
font-size: 11px;
cursor: pointer;
border-radius: 20px;
transition: all 0.2s;
}
.layer-btn:hover {
color: #fff;
background: rgba(255,255,255,0.1);
}
.layer-btn.active {
background: linear-gradient(135deg, #00d4ff, #7b2ff7);
color: #fff;
}
.controls {
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 6px;
padding: 10px 18px;
background: rgba(10, 15, 30, 0.95);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 30px;
backdrop-filter: blur(20px);
}
.ctrl-btn {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid rgba(0, 212, 255, 0.3);
background: rgba(0, 212, 255, 0.1);
color: #00d4ff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.2s;
}
.ctrl-btn:hover {
background: rgba(0, 212, 255, 0.3);
transform: scale(1.1);
box-shadow: 0 0 20px rgba(0, 212, 255, 0.4);
}
.ctrl-btn.active {
background: #00d4ff;
color: #000;
}
.divider {
width: 1px;
background: rgba(255,255,255,0.15);
margin: 0 8px;
}
.quick-locations {
bottom: 100px;
right: 20px;
display: flex;
flex-direction: column;
gap: 6px;
}
.quick-btn {
padding: 10px 16px;
background: rgba(10, 15, 30, 0.9);
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 20px;
color: rgba(255,255,255,0.7);
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
text-align: left;
}
.quick-btn:hover {
background: rgba(0, 212, 255, 0.2);
border-color: #00d4ff;
color: #fff;
}
.quick-btn span {
opacity: 0.5;
font-size: 9px;
margin-left: 8px;
}
.instructions {
bottom: 30px;
left: 20px;
font-size: 10px;
opacity: 0.35;
line-height: 2;
}
.instructions kbd {
padding: 3px 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
font-family: 'Orbitron', sans-serif;
font-size: 9px;
}
.crosshair {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
opacity: 0.3;
}
.crosshair::before,
.crosshair::after {
content: '';
position: absolute;
background: #00d4ff;
}
.crosshair::before {
width: 20px;
height: 2px;
left: -10px;
top: -1px;
}
.crosshair::after {
width: 2px;
height: 20px;
left: -1px;
top: -10px;
}
.globe-mode-indicator {
position: fixed;
top: 75px;
left: 20px;
padding: 8px 16px;
background: rgba(0, 212, 255, 0.2);
border: 1px solid rgba(0, 212, 255, 0.4);
border-radius: 20px;
font-size: 10px;
font-family: 'Orbitron', sans-serif;
letter-spacing: 1px;
display: flex;
align-items: center;
gap: 8px;
}
.mode-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #00d4ff;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</head>
<body>
<div id="map"></div>
<div class="crosshair"></div>
<!-- Header -->
<div class="ui-overlay header">
<div>
<div class="logo">BLACKROAD EARTH</div>
<div class="logo-sub">STREET-LEVEL EXPLORATION</div>
</div>
<div class="stats">
<div class="stat">
<div class="stat-value highlight" id="zoom-level">1.0</div>
<div class="stat-label">Zoom Level</div>
</div>
<div class="stat">
<div class="stat-value" id="altitude">10,000 km</div>
<div class="stat-label">Altitude</div>
</div>
<div class="stat">
<div class="stat-value" id="resolution">10 km/px</div>
<div class="stat-label">Resolution</div>
</div>
<div class="stat">
<div class="stat-value" id="coordinates">0°, 0°</div>
<div class="stat-label">Center</div>
</div>
</div>
</div>
<div class="globe-mode-indicator">
<div class="mode-dot"></div>
<span id="view-mode">GLOBE VIEW</span>
</div>
<!-- Search -->
<div class="ui-overlay search-container">
<input type="text" class="search-input" id="search" placeholder="Search any location... (Times Square, Eiffel Tower, 123 Main St)">
<button class="search-btn" id="search-btn">FLY TO</button>
</div>
<!-- Zoom Panel -->
<div class="ui-overlay zoom-panel">
<div class="zoom-title">DETAIL LEVEL</div>
<div class="zoom-bar">
<div class="zoom-fill" id="zoom-fill" style="width: 5%"></div>
</div>
<div class="zoom-labels">
<span>Space</span>
<span>Street</span>
</div>
<div class="scale-list">
<div class="scale-item active" data-min="0" data-max="3">
<span class="scale-icon">🌍</span>
<span>Planet View</span>
</div>
<div class="scale-item" data-min="3" data-max="6">
<span class="scale-icon">🗺️</span>
<span>Continent</span>
</div>
<div class="scale-item" data-min="6" data-max="10">
<span class="scale-icon">🏔️</span>
<span>Region / Country</span>
</div>
<div class="scale-item" data-min="10" data-max="14">
<span class="scale-icon">🏙️</span>
<span>City</span>
</div>
<div class="scale-item" data-min="14" data-max="17">
<span class="scale-icon">🏘️</span>
<span>Neighborhood</span>
</div>
<div class="scale-item" data-min="17" data-max="20">
<span class="scale-icon">🏠</span>
<span>Street Level</span>
</div>
<div class="scale-item" data-min="20" data-max="24">
<span class="scale-icon">🚶</span>
<span>Building Detail</span>
</div>
</div>
</div>
<!-- Info Panel -->
<div class="ui-overlay info-panel">
<div class="info-section">
<div class="info-label">Location</div>
<div class="info-value large" id="location-name">Earth</div>
</div>
<div class="info-section">
<div class="info-label">Latitude</div>
<div class="info-value" id="lat">0.000000°</div>
</div>
<div class="info-section">
<div class="info-label">Longitude</div>
<div class="info-value" id="lng">0.000000°</div>
</div>
<div class="info-section">
<div class="info-label">Tile Coordinates</div>
<div class="info-value" id="tile-coords">z0 / x0 / y0</div>
</div>
</div>
<!-- Layer Selection -->
<div class="ui-overlay layer-panel">
<button class="layer-btn active" data-style="satellite">🛰️ Satellite</button>
<button class="layer-btn" data-style="streets">🗺️ Streets</button>
<button class="layer-btn" data-style="dark">🌙 Dark</button>
<button class="layer-btn" data-style="terrain">🏔️ Terrain</button>
<button class="layer-btn" data-style="hybrid">🔀 Hybrid</button>
</div>
<!-- Quick Locations -->
<div class="ui-overlay quick-locations">
<button class="quick-btn" data-lat="40.7580" data-lng="-73.9855" data-zoom="18">Times Square <span>NYC</span></button>
<button class="quick-btn" data-lat="48.8584" data-lng="2.2945" data-zoom="18">Eiffel Tower <span>Paris</span></button>
<button class="quick-btn" data-lat="35.6595" data-lng="139.7004" data-zoom="18">Shibuya Crossing <span>Tokyo</span></button>
<button class="quick-btn" data-lat="51.5007" data-lng="-0.1246" data-zoom="18">Big Ben <span>London</span></button>
<button class="quick-btn" data-lat="40.6892" data-lng="-74.0445" data-zoom="17">Statue of Liberty <span>NYC</span></button>
<button class="quick-btn" data-lat="37.8199" data-lng="-122.4783" data-zoom="16">Golden Gate <span>SF</span></button>
</div>
<!-- Controls -->
<div class="ui-overlay controls">
<button class="ctrl-btn" id="btn-home" title="Home">🏠</button>
<button class="ctrl-btn" id="btn-north" title="Reset North">🧭</button>
<div class="divider"></div>
<button class="ctrl-btn" id="btn-zoom-in" title="Zoom In">+</button>
<button class="ctrl-btn" id="btn-zoom-out" title="Zoom Out"></button>
<div class="divider"></div>
<button class="ctrl-btn" id="btn-3d" title="3D Buildings">🏢</button>
<button class="ctrl-btn" id="btn-globe" title="Globe View">🌐</button>
<div class="divider"></div>
<button class="ctrl-btn" id="btn-locate" title="My Location">📍</button>
</div>
<!-- Instructions -->
<div class="ui-overlay instructions">
<div><kbd>DRAG</kbd> Pan / Rotate</div>
<div><kbd>SCROLL</kbd> Zoom</div>
<div><kbd>CTRL+DRAG</kbd> Tilt</div>
<div><kbd>RIGHT-CLICK</kbd> Rotate</div>
</div>
<script>
// Map styles - using free tile providers
const STYLES = {
satellite: {
version: 8,
name: 'Satellite',
sources: {
'satellite': {
type: 'raster',
tiles: [
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
],
tileSize: 256,
maxzoom: 19,
attribution: '© Esri'
}
},
layers: [{
id: 'satellite-layer',
type: 'raster',
source: 'satellite',
minzoom: 0,
maxzoom: 22
}],
glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'
},
streets: {
version: 8,
name: 'Streets',
sources: {
'osm': {
type: 'raster',
tiles: [
'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png'
],
tileSize: 256,
maxzoom: 19,
attribution: '© OpenStreetMap'
}
},
layers: [{
id: 'osm-layer',
type: 'raster',
source: 'osm'
}],
glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'
},
dark: {
version: 8,
name: 'Dark',
sources: {
'carto-dark': {
type: 'raster',
tiles: [
'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
'https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
'https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'
],
tileSize: 256,
maxzoom: 20,
attribution: '© CARTO'
}
},
layers: [{
id: 'carto-layer',
type: 'raster',
source: 'carto-dark'
}],
glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'
},
terrain: {
version: 8,
name: 'Terrain',
sources: {
'stamen': {
type: 'raster',
tiles: [
'https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.jpg'
],
tileSize: 256,
maxzoom: 18,
attribution: '© Stadia Maps'
}
},
layers: [{
id: 'stamen-layer',
type: 'raster',
source: 'stamen'
}],
glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'
},
hybrid: {
version: 8,
name: 'Hybrid',
sources: {
'hybrid': {
type: 'raster',
tiles: [
'https://mt0.google.com/vt/lyrs=y&x={x}&y={y}&z={z}',
'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}'
],
tileSize: 256,
maxzoom: 21,
attribution: '© Google'
}
},
layers: [{
id: 'hybrid-layer',
type: 'raster',
source: 'hybrid'
}],
glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'
}
};
let map;
let currentStyle = 'satellite';
let show3DBuildings = false;
function init() {
map = new maplibregl.Map({
container: 'map',
style: STYLES.satellite,
center: [0, 20],
zoom: 1.5,
minZoom: 0,
maxZoom: 22,
projection: 'globe',
antialias: true
});
// Set atmosphere/fog for globe view
map.on('style.load', () => {
map.setFog({
color: 'rgb(186, 210, 235)',
'high-color': 'rgb(36, 92, 223)',
'horizon-blend': 0.02,
'space-color': 'rgb(5, 5, 15)',
'star-intensity': 0.6
});
});
map.on('move', updateUI);
map.on('zoom', updateUI);
map.on('moveend', updateUI);
setupControls();
setupSearch();
setupLayers();
setupQuickLocations();
updateUI();
}
function updateUI() {
const zoom = map.getZoom();
const center = map.getCenter();
// Update zoom display
document.getElementById('zoom-level').textContent = zoom.toFixed(1);
// Update zoom bar
const zoomPercent = Math.min(100, (zoom / 22) * 100);
document.getElementById('zoom-fill').style.width = zoomPercent + '%';
// Update altitude estimate
const altitude = getAltitudeFromZoom(zoom);
document.getElementById('altitude').textContent = formatAltitude(altitude);
// Update resolution
const resolution = getResolutionFromZoom(zoom);
document.getElementById('resolution').textContent = resolution;
// Update coordinates
document.getElementById('coordinates').textContent =
`${center.lat.toFixed(2)}°, ${center.lng.toFixed(2)}°`;
document.getElementById('lat').textContent =
`${Math.abs(center.lat).toFixed(6)}° ${center.lat >= 0 ? 'N' : 'S'}`;
document.getElementById('lng').textContent =
`${Math.abs(center.lng).toFixed(6)}° ${center.lng >= 0 ? 'E' : 'W'}`;
// Update tile coords
const tileX = Math.floor((center.lng + 180) / 360 * Math.pow(2, Math.floor(zoom)));
const tileY = Math.floor((1 - Math.log(Math.tan(center.lat * Math.PI / 180) +
1 / Math.cos(center.lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, Math.floor(zoom)));
document.getElementById('tile-coords').textContent =
`z${Math.floor(zoom)} / x${tileX} / y${tileY}`;
// Update scale indicators
document.querySelectorAll('.scale-item').forEach(item => {
const min = parseFloat(item.dataset.min);
const max = parseFloat(item.dataset.max);
item.classList.toggle('active', zoom >= min && zoom < max);
});
// Update view mode
document.getElementById('view-mode').textContent =
zoom < 5 ? 'GLOBE VIEW' : zoom < 14 ? 'MAP VIEW' : 'STREET VIEW';
}
function getAltitudeFromZoom(zoom) {
// Approximate altitude in meters based on zoom level
return 40075000 / Math.pow(2, zoom);
}
function formatAltitude(meters) {
if (meters > 1000000) return (meters / 1000000).toFixed(1) + ' Mm';
if (meters > 1000) return (meters / 1000).toFixed(1) + ' km';
if (meters > 1) return meters.toFixed(0) + ' m';
return (meters * 100).toFixed(0) + ' cm';
}
function getResolutionFromZoom(zoom) {
const metersPerPixel = 40075000 * Math.cos(map.getCenter().lat * Math.PI / 180) /
(Math.pow(2, zoom) * 256);
if (metersPerPixel > 1000) return `~${(metersPerPixel/1000).toFixed(0)} km/px`;
if (metersPerPixel > 1) return `~${metersPerPixel.toFixed(1)} m/px`;
return `~${(metersPerPixel * 100).toFixed(0)} cm/px`;
}
function setupControls() {
document.getElementById('btn-home').addEventListener('click', () => {
map.flyTo({ center: [0, 20], zoom: 1.5, pitch: 0, bearing: 0, duration: 2000 });
});
document.getElementById('btn-north').addEventListener('click', () => {
map.easeTo({ bearing: 0, pitch: 0, duration: 500 });
});
document.getElementById('btn-zoom-in').addEventListener('click', () => {
map.zoomIn({ duration: 300 });
});
document.getElementById('btn-zoom-out').addEventListener('click', () => {
map.zoomOut({ duration: 300 });
});
document.getElementById('btn-3d').addEventListener('click', function() {
this.classList.toggle('active');
if (this.classList.contains('active')) {
map.easeTo({ pitch: 60, duration: 500 });
} else {
map.easeTo({ pitch: 0, duration: 500 });
}
});
document.getElementById('btn-globe').addEventListener('click', function() {
map.flyTo({ center: [0, 20], zoom: 1.5, pitch: 0, bearing: 0, duration: 2000 });
});
document.getElementById('btn-locate').addEventListener('click', () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((pos) => {
map.flyTo({
center: [pos.coords.longitude, pos.coords.latitude],
zoom: 17,
duration: 2000
});
document.getElementById('location-name').textContent = 'Your Location';
});
}
});
}
function setupSearch() {
const searchInput = document.getElementById('search');
const searchBtn = document.getElementById('search-btn');
const doSearch = async () => {
const query = searchInput.value.trim();
if (!query) return;
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1`
);
const data = await response.json();
if (data && data.length > 0) {
const result = data[0];
const lat = parseFloat(result.lat);
const lon = parseFloat(result.lon);
// Determine zoom based on type
let zoom = 17;
if (result.type === 'country') zoom = 5;
else if (result.type === 'state') zoom = 7;
else if (result.type === 'city' || result.type === 'town') zoom = 12;
else if (result.type === 'suburb' || result.type === 'neighbourhood') zoom = 15;
map.flyTo({
center: [lon, lat],
zoom: zoom,
pitch: zoom > 15 ? 45 : 0,
duration: 2500
});
document.getElementById('location-name').textContent =
result.display_name.split(',')[0];
} else {
document.getElementById('location-name').textContent = 'Not found';
}
} catch (err) {
console.error('Search error:', err);
}
};
searchBtn.addEventListener('click', doSearch);
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') doSearch();
});
}
function setupLayers() {
document.querySelectorAll('.layer-btn').forEach(btn => {
btn.addEventListener('click', () => {
const styleName = btn.dataset.style;
if (STYLES[styleName]) {
// Save current position
const center = map.getCenter();
const zoom = map.getZoom();
const pitch = map.getPitch();
const bearing = map.getBearing();
map.setStyle(STYLES[styleName]);
// Restore position after style loads
map.once('style.load', () => {
map.jumpTo({ center, zoom, pitch, bearing });
map.setFog({
color: styleName === 'dark' ? 'rgb(20, 20, 30)' : 'rgb(186, 210, 235)',
'high-color': styleName === 'dark' ? 'rgb(10, 20, 50)' : 'rgb(36, 92, 223)',
'horizon-blend': 0.02,
'space-color': 'rgb(5, 5, 15)',
'star-intensity': 0.6
});
});
document.querySelectorAll('.layer-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentStyle = styleName;
}
});
});
}
function setupQuickLocations() {
document.querySelectorAll('.quick-btn').forEach(btn => {
btn.addEventListener('click', () => {
const lat = parseFloat(btn.dataset.lat);
const lng = parseFloat(btn.dataset.lng);
const zoom = parseFloat(btn.dataset.zoom);
map.flyTo({
center: [lng, lat],
zoom: zoom,
pitch: 60,
bearing: Math.random() * 60 - 30,
duration: 3000
});
document.getElementById('location-name').textContent =
btn.textContent.split(' ')[0] + ' ' + (btn.textContent.split(' ')[1] || '');
});
});
}
// Initialize
init();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,629 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BlackRoad World</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #000;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
#info {
position: fixed;
top: 20px;
left: 20px;
color: white;
background: rgba(0,0,0,0.7);
padding: 15px 20px;
border-radius: 12px;
backdrop-filter: blur(10px);
z-index: 100;
}
#info h1 {
font-size: 18px;
margin-bottom: 8px;
background: linear-gradient(90deg, #FF1D6C, #F5A623);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
#info p {
font-size: 12px;
opacity: 0.7;
}
#stats {
position: fixed;
top: 20px;
right: 20px;
color: white;
background: rgba(0,0,0,0.7);
padding: 15px 20px;
border-radius: 12px;
backdrop-filter: blur(10px);
z-index: 100;
font-size: 13px;
}
.stat-row {
display: flex;
justify-content: space-between;
gap: 20px;
padding: 4px 0;
}
.stat-label { opacity: 0.6; }
.stat-value { font-weight: 600; }
#controls {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
gap: 8px;
z-index: 100;
}
.btn {
width: 44px;
height: 44px;
border: none;
border-radius: 50%;
background: rgba(0,0,0,0.7);
color: white;
font-size: 18px;
cursor: pointer;
backdrop-filter: blur(10px);
transition: all 0.2s;
}
.btn:hover { background: #FF1D6C; transform: scale(1.1); }
.btn.active { background: #FF1D6C; }
#biome-info {
position: fixed;
bottom: 20px;
left: 20px;
color: white;
background: rgba(0,0,0,0.8);
padding: 16px 20px;
border-radius: 12px;
backdrop-filter: blur(10px);
z-index: 100;
max-width: 280px;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s;
}
#biome-info.visible {
opacity: 1;
transform: translateY(0);
}
#biome-info h2 {
font-size: 16px;
margin-bottom: 6px;
}
#biome-info p {
font-size: 12px;
opacity: 0.7;
line-height: 1.5;
}
</style>
</head>
<body>
<div id="info">
<h1>🌍 BlackRoad World</h1>
<p>Drag to rotate • Scroll to zoom • Click regions</p>
</div>
<div id="stats">
<div class="stat-row">
<span class="stat-label">Biomes</span>
<span class="stat-value">10</span>
</div>
<div class="stat-row">
<span class="stat-label">Regions</span>
<span class="stat-value">50</span>
</div>
<div class="stat-row">
<span class="stat-label">Trees</span>
<span class="stat-value" id="treeCount">0</span>
</div>
<div class="stat-row">
<span class="stat-label">FPS</span>
<span class="stat-value" id="fps">60</span>
</div>
</div>
<div id="controls">
<button class="btn active" id="btnRotate">🔄</button>
<button class="btn active" id="btnClouds">☁️</button>
<button class="btn" id="btnNight">🌙</button>
</div>
<div id="biome-info">
<h2 id="biomeName">🌴 Tropical Rainforest</h2>
<p id="biomeDesc">The Amazon Basin - world's largest rainforest</p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function() {
// === CONFIG ===
const RADIUS = 100;
// === BIOMES ===
const BIOMES = [
// Rainforests
{ name: "Amazon Rainforest", lat: -3, lng: -60, r: 15, type: "🌴 Tropical Rainforest", color: 0x0d5c0d, desc: "World's largest rainforest, containing 10% of all species on Earth" },
{ name: "Congo Basin", lat: 0, lng: 22, r: 12, type: "🌴 Tropical Rainforest", color: 0x0d5c0d, desc: "Africa's largest rainforest and the second largest in the world" },
{ name: "Borneo Rainforest", lat: 1, lng: 115, r: 8, type: "🌴 Tropical Rainforest", color: 0x0d5c0d, desc: "Ancient rainforest home to orangutans and pygmy elephants" },
// Temperate Forests
{ name: "Pacific Northwest", lat: 47, lng: -122, r: 8, type: "🌲 Temperate Forest", color: 0x228b22, desc: "Misty forests of giant redwoods and Douglas firs" },
{ name: "European Forest", lat: 50, lng: 10, r: 10, type: "🌲 Temperate Forest", color: 0x228b22, desc: "Ancient woodlands spanning Germany, France and Poland" },
{ name: "East Asian Forest", lat: 35, lng: 138, r: 7, type: "🌲 Temperate Forest", color: 0x228b22, desc: "Japanese forests famous for cherry blossoms and maples" },
{ name: "Appalachian Forest", lat: 37, lng: -80, r: 8, type: "🌲 Temperate Forest", color: 0x228b22, desc: "Ancient mountains with stunning fall foliage" },
// Boreal/Taiga
{ name: "Canadian Boreal", lat: 58, lng: -100, r: 20, type: "🌲 Boreal Taiga", color: 0x1a4a1a, desc: "World's largest intact forest ecosystem" },
{ name: "Siberian Taiga", lat: 62, lng: 100, r: 25, type: "🌲 Boreal Taiga", color: 0x1a4a1a, desc: "Largest terrestrial biome, spanning Russia" },
{ name: "Scandinavian Taiga", lat: 64, lng: 20, r: 10, type: "🌲 Boreal Taiga", color: 0x1a4a1a, desc: "Northern forests of Norway, Sweden, Finland" },
// Tundra
{ name: "Arctic Tundra", lat: 72, lng: -100, r: 18, type: "❄️ Tundra", color: 0x8fbc8f, desc: "Frozen treeless plains with permafrost" },
{ name: "Siberian Tundra", lat: 72, lng: 140, r: 16, type: "❄️ Tundra", color: 0x8fbc8f, desc: "Vast frozen expanse of northern Russia" },
// Deserts
{ name: "Sahara Desert", lat: 24, lng: 10, r: 22, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "World's largest hot desert, size of the USA" },
{ name: "Arabian Desert", lat: 23, lng: 50, r: 12, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "Sandy deserts of the Arabian Peninsula" },
{ name: "Australian Outback", lat: -24, lng: 135, r: 18, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "Red desert heart of Australia" },
{ name: "Gobi Desert", lat: 43, lng: 105, r: 12, type: "🏔️ Cold Desert", color: 0xa09070, desc: "Cold desert spanning Mongolia and China" },
{ name: "Atacama Desert", lat: -24, lng: -69, r: 6, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "Driest place on Earth" },
{ name: "Sonoran Desert", lat: 32, lng: -112, r: 7, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "Home to the iconic saguaro cactus" },
// Savanna
{ name: "Serengeti", lat: -3, lng: 35, r: 10, type: "🦁 Savanna", color: 0xbdb76b, desc: "Famous for the great wildebeest migration" },
{ name: "Brazilian Cerrado", lat: -15, lng: -48, r: 12, type: "🦁 Savanna", color: 0xbdb76b, desc: "World's most biodiverse savanna" },
{ name: "Australian Savanna", lat: -16, lng: 135, r: 12, type: "🦁 Savanna", color: 0xbdb76b, desc: "Tropical woodlands of northern Australia" },
// Grasslands
{ name: "Great Plains", lat: 42, lng: -100, r: 14, type: "🌾 Grassland", color: 0x9acd32, desc: "North American prairie, once home to millions of bison" },
{ name: "Eurasian Steppe", lat: 48, lng: 65, r: 18, type: "🌾 Grassland", color: 0x9acd32, desc: "Vast grasslands from Ukraine to Mongolia" },
{ name: "Pampas", lat: -35, lng: -62, r: 10, type: "🌾 Grassland", color: 0x9acd32, desc: "Fertile grasslands of Argentina" },
// Mountains
{ name: "Himalayas", lat: 28, lng: 85, r: 12, type: "⛰️ Mountain", color: 0x696969, desc: "World's highest peaks including Mount Everest" },
{ name: "Rocky Mountains", lat: 45, lng: -110, r: 10, type: "⛰️ Mountain", color: 0x696969, desc: "North America's great mountain spine" },
{ name: "Andes Mountains", lat: -20, lng: -68, r: 12, type: "⛰️ Mountain", color: 0x696969, desc: "World's longest continental mountain range" },
{ name: "European Alps", lat: 46, lng: 10, r: 6, type: "⛰️ Mountain", color: 0x696969, desc: "Iconic peaks of central Europe" },
{ name: "Tibetan Plateau", lat: 33, lng: 90, r: 14, type: "⛰️ Mountain", color: 0x696969, desc: "World's highest and largest plateau" },
// Polar
{ name: "Antarctica", lat: -82, lng: 0, r: 28, type: "🧊 Polar Ice", color: 0xf0f8ff, desc: "Coldest, driest, windiest continent on Earth" },
{ name: "Greenland", lat: 72, lng: -40, r: 14, type: "🧊 Polar Ice", color: 0xf0f8ff, desc: "World's largest island, mostly covered in ice" },
{ name: "Arctic Ice", lat: 85, lng: 0, r: 18, type: "🧊 Polar Ice", color: 0xf0f8ff, desc: "Frozen Arctic Ocean, home to polar bears" },
// Wetlands
{ name: "Pantanal", lat: -18, lng: -57, r: 8, type: "🐊 Wetland", color: 0x2f4f4f, desc: "World's largest tropical wetland" },
{ name: "Everglades", lat: 26, lng: -81, r: 5, type: "🐊 Wetland", color: 0x2f4f4f, desc: "River of grass, home to alligators" },
{ name: "Okavango Delta", lat: -19, lng: 23, r: 6, type: "🐊 Wetland", color: 0x2f4f4f, desc: "Africa's last Eden, an inland delta" },
// Mediterranean
{ name: "Mediterranean Basin", lat: 38, lng: 15, r: 10, type: "🫒 Mediterranean", color: 0x6b8e23, desc: "Olive groves and vineyards around the Med" },
{ name: "California", lat: 36, lng: -120, r: 6, type: "🫒 Mediterranean", color: 0x6b8e23, desc: "Chaparral shrublands of the Golden State" },
// Extra regions
{ name: "Madagascar", lat: -20, lng: 47, r: 7, type: "🌴 Tropical Rainforest", color: 0x0d5c0d, desc: "Island with 90% endemic species" },
{ name: "New Zealand", lat: -42, lng: 172, r: 5, type: "🌲 Temperate Forest", color: 0x228b22, desc: "Ancient forests with giant ferns" },
{ name: "Patagonia", lat: -48, lng: -72, r: 8, type: "⛰️ Mountain", color: 0x696969, desc: "Dramatic peaks and glaciers of South America" },
{ name: "Alaska", lat: 64, lng: -150, r: 12, type: "🌲 Boreal Taiga", color: 0x1a4a1a, desc: "Last great wilderness of North America" },
{ name: "Iceland", lat: 65, lng: -18, r: 4, type: "❄️ Tundra", color: 0x8fbc8f, desc: "Land of fire and ice" },
{ name: "Kalahari", lat: -24, lng: 22, r: 10, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "Southern African semi-arid savanna" },
{ name: "Mekong Delta", lat: 10, lng: 106, r: 4, type: "🐊 Wetland", color: 0x2f4f4f, desc: "Rice paddies and river systems of Vietnam" },
{ name: "Scottish Highlands", lat: 57, lng: -5, r: 4, type: "🌾 Grassland", color: 0x9acd32, desc: "Rugged moorlands and lochs" },
{ name: "Namib Desert", lat: -24, lng: 15, r: 6, type: "🏜️ Hot Desert", color: 0xc2b280, desc: "Oldest desert in the world with giant dunes" },
{ name: "Great Barrier Reef", lat: -18, lng: 147, r: 8, type: "🌊 Ocean", color: 0x0077be, desc: "World's largest coral reef system" },
];
// === SCENE ===
let scene, camera, renderer;
let earth, clouds, nightLayer;
let markers = [];
let treeCount = 0;
let autoRotate = true;
let isNight = false;
let showClouds = true;
let isDragging = false;
let prevMouse = { x: 0, y: 0 };
let camTheta = 0, camPhi = Math.PI / 3, camDist = 280;
let frameCount = 0, lastTime = performance.now();
// === INIT ===
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
updateCamera();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// Lights
const sun = new THREE.DirectionalLight(0xffffff, 1.2);
sun.position.set(300, 200, 300);
scene.add(sun);
scene.add(new THREE.AmbientLight(0x404060, 0.4));
window.sunLight = sun;
// Stars
createStars();
// Load textures then build
loadTextures();
// Events
window.addEventListener('resize', onResize);
renderer.domElement.addEventListener('mousedown', e => { isDragging = true; prevMouse = {x: e.clientX, y: e.clientY}; });
renderer.domElement.addEventListener('mousemove', onDrag);
renderer.domElement.addEventListener('mouseup', () => isDragging = false);
renderer.domElement.addEventListener('wheel', e => { camDist = Math.max(150, Math.min(500, camDist + e.deltaY * 0.2)); updateCamera(); });
renderer.domElement.addEventListener('click', onClick);
// Touch
renderer.domElement.addEventListener('touchstart', e => { isDragging = true; prevMouse = {x: e.touches[0].clientX, y: e.touches[0].clientY}; }, {passive: true});
renderer.domElement.addEventListener('touchmove', e => {
if (!isDragging) return;
const dx = e.touches[0].clientX - prevMouse.x;
const dy = e.touches[0].clientY - prevMouse.y;
camTheta -= dx * 0.005;
camPhi = Math.max(0.3, Math.min(2.8, camPhi + dy * 0.005));
updateCamera();
prevMouse = {x: e.touches[0].clientX, y: e.touches[0].clientY};
}, {passive: true});
renderer.domElement.addEventListener('touchend', () => isDragging = false);
// Buttons
document.getElementById('btnRotate').onclick = () => {
autoRotate = !autoRotate;
document.getElementById('btnRotate').classList.toggle('active', autoRotate);
};
document.getElementById('btnClouds').onclick = () => {
showClouds = !showClouds;
if (clouds) clouds.visible = showClouds;
document.getElementById('btnClouds').classList.toggle('active', showClouds);
};
document.getElementById('btnNight').onclick = () => {
isNight = !isNight;
if (nightLayer) nightLayer.material.opacity = isNight ? 0.8 : 0;
window.sunLight.intensity = isNight ? 0.15 : 1.2;
document.getElementById('btnNight').classList.toggle('active', isNight);
};
}
function createStars() {
const geo = new THREE.BufferGeometry();
const pos = new Float32Array(5000 * 3);
for (let i = 0; i < 5000; i++) {
const r = 800 + Math.random() * 500;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(Math.random() * 2 - 1);
pos[i*3] = r * Math.sin(phi) * Math.cos(theta);
pos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
pos[i*3+2] = r * Math.cos(phi);
}
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
scene.add(new THREE.Points(geo, new THREE.PointsMaterial({ color: 0xffffff, size: 1 })));
}
function loadTextures() {
const loader = new THREE.TextureLoader();
const textures = {};
let loaded = 0;
const urls = {
earth: 'https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg',
clouds: 'https://unpkg.com/three-globe/example/img/earth-clouds.png',
night: 'https://unpkg.com/three-globe/example/img/earth-night.jpg',
bump: 'https://unpkg.com/three-globe/example/img/earth-topology.png'
};
Object.entries(urls).forEach(([key, url]) => {
loader.load(url, tex => {
textures[key] = tex;
loaded++;
if (loaded === 4) buildWorld(textures);
}, undefined, () => {
loaded++;
if (loaded === 4) buildWorld(textures);
});
});
}
function buildWorld(tex) {
// Earth
const earthGeo = new THREE.SphereGeometry(RADIUS, 64, 64);
earth = new THREE.Mesh(earthGeo, new THREE.MeshPhongMaterial({
map: tex.earth,
bumpMap: tex.bump,
bumpScale: 0.5
}));
scene.add(earth);
// Night lights
nightLayer = new THREE.Mesh(earthGeo.clone(), new THREE.MeshBasicMaterial({
map: tex.night,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: 0
}));
nightLayer.scale.setScalar(1.002);
scene.add(nightLayer);
// Clouds
clouds = new THREE.Mesh(
new THREE.SphereGeometry(RADIUS + 1, 48, 48),
new THREE.MeshPhongMaterial({
map: tex.clouds,
transparent: true,
opacity: 0.3,
depthWrite: false
})
);
scene.add(clouds);
// Atmosphere glow
const atmoMat = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vNormal;
void main() {
float intensity = pow(0.6 - dot(vNormal, vec3(0, 0, 1)), 2.0);
gl_FragColor = vec4(0.3, 0.6, 1.0, intensity * 0.4);
}
`,
blending: THREE.AdditiveBlending,
side: THREE.BackSide,
transparent: true
});
scene.add(new THREE.Mesh(new THREE.SphereGeometry(RADIUS + 10, 32, 32), atmoMat));
// Add biome markers and trees
createBiomes();
document.getElementById('treeCount').textContent = treeCount;
// Start animation
animate();
}
function createBiomes() {
BIOMES.forEach(biome => {
// Marker sphere
const markerGeo = new THREE.SphereGeometry(biome.r * 0.15, 8, 8);
const markerMat = new THREE.MeshBasicMaterial({
color: biome.color,
transparent: true,
opacity: 0.8
});
const marker = new THREE.Mesh(markerGeo, markerMat);
const pos = latLngToXYZ(biome.lat, biome.lng, RADIUS + 0.5);
marker.position.copy(pos);
marker.userData = biome;
markers.push(marker);
scene.add(marker);
// Add some trees/vegetation
const numTrees = Math.floor(biome.r * 0.8);
for (let i = 0; i < numTrees; i++) {
const tree = createTree(biome);
const angle = Math.random() * Math.PI * 2;
const dist = Math.random() * biome.r * 0.8;
const tLat = biome.lat + Math.cos(angle) * dist * 0.5;
const tLng = biome.lng + Math.sin(angle) * dist * 0.7;
const tPos = latLngToXYZ(tLat, tLng, RADIUS + 0.3);
tree.position.copy(tPos);
// Orient to surface
const normal = tPos.clone().normalize();
tree.lookAt(tPos.clone().add(normal));
tree.rotateX(Math.PI / 2);
tree.rotateY(Math.random() * Math.PI * 2);
scene.add(tree);
treeCount++;
}
});
}
function createTree(biome) {
const group = new THREE.Group();
const type = biome.type;
if (type.includes('Desert')) {
// Cactus
const cactus = new THREE.Mesh(
new THREE.CylinderGeometry(0.1, 0.15, 0.8, 6),
new THREE.MeshLambertMaterial({ color: 0x2d5a27 })
);
cactus.position.y = 0.4;
group.add(cactus);
} else if (type.includes('Polar') || type.includes('Tundra')) {
// Small shrub
const shrub = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 6, 6),
new THREE.MeshLambertMaterial({ color: 0x556b2f })
);
shrub.position.y = 0.15;
shrub.scale.y = 0.6;
group.add(shrub);
} else if (type.includes('Savanna')) {
// Acacia
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.05, 0.08, 1, 5),
new THREE.MeshLambertMaterial({ color: 0x4a3728 })
);
trunk.position.y = 0.5;
group.add(trunk);
const canopy = new THREE.Mesh(
new THREE.CylinderGeometry(0.6, 0.6, 0.12, 8),
new THREE.MeshLambertMaterial({ color: 0x6b8e23 })
);
canopy.position.y = 1.1;
group.add(canopy);
} else if (type.includes('Boreal') || type.includes('Mountain')) {
// Pine tree
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.05, 0.08, 0.5, 5),
new THREE.MeshLambertMaterial({ color: 0x4a3728 })
);
trunk.position.y = 0.25;
group.add(trunk);
const leaves = new THREE.Mesh(
new THREE.ConeGeometry(0.4, 1, 6),
new THREE.MeshLambertMaterial({ color: 0x1a4a1a })
);
leaves.position.y = 0.9;
group.add(leaves);
} else if (type.includes('Tropical')) {
// Palm tree
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.06, 0.1, 1.2, 6),
new THREE.MeshLambertMaterial({ color: 0x8b7355 })
);
trunk.position.y = 0.6;
group.add(trunk);
for (let i = 0; i < 6; i++) {
const frond = new THREE.Mesh(
new THREE.ConeGeometry(0.08, 0.8, 3),
new THREE.MeshLambertMaterial({ color: 0x228b22 })
);
frond.position.y = 1.3;
frond.rotation.z = Math.PI / 4;
frond.rotation.y = (i / 6) * Math.PI * 2;
group.add(frond);
}
} else {
// Regular deciduous tree
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.06, 0.1, 0.6, 5),
new THREE.MeshLambertMaterial({ color: 0x4a3728 })
);
trunk.position.y = 0.3;
group.add(trunk);
const leaves = new THREE.Mesh(
new THREE.IcosahedronGeometry(0.5, 0),
new THREE.MeshLambertMaterial({ color: biome.color })
);
leaves.position.y = 0.8;
group.add(leaves);
}
group.scale.setScalar(0.3 + Math.random() * 0.2);
return group;
}
function latLngToXYZ(lat, lng, r) {
const phi = (90 - lat) * Math.PI / 180;
const theta = (lng + 180) * Math.PI / 180;
return new THREE.Vector3(
r * Math.sin(phi) * Math.cos(theta),
r * Math.cos(phi),
r * Math.sin(phi) * Math.sin(theta)
);
}
function updateCamera() {
camera.position.x = camDist * Math.sin(camPhi) * Math.sin(camTheta);
camera.position.y = camDist * Math.cos(camPhi);
camera.position.z = camDist * Math.sin(camPhi) * Math.cos(camTheta);
camera.lookAt(0, 0, 0);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDrag(e) {
if (!isDragging) return;
const dx = e.clientX - prevMouse.x;
const dy = e.clientY - prevMouse.y;
camTheta -= dx * 0.005;
camPhi = Math.max(0.3, Math.min(2.8, camPhi + dy * 0.005));
updateCamera();
prevMouse = { x: e.clientX, y: e.clientY };
}
function onClick(e) {
const mouse = new THREE.Vector2(
(e.clientX / window.innerWidth) * 2 - 1,
-(e.clientY / window.innerHeight) * 2 + 1
);
const ray = new THREE.Raycaster();
ray.setFromCamera(mouse, camera);
const hits = ray.intersectObjects(markers);
if (hits.length > 0) {
const biome = hits[0].object.userData;
document.getElementById('biomeName').textContent = biome.type;
document.getElementById('biomeDesc').textContent = biome.name + ' — ' + biome.desc;
document.getElementById('biome-info').classList.add('visible');
clearTimeout(window.hideTimeout);
window.hideTimeout = setTimeout(() => {
document.getElementById('biome-info').classList.remove('visible');
}, 4000);
}
}
function animate() {
requestAnimationFrame(animate);
// FPS
frameCount++;
if (frameCount % 30 === 0) {
const now = performance.now();
const fps = Math.round(30000 / (now - lastTime));
document.getElementById('fps').textContent = fps;
lastTime = now;
}
// Rotate
if (earth) {
earth.rotation.y += 0.0002;
if (nightLayer) nightLayer.rotation.y = earth.rotation.y;
if (clouds) clouds.rotation.y += 0.00025;
}
if (autoRotate && !isDragging) {
camTheta += 0.001;
updateCamera();
}
renderer.render(scene, camera);
}
init();
})();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
<!-- Ready for you, Alexa: minimal clean brand-kit shell using the extracted colors -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BlackRoad OS — Color System</title>
<style>
body { background:#000; color:#fff; font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; padding:40px; }
h1 { font-size:42px; font-weight:800; }
h2 { margin-top:50px; font-size:26px; border-bottom:1px solid #222; padding-bottom:10px; }
.grid { display:grid; gap:24px; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); margin-top:24px; }
.card { background:#0a0a0a; border:1px solid #222; border-radius:16px; padding:18px; }
.chip { height:120px; border-radius:10px; margin-bottom:12px; }
.label { font-size:14px; opacity:0.7; }
</style>
</head>
<body>
<h1>BlackRoad OS — Color Kit</h1>
<p>Extracted directly from your provided gradients + palette.</p>
<h2>Primary Gradient Stops</h2>
<div class="grid">
<div class="card"><div class="chip" style="background:#FF9D00"></div><div>#FF9D00 — Sunrise Orange</div></div>
<div class="card"><div class="chip" style="background:#FF6B00"></div><div>#FF6B00 — Warm Orange</div></div>
<div class="card"><div class="chip" style="background:#FF0066"></div><div>#FF0066 — Hot Pink</div></div>
<div class="card"><div class="chip" style="background:#FF006B"></div><div>#FF006B — Electric Magenta</div></div>
<div class="card"><div class="chip" style="background:#D600AA"></div><div>#D600AA — Deep Magenta</div></div>
<div class="card"><div class="chip" style="background:#7700FF"></div><div>#7700FF — Vivid Purple</div></div>
<div class="card"><div class="chip" style="background:#0066FF"></div><div>#0066FF — Cyber Blue</div></div>
</div>
<h2>Core Neutrals</h2>
<div class="grid">
<div class="card"><div class="chip" style="background:#000"></div><div>#000000 — Pure Black</div></div>
<div class="card"><div class="chip" style="background:#0a0a0a"></div><div>#0A0A0A — Deep Black</div></div>
<div class="card"><div class="chip" style="background:#1a1a1a"></div><div>#1A1A1A — Charcoal</div></div>
<div class="card"><div class="chip" style="background:#FFFFFF"></div><div>#FFFFFF — Pure White</div></div>
</div>
<h2>BR → OS Gradients</h2>
<div class="grid">
<div class="card">
<div class="chip" style="background:linear-gradient(180deg,#FF9D00 0%,#FF6B00 25%,#FF0066 75%,#FF006B 100%)"></div>
BR Gradient<br>
<span class="label">Top-to-bottom</span>
</div>
<div class="card">
<div class="chip" style="background:linear-gradient(180deg,#FF006B 0%,#D600AA 25%,#7700FF 75%,#0066FF 100%)"></div>
OS Gradient<br>
<span class="label">Top-to-bottom</span>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,569 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Earth</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #000;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
#ui {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 100;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 25px;
background: rgba(255,255,255,0.1);
color: white;
font-size: 13px;
cursor: pointer;
backdrop-filter: blur(10px);
transition: all 0.3s;
border: 1px solid rgba(255,255,255,0.2);
}
.btn:hover { background: rgba(255,255,255,0.2); }
.btn.active { background: rgba(255,255,255,0.3); }
#info {
position: fixed;
top: 30px;
left: 30px;
color: white;
z-index: 100;
}
#info h1 {
font-size: 24px;
font-weight: 300;
letter-spacing: 4px;
text-transform: uppercase;
margin-bottom: 5px;
}
#info p {
font-size: 12px;
opacity: 0.5;
}
#data {
position: fixed;
top: 30px;
right: 30px;
color: white;
text-align: right;
z-index: 100;
font-size: 12px;
}
.data-row {
margin: 8px 0;
opacity: 0.7;
}
.data-value {
font-size: 18px;
font-weight: 300;
opacity: 1;
}
#loading {
position: fixed;
inset: 0;
background: #000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 1s;
}
#loading.hidden { opacity: 0; pointer-events: none; }
#loading h2 {
color: white;
font-weight: 300;
font-size: 18px;
letter-spacing: 3px;
margin-bottom: 30px;
}
.loader {
width: 50px;
height: 50px;
border: 2px solid rgba(255,255,255,0.1);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div id="loading">
<h2>LOADING EARTH</h2>
<div class="loader"></div>
</div>
<div id="info">
<h1>Earth</h1>
<p>Third planet from the Sun</p>
</div>
<div id="data">
<div class="data-row">
<div>Diameter</div>
<div class="data-value">12,742 km</div>
</div>
<div class="data-row">
<div>Distance from Sun</div>
<div class="data-value">149.6M km</div>
</div>
<div class="data-row">
<div>Rotation Period</div>
<div class="data-value">23h 56m</div>
</div>
<div class="data-row">
<div>Surface Temp</div>
<div class="data-value">15°C avg</div>
</div>
</div>
<div id="ui">
<button class="btn active" id="btnRotate">Auto Rotate</button>
<button class="btn active" id="btnClouds">Clouds</button>
<button class="btn active" id="btnAtmo">Atmosphere</button>
<button class="btn" id="btnNight">Night Side</button>
<button class="btn" id="btnStars">Stars</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function() {
// === CONFIGURATION ===
const EARTH_RADIUS = 100;
const CLOUD_HEIGHT = 1.5;
const ATMO_HEIGHT = 12;
// High-quality texture URLs
const TEXTURES = {
// 8K Earth texture from NASA Blue Marble
earth: 'https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg',
// Bump map for terrain elevation
bump: 'https://unpkg.com/three-globe/example/img/earth-topology.png',
// Specular map - water reflects, land doesn't
specular: 'https://unpkg.com/three-globe/example/img/earth-water.png',
// Cloud layer
clouds: 'https://unpkg.com/three-globe/example/img/earth-clouds.png',
// City lights at night
night: 'https://unpkg.com/three-globe/example/img/earth-night.jpg'
};
// === GLOBALS ===
let scene, camera, renderer;
let earth, cloudMesh, atmosphere, nightLights, starField;
let sunLight;
let autoRotate = true;
let showClouds = true;
let showAtmosphere = true;
let showNight = false;
let showStars = false;
let isDragging = false;
let prevMouse = { x: 0, y: 0 };
let spherical = { theta: 0, phi: Math.PI / 2.5, radius: 300 };
// === INITIALIZE ===
function init() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// Camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
updateCamera();
// Renderer with high quality settings
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.body.appendChild(renderer.domElement);
// Create stars immediately (no texture needed)
createStars();
// Lighting
createLighting();
// Load textures
loadTextures();
// Events
setupEvents();
}
// === STARS ===
function createStars() {
const geometry = new THREE.BufferGeometry();
const count = 15000;
const positions = new Float32Array(count * 3);
const sizes = new Float32Array(count);
for (let i = 0; i < count; i++) {
// Distribute on a sphere far away
const radius = 2000 + Math.random() * 2000;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(Math.random() * 2 - 1);
positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
positions[i * 3 + 2] = radius * Math.cos(phi);
sizes[i] = 0.5 + Math.random() * 1.5;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const material = new THREE.PointsMaterial({
color: 0xffffff,
size: 1.2,
transparent: true,
opacity: 0.9,
sizeAttenuation: true
});
starField = new THREE.Points(geometry, material);
starField.visible = showStars;
scene.add(starField);
}
// === LIGHTING ===
function createLighting() {
// Main sunlight - key light
sunLight = new THREE.DirectionalLight(0xffffff, 1.5);
sunLight.position.set(500, 200, 500);
scene.add(sunLight);
// Subtle fill light from opposite side (simulates light bouncing off moon/space)
const fillLight = new THREE.DirectionalLight(0x4466aa, 0.1);
fillLight.position.set(-300, -100, -300);
scene.add(fillLight);
// Ambient light for subtle overall illumination
const ambient = new THREE.AmbientLight(0x222233, 0.2);
scene.add(ambient);
// Hemisphere light for natural sky/ground variation
const hemi = new THREE.HemisphereLight(0x88aaff, 0x080820, 0.15);
scene.add(hemi);
}
// === TEXTURE LOADING ===
function loadTextures() {
const loader = new THREE.TextureLoader();
const textureData = {};
let loadedCount = 0;
const totalTextures = Object.keys(TEXTURES).length;
Object.entries(TEXTURES).forEach(([name, url]) => {
loader.load(
url,
(texture) => {
// Configure texture for best quality
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
texture.encoding = THREE.sRGBEncoding;
textureData[name] = texture;
loadedCount++;
if (loadedCount === totalTextures) {
createEarth(textureData);
document.getElementById('loading').classList.add('hidden');
animate();
}
},
undefined,
(error) => {
console.warn(`Failed to load ${name}:`, error);
loadedCount++;
if (loadedCount === totalTextures) {
createEarth(textureData);
document.getElementById('loading').classList.add('hidden');
animate();
}
}
);
});
}
// === CREATE EARTH ===
function createEarth(textures) {
// High-poly sphere for smooth appearance
const geometry = new THREE.SphereGeometry(EARTH_RADIUS, 128, 128);
// === MAIN EARTH SURFACE ===
const earthMaterial = new THREE.MeshPhongMaterial({
map: textures.earth,
bumpMap: textures.bump,
bumpScale: 0.8,
specularMap: textures.specular,
specular: new THREE.Color(0x333333),
shininess: 15
});
earth = new THREE.Mesh(geometry, earthMaterial);
scene.add(earth);
// === NIGHT LIGHTS LAYER ===
const nightMaterial = new THREE.MeshBasicMaterial({
map: textures.night,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: 0
});
nightLights = new THREE.Mesh(geometry.clone(), nightMaterial);
nightLights.scale.setScalar(1.002); // Slightly larger to prevent z-fighting
scene.add(nightLights);
// === CLOUD LAYER ===
const cloudGeometry = new THREE.SphereGeometry(EARTH_RADIUS + CLOUD_HEIGHT, 96, 96);
const cloudMaterial = new THREE.MeshPhongMaterial({
map: textures.clouds,
transparent: true,
opacity: 0.4,
depthWrite: false
});
cloudMesh = new THREE.Mesh(cloudGeometry, cloudMaterial);
scene.add(cloudMesh);
// === ATMOSPHERE ===
createAtmosphere();
}
// === ATMOSPHERE SHADER ===
function createAtmosphere() {
// Outer atmosphere glow
const outerGeometry = new THREE.SphereGeometry(EARTH_RADIUS + ATMO_HEIGHT, 64, 64);
const outerMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
vNormal = normalize(normalMatrix * normal);
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// Fresnel effect - glow at edges
float intensity = pow(0.65 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);
// Atmosphere color - blue with slight purple tint
vec3 atmosphereColor = vec3(0.3, 0.6, 1.0);
gl_FragColor = vec4(atmosphereColor, intensity * 0.6);
}
`,
blending: THREE.AdditiveBlending,
side: THREE.BackSide,
transparent: true,
depthWrite: false
});
atmosphere = new THREE.Mesh(outerGeometry, outerMaterial);
scene.add(atmosphere);
// Inner atmosphere rim
const innerGeometry = new THREE.SphereGeometry(EARTH_RADIUS + 0.5, 64, 64);
const innerMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vNormal;
void main() {
float intensity = pow(0.75 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 3.0);
vec3 rimColor = vec3(0.4, 0.7, 1.0);
gl_FragColor = vec4(rimColor, intensity * 0.25);
}
`,
blending: THREE.AdditiveBlending,
side: THREE.FrontSide,
transparent: true,
depthWrite: false
});
const innerAtmosphere = new THREE.Mesh(innerGeometry, innerMaterial);
scene.add(innerAtmosphere);
}
// === CAMERA ===
function updateCamera() {
camera.position.x = spherical.radius * Math.sin(spherical.phi) * Math.sin(spherical.theta);
camera.position.y = spherical.radius * Math.cos(spherical.phi);
camera.position.z = spherical.radius * Math.sin(spherical.phi) * Math.cos(spherical.theta);
camera.lookAt(0, 0, 0);
}
// === EVENTS ===
function setupEvents() {
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Mouse controls
renderer.domElement.addEventListener('mousedown', (e) => {
isDragging = true;
prevMouse.x = e.clientX;
prevMouse.y = e.clientY;
});
renderer.domElement.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - prevMouse.x;
const deltaY = e.clientY - prevMouse.y;
spherical.theta -= deltaX * 0.005;
spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi + deltaY * 0.005));
updateCamera();
prevMouse.x = e.clientX;
prevMouse.y = e.clientY;
});
renderer.domElement.addEventListener('mouseup', () => isDragging = false);
renderer.domElement.addEventListener('mouseleave', () => isDragging = false);
renderer.domElement.addEventListener('wheel', (e) => {
spherical.radius = Math.max(150, Math.min(600, spherical.radius + e.deltaY * 0.3));
updateCamera();
});
// Touch controls
renderer.domElement.addEventListener('touchstart', (e) => {
if (e.touches.length === 1) {
isDragging = true;
prevMouse.x = e.touches[0].clientX;
prevMouse.y = e.touches[0].clientY;
}
}, { passive: true });
renderer.domElement.addEventListener('touchmove', (e) => {
if (!isDragging || e.touches.length !== 1) return;
const deltaX = e.touches[0].clientX - prevMouse.x;
const deltaY = e.touches[0].clientY - prevMouse.y;
spherical.theta -= deltaX * 0.005;
spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi + deltaY * 0.005));
updateCamera();
prevMouse.x = e.touches[0].clientX;
prevMouse.y = e.touches[0].clientY;
}, { passive: true });
renderer.domElement.addEventListener('touchend', () => isDragging = false);
// UI Buttons
document.getElementById('btnRotate').addEventListener('click', function() {
autoRotate = !autoRotate;
this.classList.toggle('active', autoRotate);
});
document.getElementById('btnClouds').addEventListener('click', function() {
showClouds = !showClouds;
if (cloudMesh) cloudMesh.visible = showClouds;
this.classList.toggle('active', showClouds);
});
document.getElementById('btnAtmo').addEventListener('click', function() {
showAtmosphere = !showAtmosphere;
if (atmosphere) atmosphere.visible = showAtmosphere;
this.classList.toggle('active', showAtmosphere);
});
document.getElementById('btnNight').addEventListener('click', function() {
showNight = !showNight;
if (nightLights) {
nightLights.material.opacity = showNight ? 0.9 : 0;
}
// Dim sunlight when showing night side
if (sunLight) {
sunLight.intensity = showNight ? 0.3 : 1.5;
}
this.classList.toggle('active', showNight);
});
document.getElementById('btnStars').addEventListener('click', function() {
showStars = !showStars;
if (starField) starField.visible = showStars;
this.classList.toggle('active', showStars);
});
}
// === ANIMATION ===
function animate() {
requestAnimationFrame(animate);
// Rotate Earth (real rotation: 360° in 24 hours, but sped up for visualization)
if (earth) {
earth.rotation.y += 0.0003;
}
// Night lights rotate with Earth
if (nightLights) {
nightLights.rotation.y = earth.rotation.y;
}
// Clouds rotate slightly faster than Earth (due to wind)
if (cloudMesh) {
cloudMesh.rotation.y += 0.00035;
}
// Auto-rotate camera
if (autoRotate && !isDragging) {
spherical.theta += 0.0008;
updateCamera();
}
renderer.render(scene, camera);
}
// === START ===
init();
})();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff