🌈 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:
577
.trinity/redlight/docs/REDLIGHT_TEMPLATE_SYSTEM.md
Normal file
577
.trinity/redlight/docs/REDLIGHT_TEMPLATE_SYSTEM.md
Normal 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
|
||||
650
.trinity/redlight/scripts/memory-redlight-templates.sh
Executable file
650
.trinity/redlight/scripts/memory-redlight-templates.sh
Executable 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
|
||||
1814
.trinity/redlight/templates/black-road-os-ultimate (2).html
Normal file
1814
.trinity/redlight/templates/black-road-os-ultimate (2).html
Normal file
File diff suppressed because it is too large
Load Diff
897
.trinity/redlight/templates/blackroad-3d-world.html
Normal file
897
.trinity/redlight/templates/blackroad-3d-world.html
Normal 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>
|
||||
1958
.trinity/redlight/templates/blackroad-animation.html
Normal file
1958
.trinity/redlight/templates/blackroad-animation.html
Normal file
File diff suppressed because it is too large
Load Diff
964
.trinity/redlight/templates/blackroad-architecture-visual.html
Normal file
964
.trinity/redlight/templates/blackroad-architecture-visual.html
Normal 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>
|
||||
800
.trinity/redlight/templates/blackroad-earth-biomes.html
Normal file
800
.trinity/redlight/templates/blackroad-earth-biomes.html
Normal 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">0°</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>
|
||||
2009
.trinity/redlight/templates/blackroad-earth-game.html
Normal file
2009
.trinity/redlight/templates/blackroad-earth-game.html
Normal file
File diff suppressed because it is too large
Load Diff
1205
.trinity/redlight/templates/blackroad-earth-real.html
Normal file
1205
.trinity/redlight/templates/blackroad-earth-real.html
Normal file
File diff suppressed because it is too large
Load Diff
955
.trinity/redlight/templates/blackroad-earth-street.html
Normal file
955
.trinity/redlight/templates/blackroad-earth-street.html
Normal 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>
|
||||
1253
.trinity/redlight/templates/blackroad-earth.html
Normal file
1253
.trinity/redlight/templates/blackroad-earth.html
Normal file
File diff suppressed because it is too large
Load Diff
1787
.trinity/redlight/templates/blackroad-game.html
Normal file
1787
.trinity/redlight/templates/blackroad-game.html
Normal file
File diff suppressed because it is too large
Load Diff
1332
.trinity/redlight/templates/blackroad-living-earth.html
Normal file
1332
.trinity/redlight/templates/blackroad-living-earth.html
Normal file
File diff suppressed because it is too large
Load Diff
1320
.trinity/redlight/templates/blackroad-living-planet.html
Normal file
1320
.trinity/redlight/templates/blackroad-living-planet.html
Normal file
File diff suppressed because it is too large
Load Diff
1719
.trinity/redlight/templates/blackroad-living-world.html
Normal file
1719
.trinity/redlight/templates/blackroad-living-world.html
Normal file
File diff suppressed because it is too large
Load Diff
1486
.trinity/redlight/templates/blackroad-metaverse.html
Normal file
1486
.trinity/redlight/templates/blackroad-metaverse.html
Normal file
File diff suppressed because it is too large
Load Diff
1471
.trinity/redlight/templates/blackroad-motion.html
Normal file
1471
.trinity/redlight/templates/blackroad-motion.html
Normal file
File diff suppressed because it is too large
Load Diff
1842
.trinity/redlight/templates/blackroad-ultimate.html
Normal file
1842
.trinity/redlight/templates/blackroad-ultimate.html
Normal file
File diff suppressed because it is too large
Load Diff
629
.trinity/redlight/templates/blackroad-world-template.html
Normal file
629
.trinity/redlight/templates/blackroad-world-template.html
Normal 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>
|
||||
1982
.trinity/redlight/templates/blackroad-world-v2.html
Normal file
1982
.trinity/redlight/templates/blackroad-world-v2.html
Normal file
File diff suppressed because it is too large
Load Diff
57
.trinity/redlight/templates/blackroad_brand_take_2.html
Normal file
57
.trinity/redlight/templates/blackroad_brand_take_2.html
Normal 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>
|
||||
1310
.trinity/redlight/templates/blackroad_os_brand_kit_pretty.html
Normal file
1310
.trinity/redlight/templates/blackroad_os_brand_kit_pretty.html
Normal file
File diff suppressed because it is too large
Load Diff
569
.trinity/redlight/templates/earth-replica.html
Normal file
569
.trinity/redlight/templates/earth-replica.html
Normal 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>
|
||||
1856
.trinity/redlight/templates/schematiq-animation.html
Normal file
1856
.trinity/redlight/templates/schematiq-animation.html
Normal file
File diff suppressed because it is too large
Load Diff
1932
.trinity/redlight/templates/schematiq-page.html
Normal file
1932
.trinity/redlight/templates/schematiq-page.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user