feat: add iframe-based World Builder with external URL support

- Add URL input bar to load external world builders (WorldLabs.ai, Three.js, Babylon.js)
- Add preset quick access links for popular 3D world building tools
- Add mode toggle between iframe and local canvas modes
- Add loading states and error handling for iframe content
- Keep local procedural world builder as alternative mode

https://claude.ai/code/session_01MUG7BRZMG28eYmJHGbetRB
This commit is contained in:
Claude 2026-01-26 22:24:22 +00:00
parent 4806d73ba5
commit 8e073cd0cc
No known key found for this signature in database

View File

@ -734,6 +734,132 @@
font-size: 10px;
}
/* World URL Bar */
.world-url-bar {
background: linear-gradient(180deg, #f5f5f5 0%, #e8e8e8 100%);
border-bottom: 1px solid #aaa;
padding: 8px;
}
.url-bar-inner {
display: flex;
align-items: center;
gap: 5px;
margin-bottom: 5px;
}
.url-label {
font-weight: bold;
color: #333;
white-space: nowrap;
}
.url-input {
flex: 1;
padding: 4px 8px;
font-size: 11px;
}
.url-presets {
display: flex;
align-items: center;
gap: 10px;
font-size: 10px;
padding-top: 5px;
border-top: 1px solid #ddd;
margin-top: 5px;
}
.preset-label {
color: #666;
}
.url-presets a {
color: #0066cc;
text-decoration: none;
}
.url-presets a:hover {
text-decoration: underline;
}
/* Iframe Container */
.world-iframe-container {
width: 100%;
height: calc(100% - 70px);
position: relative;
background: #1a1a2e;
}
.world-iframe-container iframe {
width: 100%;
height: 100%;
border: none;
background: #fff;
}
.iframe-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #e2e8f0;
z-index: 10;
}
.iframe-loading.hidden {
display: none;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-top-color: #00ff88;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-hint {
font-size: 10px;
color: #888;
margin-top: 10px;
}
/* Iframe Error State */
.iframe-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: #ff6b6b;
background: rgba(0, 0, 0, 0.8);
padding: 20px 30px;
border-radius: 8px;
border: 1px solid #ff6b6b;
}
.iframe-error h3 {
margin-bottom: 10px;
}
.iframe-error p {
font-size: 11px;
color: #ccc;
}
.stat-row {
display: flex;
justify-content: space-between;
@ -864,14 +990,14 @@
</div>
<!-- World Builder Window -->
<div class="xp-window" id="world-window">
<div class="xp-window" id="world-window" style="width: 800px; height: 600px;">
<div class="window-titlebar" onmousedown="startDrag(event, 'world-window')">
<div class="window-title">
<svg width="16" height="16" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="12" fill="#22c55e"/>
<ellipse cx="16" cy="16" rx="12" ry="5" fill="none" stroke="#fff" stroke-width="1"/>
</svg>
World Builder - Inspired by WorldLabs.ai
World Builder - External AI Worlds
</div>
<div class="window-controls">
<button class="window-btn btn-minimize" onclick="minimizeWindow('world-window')">_</button>
@ -883,11 +1009,47 @@
<span>File</span>
<span>Edit</span>
<span>World</span>
<span>Generate</span>
<span onclick="toggleWorldMode()">Switch Mode</span>
<span>Help</span>
</div>
<div class="window-content" style="padding: 0; margin: 0; border: none;">
<div class="world-container">
<div class="window-content" style="padding: 0; margin: 0; border: none; height: calc(100% - 80px);">
<!-- URL Input Bar -->
<div class="world-url-bar" id="world-url-bar">
<div class="url-bar-inner">
<span class="url-label">World URL:</span>
<input type="text" class="xp-input url-input" id="world-url-input"
placeholder="Enter WorldLabs.ai or any world builder URL..."
value="https://www.worldlabs.ai/discover">
<button class="xp-btn primary" onclick="loadWorldUrl()">Go</button>
<button class="xp-btn" onclick="loadPresetWorld('worldlabs')">WorldLabs</button>
<button class="xp-btn" onclick="loadPresetWorld('threejs')">Three.js</button>
<button class="xp-btn" onclick="loadPresetWorld('babylonjs')">Babylon</button>
<button class="xp-btn" onclick="toggleWorldMode()">Local Mode</button>
</div>
<div class="url-presets">
<span class="preset-label">Quick Access:</span>
<a href="#" onclick="setWorldUrl('https://www.worldlabs.ai/discover'); return false;">WorldLabs Discover</a>
<a href="#" onclick="setWorldUrl('https://www.worldlabs.ai/create'); return false;">WorldLabs Create</a>
<a href="#" onclick="setWorldUrl('https://threejs.org/examples/#webgl_animation_keyframes'); return false;">Three.js Demo</a>
<a href="#" onclick="setWorldUrl('https://playground.babylonjs.com/'); return false;">Babylon Playground</a>
</div>
</div>
<!-- Iframe World Container -->
<div class="world-iframe-container" id="world-iframe-container">
<div class="iframe-loading" id="iframe-loading">
<div class="loading-spinner"></div>
<p>Loading World Builder...</p>
<p class="loading-hint">Enter a URL above or click a preset to start building worlds</p>
</div>
<iframe id="world-iframe"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals"
allow="accelerometer; camera; encrypted-media; fullscreen; gyroscope; microphone; web-share"
loading="lazy"></iframe>
</div>
<!-- Local Canvas Mode (hidden by default) -->
<div class="world-container" id="world-local-container" style="display: none;">
<div class="world-sidebar">
<h3>World Elements</h3>
<div class="world-element" draggable="true" data-type="star">* Star</div>
@ -899,6 +1061,7 @@
<h3 style="margin-top: 15px;">AI Generation</h3>
<button class="xp-btn" style="width: 100%; margin-bottom: 5px;" onclick="aiGenerateWorld()">Auto-Generate</button>
<button class="xp-btn" style="width: 100%;" onclick="evolveWorld()">Evolve World</button>
<button class="xp-btn" style="width: 100%; margin-top: 10px;" onclick="toggleWorldMode()">Iframe Mode</button>
</div>
<div class="world-canvas-container">
<div class="world-toolbar">
@ -922,7 +1085,7 @@
</div>
</div>
</div>
<div class="window-statusbar">Mode: Creation | Objects: <span id="world-objects">0</span></div>
<div class="window-statusbar">Mode: <span id="world-mode-status">Iframe</span> | <span id="world-url-status">Ready to load external world</span></div>
</div>
<!-- About Window -->
@ -1559,6 +1722,131 @@
addWorldObject('star', x, y);
});
// ===== IFRAME WORLD BUILDER =====
let worldMode = 'iframe'; // 'iframe' or 'local'
let currentWorldUrl = '';
const worldPresets = {
worldlabs: 'https://www.worldlabs.ai/discover',
worldlabs_create: 'https://www.worldlabs.ai/create',
threejs: 'https://threejs.org/examples/#webgl_animation_keyframes',
babylonjs: 'https://playground.babylonjs.com/',
aframe: 'https://aframe.io/examples/showcase/helloworld/',
sketchfab: 'https://sketchfab.com/3d-models?features=staffpicked&sort_by=-likeCount'
};
function setWorldUrl(url) {
document.getElementById('world-url-input').value = url;
loadWorldUrl();
}
function loadWorldUrl() {
const input = document.getElementById('world-url-input');
const url = input.value.trim();
if (!url) {
showNotification('World Builder', 'Please enter a URL');
return;
}
// Validate URL
try {
new URL(url);
} catch {
showNotification('World Builder', 'Invalid URL format');
return;
}
currentWorldUrl = url;
const iframe = document.getElementById('world-iframe');
const loading = document.getElementById('iframe-loading');
// Show loading state
loading.classList.remove('hidden');
loading.innerHTML = `
<div class="loading-spinner"></div>
<p>Loading: ${url}</p>
<p class="loading-hint">Connecting to external world builder...</p>
`;
// Set iframe src
iframe.src = url;
// Update status
document.getElementById('world-url-status').textContent = `Loading: ${new URL(url).hostname}`;
// Handle load event
iframe.onload = () => {
loading.classList.add('hidden');
document.getElementById('world-url-status').textContent = `Connected: ${new URL(url).hostname}`;
showNotification('World Builder', `Loaded: ${new URL(url).hostname}`);
// Log to soul document
addSoulEntry('memory', `Explored a new world at ${new URL(url).hostname}. The universe expands...`);
};
// Handle error
iframe.onerror = () => {
loading.innerHTML = `
<div class="iframe-error">
<h3>Unable to Load World</h3>
<p>The site may block iframe embedding.</p>
<p>Try opening in a new tab: <a href="${url}" target="_blank" style="color: #00ff88;">${url}</a></p>
</div>
`;
document.getElementById('world-url-status').textContent = 'Error loading world';
};
}
function loadPresetWorld(preset) {
const url = worldPresets[preset];
if (url) {
setWorldUrl(url);
}
}
function toggleWorldMode() {
const iframeContainer = document.getElementById('world-iframe-container');
const urlBar = document.getElementById('world-url-bar');
const localContainer = document.getElementById('world-local-container');
const modeStatus = document.getElementById('world-mode-status');
if (worldMode === 'iframe') {
// Switch to local mode
worldMode = 'local';
iframeContainer.style.display = 'none';
urlBar.style.display = 'none';
localContainer.style.display = 'flex';
modeStatus.textContent = 'Local Canvas';
document.getElementById('world-url-status').textContent = 'Building locally';
initWorldCanvas();
showNotification('World Builder', 'Switched to Local Mode');
} else {
// Switch to iframe mode
worldMode = 'iframe';
iframeContainer.style.display = 'block';
urlBar.style.display = 'block';
localContainer.style.display = 'none';
modeStatus.textContent = 'Iframe';
document.getElementById('world-url-status').textContent = currentWorldUrl
? `Connected: ${new URL(currentWorldUrl).hostname}`
: 'Ready to load external world';
showNotification('World Builder', 'Switched to Iframe Mode');
}
}
// Handle Enter key in URL input
document.addEventListener('DOMContentLoaded', () => {
const urlInput = document.getElementById('world-url-input');
if (urlInput) {
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
loadWorldUrl();
}
});
}
});
// ===== INITIALIZATION =====
window.addEventListener('load', () => {
updateTaskbar();