Show description
Penguin Database World
Penguin Database World
🤖 Create Table with AI
📊 Go to Data Area
🎬 Go to Drive-In
🏠 Go to Main Area
⚙️
🐧 WASD or Arrow Keys to move
🖱️ Mouse to rotate camera
🔍 Walk near tables to view them
📝 Use Data Area to add records
🎬 Drive-In: View HTML pages on big screen
💜 Purple tables = HTML content
🤖 Create tables with natural language
💾 Data saved in browser storage
Penguin Status:
Position: (0, 0, 0)
Nearby: None
Tables: 0
📊 Data Entry Area
Walk here to add data to tables!
🎬 Drive-In Theater
View HTML pages on the big screen!
⚙️ Settings
Claude API Key:
Get your API key from console.anthropic.com
Clear All Data:
🗑️ Clear Database
This will delete all tables and data from browser storage
💾 Save Settings
Cancel
🤖 AI Table Creator
Describe what kind of table you want to create:
Generated Table Structure:
🎯 Generate Table
✅ Create Table
Cancel
Table Details
Close
📊 Select Table for Data Entry
Choose which table you'd like to add data to:
Cancel
Add Data to Table
Add Record
Cancel
HTML Pages
Close
Penguin Database World
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Penguin Database World</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: #001122;
font-family: 'Courier New', monospace;
overflow: hidden;
}
#gameContainer {
position: relative;
width: 100vw;
height: 100vh;
}
#ui {
position: absolute;
top: 20px;
left: 20px;
z-index: 100;
display: flex;
flex-direction: column;
gap: 10px;
}
.ui-button {
padding: 12px 20px;
background: rgba(0, 150, 255, 0.9);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
gap: 8px;
}
.ui-button:hover {
background: rgba(0, 180, 255, 1);
transform: translateY(-2px);
}
.ui-button:disabled {
background: rgba(100, 100, 100, 0.5);
cursor: not-allowed;
transform: none;
}
#settingsBtn {
position: absolute;
top: 20px;
right: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(100, 100, 100, 0.9);
justify-content: center;
padding: 0;
z-index: 150;
}
#instructions {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
font-size: 12px;
backdrop-filter: blur(10px);
}
#status {
position: absolute;
top: 20px;
right: 90px;
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
min-width: 200px;
backdrop-filter: blur(10px);
}
.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(20, 20, 40, 0.95);
color: white;
padding: 30px;
border-radius: 15px;
border: 2px solid #0096ff;
backdrop-filter: blur(20px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
z-index: 200;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
}
.modal h3 {
margin-top: 0;
color: #00ccff;
}
.modal input, .modal textarea {
width: 100%;
padding: 10px;
margin: 10px 0;
background: rgba(255, 255, 255, 0.1);
border: 1px solid #0096ff;
border-radius: 5px;
color: white;
font-family: inherit;
box-sizing: border-box;
}
.modal button {
padding: 10px 20px;
margin: 5px;
background: #0096ff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
}
.modal button:hover {
background: #00b3ff;
}
.modal button.cancel {
background: #666;
}
.modal button.cancel:hover {
background: #888;
}
.table-preview {
margin: 15px 0;
padding: 15px;
background: rgba(0, 50, 100, 0.3);
border-radius: 8px;
border: 1px solid #0096ff;
}
.table-data {
max-height: 300px;
overflow-y: auto;
margin: 10px 0;
}
.table-row {
padding: 8px;
margin: 5px 0;
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
border-left: 3px solid #00ccff;
}
.hidden {
display: none;
}
#dataArea {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0, 100, 200, 0.9);
color: white;
padding: 15px;
border-radius: 8px;
backdrop-filter: blur(10px);
opacity: 0;
transition: opacity 0.3s;
}
#dataArea.visible {
opacity: 1;
}
#driveInArea {
position: absolute;
bottom: 80px;
right: 20px;
background: rgba(200, 0, 100, 0.9);
color: white;
padding: 15px;
border-radius: 8px;
backdrop-filter: blur(10px);
opacity: 0;
transition: opacity 0.3s;
}
#driveInArea.visible {
opacity: 1;
}
.loading {
opacity: 0.5;
pointer-events: none;
}
.api-status {
font-size: 12px;
margin-top: 10px;
padding: 8px;
border-radius: 4px;
}
.api-status.success {
background: rgba(0, 255, 0, 0.2);
color: #00ff00;
}
.api-status.error {
background: rgba(255, 0, 0, 0.2);
color: #ff6666;
}
.html-page-item {
padding: 10px;
margin: 5px 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
cursor: pointer;
border: 1px solid transparent;
}
.html-page-item:hover {
border-color: #0096ff;
background: rgba(0, 150, 255, 0.2);
}
.table-select-item {
padding: 15px;
margin: 10px 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s;
}
.table-select-item:hover {
border-color: #0096ff;
background: rgba(0, 150, 255, 0.2);
}
.table-select-item h4 {
margin: 0 0 8px 0;
color: #00ccff;
}
.table-select-item .table-info {
font-size: 12px;
color: #aaa;
}
</style>
</head>
<body>
<div id="gameContainer">
<div id="ui">
<button class="ui-button" onclick="showCreateTableModal()">🤖 Create Table with AI</button>
<button class="ui-button" onclick="teleportToDataArea()">📊 Go to Data Area</button>
<button class="ui-button" onclick="teleportToDriveIn()">🎬 Go to Drive-In</button>
<button class="ui-button" onclick="teleportToMainArea()">🏠 Go to Main Area</button>
</div>
<button id="settingsBtn" class="ui-button" onclick="showSettingsModal()">⚙️</button>
<div id="instructions">
🐧 WASD or Arrow Keys to move<br>
🖱️ Mouse to rotate camera<br>
🔍 Walk near tables to view them<br>
📝 Use Data Area to add records<br>
🎬 Drive-In: View HTML pages on big screen<br>
💜 Purple tables = HTML content<br>
🤖 Create tables with natural language<br>
💾 Data saved in browser storage
</div>
<div id="status">
<div><strong>Penguin Status:</strong></div>
<div id="penguinPos">Position: (0, 0, 0)</div>
<div id="nearbyTable">Nearby: None</div>
<div id="tableCount">Tables: 0</div>
<div id="apiStatus" class="api-status" style="display: none;"></div>
</div>
<div id="dataArea">
<strong>📊 Data Entry Area</strong><br>
Walk here to add data to tables!
</div>
<div id="driveInArea">
<strong>🎬 Drive-In Theater</strong><br>
View HTML pages on the big screen!
</div>
</div>
<!-- Settings Modal -->
<div id="settingsModal" class="modal hidden">
<h3>⚙️ Settings</h3>
<div>
<label><strong>Claude API Key:</strong></label>
<input type="password" id="apiKeyInput" placeholder="Enter your Claude API key">
<small style="color: #aaa; display: block; margin-top: 5px;">
Get your API key from <a href="https://console.anthropic.com/" target="_blank" style="color: #00ccff;">console.anthropic.com</a>
</small>
</div>
<div>
<label><strong>Clear All Data:</strong></label>
<button onclick="clearAllData()" style="background: #e74c3c;">🗑️ Clear Database</button>
<small style="color: #aaa; display: block; margin-top: 5px;">
This will delete all tables and data from browser storage
</small>
</div>
<div>
<button onclick="saveSettings()">💾 Save Settings</button>
<button class="cancel" onclick="hideSettingsModal()">Cancel</button>
</div>
</div>
<!-- Create Table Modal -->
<div id="createTableModal" class="modal hidden">
<h3>🤖 AI Table Creator</h3>
<p>Describe what kind of table you want to create:</p>
<textarea id="tableDescription" placeholder="e.g., 'Create a table for tracking my daily habits' or 'I need a customer database with contact info' or 'HTML pages storage for my favorite websites'" rows="4"></textarea>
<div class="table-preview" id="tablePreview" style="display: none;">
<strong>Generated Table Structure:</strong>
<div id="previewContent"></div>
</div>
<div>
<button onclick="generateTable()" id="generateBtn">🎯 Generate Table</button>
<button onclick="createTable()" id="createBtn" style="display: none;">✅ Create Table</button>
<button class="cancel" onclick="hideCreateTableModal()">Cancel</button>
</div>
</div>
<!-- View Table Modal -->
<div id="viewTableModal" class="modal hidden">
<h3 id="viewTableTitle">Table Details</h3>
<div id="viewTableContent"></div>
<div>
<button onclick="hideViewTableModal()">Close</button>
</div>
</div>
<!-- Table Selection Modal -->
<div id="tableSelectModal" class="modal hidden">
<h3>📊 Select Table for Data Entry</h3>
<p>Choose which table you'd like to add data to:</p>
<div id="tableSelectContent"></div>
<div>
<button class="cancel" onclick="hideTableSelectModal()">Cancel</button>
</div>
</div>
<!-- Add Data Modal -->
<div id="addDataModal" class="modal hidden">
<h3 id="addDataTitle">Add Data to Table</h3>
<div id="addDataForm"></div>
<div>
<button onclick="addRecord()" id="addRecordBtn">Add Record</button>
<button class="cancel" onclick="hideAddDataModal()">Cancel</button>
</div>
</div>
<!-- HTML Page Modal -->
<div id="htmlPageModal" class="modal hidden">
<h3 id="htmlPageTitle">HTML Pages</h3>
<div id="htmlPageContent"></div>
<div>
<button onclick="hideHtmlPageModal()">Close</button>
</div>
</div>
<script>
// Game state
let scene, camera, renderer, penguin, driveInScreen;
let moveState = { forward: false, backward: false, left: false, right: false };
let tables = [];
let database = {};
let currentTable = null;
let generatedTableStructure = null;
let isInDataArea = false;
let isInDriveInArea = false;
let wasInDataArea = false;
let wasInDriveInArea = false;
let mouseX = 0, mouseY = 0;
let isMouseDown = false;
let cameraRotation = { x: 0, y: 0 };
let apiKey = localStorage.getItem('claudeApiKey') || '';
// Local storage database functions
function saveDatabase() {
localStorage.setItem('penguinDatabase', JSON.stringify(database));
}
function loadDatabase() {
const saved = localStorage.getItem('penguinDatabase');
if (saved) {
try {
database = JSON.parse(saved);
} catch (e) {
console.error('Failed to load database:', e);
database = {};
}
}
// Initialize with sample data if empty
if (Object.keys(database).length === 0) {
initializeSampleData();
}
}
function initializeSampleData() {
// Sample tables
database.users = {
structure: {
id: { type: 'number', primaryKey: true },
name: { type: 'text' },
email: { type: 'text' },
created_at: { type: 'date' }
},
data: []
};
database.tasks = {
structure: {
id: { type: 'number', primaryKey: true },
title: { type: 'text' },
description: { type: 'text' },
completed: { type: 'boolean' },
due_date: { type: 'date' },
created_at: { type: 'date' }
},
data: []
};
database.websites = {
structure: {
id: { type: 'number', primaryKey: true },
title: { type: 'text' },
url: { type: 'text' },
html_content: { type: 'html' },
description: { type: 'text' },
created_at: { type: 'date' }
},
data: [
{
id: 1,
title: 'Welcome Page',
url: 'https://example.com/welcome',
description: 'A colorful welcome page with gradient background',
created_at: '2024-01-15',
html_content: '<!DOCTYPE html><html><head><title>Welcome to My Site</title><style>body { font-family: Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; } .container { max-width: 800px; margin: 0 auto; text-align: center; } h1 { font-size: 3em; margin-bottom: 20px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } p { font-size: 1.2em; line-height: 1.6; } .button { background: #ff6b6b; color: white; padding: 15px 30px; border: none; border-radius: 25px; font-size: 1.1em; margin: 20px; cursor: pointer; }</style></head><body><div class="container"><h1>🎉 Welcome to the Future!</h1><p>This is a sample webpage displaying in the Penguin Database World drive-in theater. Pretty cool, right?</p><button class="button">Get Started</button><button class="button">Learn More</button><p>You can create your own HTML pages and store them in the database, then view them on this big screen!</p></div></body></html>'
},
{
id: 2,
title: 'Dashboard',
url: 'https://example.com/dashboard',
description: 'A dark-themed analytics dashboard with metrics',
created_at: '2024-01-16',
html_content: '<!DOCTYPE html><html><head><title>Analytics Dashboard</title><style>body { font-family: "Segoe UI", sans-serif; background: #1a1a2e; color: #eee; margin: 0; padding: 20px; } .dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; } .card { background: #16213e; border-radius: 10px; padding: 20px; border-left: 4px solid #0f3460; } .card h3 { color: #64b5f6; margin-top: 0; } .metric { font-size: 2em; font-weight: bold; color: #4fc3f7; } .chart { height: 100px; background: linear-gradient(90deg, #4fc3f7 0%, #29b6f6 50%, #0288d1 100%); border-radius: 5px; margin: 10px 0; }</style></head><body><h1>📊 Analytics Dashboard</h1><div class="dashboard"><div class="card"><h3>Total Users</h3><div class="metric">1,234</div><div class="chart"></div></div><div class="card"><h3>Revenue</h3><div class="metric">$45,678</div><div class="chart"></div></div><div class="card"><h3>Active Sessions</h3><div class="metric">892</div><div class="chart"></div></div></div></body></html>'
},
{
id: 3,
title: 'Portfolio',
url: 'https://example.com/portfolio',
description: 'A clean portfolio website showcasing creative projects',
created_at: '2024-01-17',
html_content: '<!DOCTYPE html><html><head><title>Creative Portfolio</title><style>body { font-family: "Helvetica", sans-serif; background: #f8f9fa; margin: 0; padding: 40px; } .header { text-align: center; margin-bottom: 50px; } h1 { font-size: 3.5em; color: #2c3e50; margin: 0; } .subtitle { color: #7f8c8d; font-size: 1.2em; } .projects { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; } .project { background: white; border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .project h3 { color: #e74c3c; margin-top: 0; } .tag { background: #3498db; color: white; padding: 5px 10px; border-radius: 15px; font-size: 0.8em; display: inline-block; margin: 5px 5px 0 0; }</style></head><body><div class="header"><h1>✨ Creative Portfolio</h1><p class="subtitle">Showcasing amazing projects and ideas</p></div><div class="projects"><div class="project"><h3>🚀 Space Explorer</h3><p>An interactive 3D space exploration game built with Three.js</p><span class="tag">JavaScript</span><span class="tag">Three.js</span><span class="tag">WebGL</span></div><div class="project"><h3>🎨 Color Palette Generator</h3><p>AI-powered tool for creating beautiful color schemes</p><span class="tag">React</span><span class="tag">AI</span><span class="tag">Design</span></div><div class="project"><h3>📱 Mobile Weather App</h3><p>Clean, minimalist weather forecasting application</p><span class="tag">React Native</span><span class="tag">API</span><span class="tag">Mobile</span></div></div></body></html>'
}
]
};
saveDatabase();
}
function createTableInDatabase(name, structure) {
database[name] = {
structure: structure,
data: []
};
saveDatabase();
}
function addRecordToDatabase(tableName, record) {
if (!database[tableName]) return false;
// Generate ID
const maxId = database[tableName].data.reduce((max, row) => Math.max(max, row.id || 0), 0);
record.id = maxId + 1;
database[tableName].data.push(record);
saveDatabase();
return true;
}
function clearAllData() {
if (confirm('Are you sure you want to clear all data? This cannot be undone.')) {
localStorage.removeItem('penguinDatabase');
database = {};
initializeSampleData();
loadTables();
hideSettingsModal();
}
}
function loadTables() {
// Clear existing table objects
tables.forEach(table => scene.remove(table));
tables = [];
// Create table objects
let x = -40, z = -40;
Object.keys(database).forEach((tableName, index) => {
createTableObject(tableName, x, z);
x += 30;
if (x > 40) {
x = -40;
z += 30;
}
});
}
// Claude API functions
async function callClaudeAPI(description, apiKey) {
if (!apiKey) {
throw new Error('API key is required');
}
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-3-sonnet-20240229',
max_tokens: 1000,
messages: [{
role: 'user',
content: `Create a database table structure based on this description: "${description}"\n\nPlease respond with a JSON object in this exact format:\n{\n "name": "table_name",\n "structure": {\n "id": { "type": "number", "primaryKey": true },\n "column_name": { "type": "text|number|date|boolean|html" },\n "another_column": { "type": "text" }\n }\n}\n\nFor HTML/webpage storage requests, use type "html" for content fields. Always include an id field as primary key and created_at date field. Make the table name lowercase and descriptive.`
}]
})
});
if (!response.ok) {
throw new Error(`API call failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const content = data.content[0].text;
// Extract JSON from the response
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
} else {
throw new Error('No valid JSON found in response');
}
}
function extractTableName(description) {
const words = description.toLowerCase();
const tableTypes = ['users', 'customers', 'products', 'orders', 'tasks', 'habits', 'expenses', 'notes', 'inventory', 'contacts', 'websites', 'pages', 'html'];
for (const type of tableTypes) {
if (words.includes(type)) {
return type;
}
}
if (words.includes('customer') || words.includes('client')) return 'customers';
if (words.includes('product') || words.includes('item')) return 'products';
if (words.includes('task') || words.includes('todo')) return 'tasks';
if (words.includes('habit') || words.includes('daily')) return 'habits';
if (words.includes('expense') || words.includes('money')) return 'expenses';
if (words.includes('note') || words.includes('memo')) return 'notes';
if (words.includes('contact') || words.includes('person')) return 'contacts';
if (words.includes('html') || words.includes('webpage') || words.includes('website')) return 'websites';
return 'custom_table_' + Date.now();
}
function generateTableStructure(description) {
const structure = { id: { type: 'number', primaryKey: true } };
const words = description.toLowerCase();
if (words.includes('html') || words.includes('webpage') || words.includes('website')) {
structure.title = { type: 'text' };
structure.url = { type: 'text' };
structure.html_content = { type: 'html' };
structure.description = { type: 'text' };
structure.created_at = { type: 'date' };
return structure;
}
if (words.includes('name') || words.includes('customer') || words.includes('person')) {
structure.name = { type: 'text' };
}
if (words.includes('email') || words.includes('contact')) {
structure.email = { type: 'text' };
}
if (words.includes('phone') || words.includes('contact')) {
structure.phone = { type: 'text' };
}
if (words.includes('title') || words.includes('task')) {
structure.title = { type: 'text' };
}
if (words.includes('description') || words.includes('note')) {
structure.description = { type: 'text' };
}
if (words.includes('completed') || words.includes('done')) {
structure.completed = { type: 'boolean' };
}
if (words.includes('price') || words.includes('cost') || words.includes('expense')) {
structure.amount = { type: 'number' };
}
if (words.includes('category') || words.includes('type')) {
structure.category = { type: 'text' };
}
if (words.includes('habit') || words.includes('daily')) {
structure.habit_name = { type: 'text' };
structure.streak = { type: 'number' };
structure.last_completed = { type: 'date' };
}
structure.created_at = { type: 'date' };
return structure;
}
// Initialize the game
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
// Create camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 20);
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('gameContainer').appendChild(renderer.domElement);
// Create lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 50, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// Create ground
const groundGeometry = new THREE.PlaneGeometry(300, 300);
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Create data area indicator
const dataAreaGeometry = new THREE.PlaneGeometry(20, 20);
const dataAreaMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00, transparent: true, opacity: 0.3 });
const dataAreaMarker = new THREE.Mesh(dataAreaGeometry, dataAreaMaterial);
dataAreaMarker.rotation.x = -Math.PI / 2;
dataAreaMarker.position.set(80, 0.1, 80);
scene.add(dataAreaMarker);
// Create drive-in theater
createDriveInTheater();
// Create penguin
createPenguin();
// Set up controls
setupControls();
// Load database and tables
loadDatabase();
loadTables();
// Start render loop
animate();
// Update API status
updateApiStatus();
}
function createDriveInTheater() {
// Drive-in area marker
const driveInAreaGeometry = new THREE.PlaneGeometry(30, 25);
const driveInAreaMaterial = new THREE.MeshLambertMaterial({ color: 0xff00ff, transparent: true, opacity: 0.2 });
const driveInAreaMarker = new THREE.Mesh(driveInAreaGeometry, driveInAreaMaterial);
driveInAreaMarker.rotation.x = -Math.PI / 2;
driveInAreaMarker.position.set(-80, 0.1, -80);
scene.add(driveInAreaMarker);
// Screen support posts
const postGeometry = new THREE.CylinderGeometry(0.5, 0.5, 15, 8);
const postMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });
const leftPost = new THREE.Mesh(postGeometry, postMaterial);
leftPost.position.set(-95, 7.5, -90);
leftPost.castShadow = true;
scene.add(leftPost);
const rightPost = new THREE.Mesh(postGeometry, postMaterial);
rightPost.position.set(-65, 7.5, -90);
rightPost.castShadow = true;
scene.add(rightPost);
// Screen frame
const frameGeometry = new THREE.BoxGeometry(32, 20, 0.5);
const frameMaterial = new THREE.MeshLambertMaterial({ color: 0x222222 });
const frame = new THREE.Mesh(frameGeometry, frameMaterial);
frame.position.set(-80, 10, -90);
frame.castShadow = true;
scene.add(frame);
// Screen
const screenGeometry = new THREE.PlaneGeometry(30, 18);
const screenMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 });
driveInScreen = new THREE.Mesh(screenGeometry, screenMaterial);
driveInScreen.position.set(-80, 10, -89.5);
scene.add(driveInScreen);
// Add some decorative elements
const speakerGeometry = new THREE.BoxGeometry(1, 3, 1);
const speakerMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
for (let i = 0; i < 6; i++) {
const speaker = new THREE.Mesh(speakerGeometry, speakerMaterial);
speaker.position.set(-95 + i * 6, 1.5, -75);
speaker.castShadow = true;
scene.add(speaker);
}
}
function createPenguin() {
const penguinGroup = new THREE.Group();
// Body (main sphere)
const bodyGeometry = new THREE.SphereGeometry(1, 16, 16);
const bodyMaterial = new THREE.MeshLambertMaterial({ color: 0x2c2c2c });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.scale.set(1, 1.2, 0.8);
body.castShadow = true;
penguinGroup.add(body);
// Belly
const bellyGeometry = new THREE.SphereGeometry(0.7, 16, 16);
const bellyMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const belly = new THREE.Mesh(bellyGeometry, bellyMaterial);
belly.position.z = 0.3;
belly.scale.set(1, 1.1, 0.5);
penguinGroup.add(belly);
// Head
const headGeometry = new THREE.SphereGeometry(0.6, 16, 16);
const headMaterial = new THREE.MeshLambertMaterial({ color: 0x2c2c2c });
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 1.5;
head.castShadow = true;
penguinGroup.add(head);
// Beak
const beakGeometry = new THREE.ConeGeometry(0.1, 0.4, 8);
const beakMaterial = new THREE.MeshLambertMaterial({ color: 0xffa500 });
const beak = new THREE.Mesh(beakGeometry, beakMaterial);
beak.position.set(0, 1.5, 0.5);
beak.rotation.x = Math.PI / 2;
penguinGroup.add(beak);
// Eyes
const eyeGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const eyeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
leftEye.position.set(-0.2, 1.6, 0.4);
rightEye.position.set(0.2, 1.6, 0.4);
penguinGroup.add(leftEye);
penguinGroup.add(rightEye);
// Pupils
const pupilGeometry = new THREE.SphereGeometry(0.05, 8, 8);
const pupilMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 });
const leftPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
const rightPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
leftPupil.position.set(-0.2, 1.6, 0.45);
rightPupil.position.set(0.2, 1.6, 0.45);
penguinGroup.add(leftPupil);
penguinGroup.add(rightPupil);
// Feet
const footGeometry = new THREE.CylinderGeometry(0.3, 0.4, 0.2, 8);
const footMaterial = new THREE.MeshLambertMaterial({ color: 0xffa500 });
const leftFoot = new THREE.Mesh(footGeometry, footMaterial);
const rightFoot = new THREE.Mesh(footGeometry, footMaterial);
leftFoot.position.set(-0.4, -1.1, 0.2);
rightFoot.position.set(0.4, -1.1, 0.2);
leftFoot.castShadow = true;
rightFoot.castShadow = true;
penguinGroup.add(leftFoot);
penguinGroup.add(rightFoot);
penguinGroup.position.y = 1;
penguin = penguinGroup;
scene.add(penguin);
}
function setupControls() {
document.addEventListener('keydown', (event) => {
switch(event.code) {
case 'KeyW':
case 'ArrowUp':
moveState.forward = true;
break;
case 'KeyS':
case 'ArrowDown':
moveState.backward = true;
break;
case 'KeyA':
case 'ArrowLeft':
moveState.left = true;
break;
case 'KeyD':
case 'ArrowRight':
moveState.right = true;
break;
}
});
document.addEventListener('keyup', (event) => {
switch(event.code) {
case 'KeyW':
case 'ArrowUp':
moveState.forward = false;
break;
case 'KeyS':
case 'ArrowDown':
moveState.backward = false;
break;
case 'KeyA':
case 'ArrowLeft':
moveState.left = false;
break;
case 'KeyD':
case 'ArrowRight':
moveState.right = false;
break;
}
});
// Mouse controls
document.addEventListener('mousedown', (event) => {
if (event.button === 0) { // Left mouse button
isMouseDown = true;
mouseX = event.clientX;
mouseY = event.clientY;
}
});
document.addEventListener('mouseup', (event) => {
if (event.button === 0) {
isMouseDown = false;
}
});
document.addEventListener('mousemove', (event) => {
if (isMouseDown) {
const deltaX = event.clientX - mouseX;
const deltaY = event.clientY - mouseY;
cameraRotation.y -= deltaX * 0.01;
cameraRotation.x -= deltaY * 0.01;
// Clamp vertical rotation
cameraRotation.x = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, cameraRotation.x));
mouseX = event.clientX;
mouseY = event.clientY;
}
});
}
function updateMovement() {
const speed = 0.3;
let moved = false;
if (moveState.forward) {
penguin.position.z -= speed;
moved = true;
}
if (moveState.backward) {
penguin.position.z += speed;
moved = true;
}
if (moveState.left) {
penguin.position.x -= speed;
moved = true;
}
if (moveState.right) {
penguin.position.x += speed;
moved = true;
}
// Simple waddle animation
if (moved) {
penguin.rotation.z = Math.sin(Date.now() * 0.01) * 0.1;
penguin.position.y = 1 + Math.abs(Math.sin(Date.now() * 0.015)) * 0.1;
}
// Update camera to follow penguin with mouse rotation
const distance = 20;
const height = 10;
camera.position.x = penguin.position.x + Math.sin(cameraRotation.y) * distance;
camera.position.z = penguin.position.z + Math.cos(cameraRotation.y) * distance;
camera.position.y = penguin.position.y + height + Math.sin(cameraRotation.x) * 10;
camera.lookAt(penguin.position);
// Check proximity to tables and areas
checkProximity();
updateStatus();
}
function checkProximity() {
const penguinPos = penguin.position;
let nearbyTable = null;
// Check tables
tables.forEach(table => {
const distance = penguinPos.distanceTo(table.position);
if (distance < 5) {
nearbyTable = table;
}
});
// Check data area
const dataAreaDistance = penguinPos.distanceTo(new THREE.Vector3(80, 0, 80));
const newIsInDataArea = dataAreaDistance < 15;
// Check drive-in area
const driveInDistance = penguinPos.distanceTo(new THREE.Vector3(-80, 0, -80));
const newIsInDriveInArea = driveInDistance < 20;
// Handle data area entry/exit
if (newIsInDataArea && !wasInDataArea) {
showTableSelectModal();
} else if (!newIsInDataArea && wasInDataArea) {
hideTableSelectModal();
hideAddDataModal();
}
// Handle drive-in area entry/exit
if (newIsInDriveInArea && !wasInDriveInArea) {
// Just entered drive-in area - show HTML pages from websites table
if (database.websites && database.websites.data && database.websites.data.length > 0) {
showHtmlPageModal('websites');
}
} else if (!newIsInDriveInArea && wasInDriveInArea) {
// Just left drive-in area
hideHtmlPageModal();
}
wasInDataArea = isInDataArea;
wasInDriveInArea = isInDriveInArea;
isInDataArea = newIsInDataArea;
isInDriveInArea = newIsInDriveInArea;
// Update UI
document.getElementById('dataArea').classList.toggle('visible', isInDataArea);
document.getElementById('driveInArea').classList.toggle('visible', isInDriveInArea);
// Show table interaction (only when not in special areas)
if (nearbyTable && nearbyTable !== currentTable && !isInDataArea && !isInDriveInArea) {
currentTable = nearbyTable;
showViewTableModal(currentTable.userData.tableName);
} else if (!nearbyTable || isInDataArea || isInDriveInArea) {
if (currentTable && !isInDataArea && !isInDriveInArea) {
hideViewTableModal();
}
currentTable = null;
}
}
function updateStatus() {
const pos = penguin.position;
document.getElementById('penguinPos').textContent =
`Position: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)}, ${pos.z.toFixed(1)})`;
document.getElementById('nearbyTable').textContent =
currentTable ? `Nearby: ${currentTable.userData.tableName}` : 'Nearby: None';
document.getElementById('tableCount').textContent = `Tables: ${tables.length}`;
}
function updateApiStatus() {
const statusEl = document.getElementById('apiStatus');
if (apiKey) {
statusEl.textContent = '🤖 AI: Connected';
statusEl.className = 'api-status success';
statusEl.style.display = 'block';
} else {
statusEl.textContent = '⚠️ AI: No API Key';
statusEl.className = 'api-status error';
statusEl.style.display = 'block';
}
}
function createTableObject(name, x, z) {
const tableGroup = new THREE.Group();
// Table base
const baseGeometry = new THREE.CylinderGeometry(2, 2.5, 0.5, 8);
const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.y = 0.25;
base.castShadow = true;
tableGroup.add(base);
// Table top
const topGeometry = new THREE.CylinderGeometry(2.5, 2.5, 0.2, 8);
const topMaterial = new THREE.MeshLambertMaterial({ color: 0xDEB887 });
const top = new THREE.Mesh(topGeometry, topMaterial);
top.position.y = 0.6;
top.castShadow = true;
tableGroup.add(top);
// Floating hologram effect
const holoGeometry = new THREE.BoxGeometry(3, 2, 0.1);
const holoMaterial = new THREE.MeshLambertMaterial({
color: database[name]?.structure?.html_content ? 0xff00ff : 0x00ffff,
transparent: true,
opacity: 0.6
});
const holo = new THREE.Mesh(holoGeometry, holoMaterial);
holo.position.y = 2;
tableGroup.add(holo);
// Create text sprite for table name
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 128;
context.fillStyle = '#ffffff';
context.font = 'bold 24px Arial';
context.textAlign = 'center';
context.fillText(name, 128, 64);
const texture = new THREE.CanvasTexture(canvas);
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(spriteMaterial);
sprite.position.y = 3;
sprite.scale.set(4, 2, 1);
tableGroup.add(sprite);
tableGroup.position.set(x, 0, z);
tableGroup.userData = { tableName: name };
scene.add(tableGroup);
tables.push(tableGroup);
return tableGroup;
}
// AI Table Generation
async function generateTable() {
const description = document.getElementById('tableDescription').value.trim();
if (!description) {
alert('Please enter a description for your table');
return;
}
const generateBtn = document.getElementById('generateBtn');
generateBtn.classList.add('loading');
generateBtn.textContent = '🤖 Generating...';
try {
if (apiKey) {
try {
// Try Claude API first
generatedTableStructure = await callClaudeAPI(description, apiKey);
} catch (apiError) {
console.warn('API failed, using fallback:', apiError.message);
// Fallback to local generation
const tableName = extractTableName(description);
const structure = generateTableStructure(description);
generatedTableStructure = { name: tableName, structure: structure };
}
} else {
// Local generation
const tableName = extractTableName(description);
const structure = generateTableStructure(description);
generatedTableStructure = { name: tableName, structure: structure };
}
displayTablePreview(generatedTableStructure.name, generatedTableStructure.structure);
document.getElementById('createBtn').style.display = 'inline-block';
} catch (error) {
console.error('Error generating table:', error);
alert('Error generating table: ' + error.message);
} finally {
generateBtn.classList.remove('loading');
generateBtn.textContent = '🎯 Generate Table';
}
}
function displayTablePreview(name, structure) {
const preview = document.getElementById('tablePreview');
const content = document.getElementById('previewContent');
let html = `<strong>Table Name:</strong> ${name}<br><br><strong>Columns:</strong><br>`;
Object.entries(structure).forEach(([column, info]) => {
html += `• ${column} (${info.type})${info.primaryKey ? ' - PRIMARY KEY' : ''}<br>`;
});
content.innerHTML = html;
preview.style.display = 'block';
}
function createTable() {
if (!generatedTableStructure) return;
const { name, structure } = generatedTableStructure;
// Check if table already exists
if (database[name]) {
if (!confirm(`Table "${name}" already exists. Replace it?`)) {
return;
}
}
// Create in database
createTableInDatabase(name, structure);
// Reload tables
loadTables();
hideCreateTableModal();
generatedTableStructure = null;
}
function showTableSelectModal() {
const modal = document.getElementById('tableSelectModal');
const content = document.getElementById('tableSelectContent');
let html = '';
const tableNames = Object.keys(database);
if (tableNames.length === 0) {
html = '<p><em>No tables available. Create some tables first!</em></p>';
} else {
tableNames.forEach(tableName => {
const table = database[tableName];
const recordCount = table.data.length;
const columnCount = Object.keys(table.structure).length;
html += `<div class="table-select-item" onclick="selectTableForData('${tableName}')">`;
html += `<h4>📊 ${tableName}</h4>`;
html += `<div class="table-info">${columnCount} columns • ${recordCount} records</div>`;
html += '</div>';
});
}
content.innerHTML = html;
modal.classList.remove('hidden');
}
function hideTableSelectModal() {
document.getElementById('tableSelectModal').classList.add('hidden');
}
function selectTableForData(tableName) {
hideTableSelectModal();
showAddDataModal(tableName);
}
// Settings functions
function showSettingsModal() {
document.getElementById('settingsModal').classList.remove('hidden');
document.getElementById('apiKeyInput').value = apiKey;
}
function hideSettingsModal() {
document.getElementById('settingsModal').classList.add('hidden');
}
function saveSettings() {
apiKey = document.getElementById('apiKeyInput').value.trim();
localStorage.setItem('claudeApiKey', apiKey);
updateApiStatus();
hideSettingsModal();
}
// Modal functions
function showCreateTableModal() {
document.getElementById('createTableModal').classList.remove('hidden');
document.getElementById('tableDescription').focus();
}
function hideCreateTableModal() {
document.getElementById('createTableModal').classList.add('hidden');
document.getElementById('tableDescription').value = '';
document.getElementById('tablePreview').style.display = 'none';
document.getElementById('createBtn').style.display = 'none';
generatedTableStructure = null;
}
function showViewTableModal(tableName) {
if (!database[tableName]) return;
const modal = document.getElementById('viewTableModal');
const title = document.getElementById('viewTableTitle');
const content = document.getElementById('viewTableContent');
title.textContent = `📊 ${tableName} Table`;
const table = database[tableName];
let html = '<div class="table-preview">';
html += '<strong>Structure:</strong><br>';
Object.entries(table.structure).forEach(([column, info]) => {
html += `• ${column} (${info.type})${info.primaryKey ? ' - PRIMARY KEY' : ''}<br>`;
});
html += '</div>';
if (table.data.length > 0) {
html += '<div class="table-data"><strong>Data:</strong>';
table.data.forEach((row, index) => {
html += '<div class="table-row">';
Object.entries(row).forEach(([key, value]) => {
if (key === 'html_content' && value) {
html += `<strong>${key}:</strong> [HTML Content - ${value.length} characters]<br>`;
} else {
html += `<strong>${key}:</strong> ${value}<br>`;
}
});
html += '</div>';
});
html += '</div>';
} else {
html += '<p><em>No data in this table yet. Go to the Data Area to add records!</em></p>';
}
content.innerHTML = html;
modal.classList.remove('hidden');
}
function hideViewTableModal() {
document.getElementById('viewTableModal').classList.add('hidden');
}
function showAddDataModal(tableName) {
if (!database[tableName]) return;
const modal = document.getElementById('addDataModal');
const title = document.getElementById('addDataTitle');
const form = document.getElementById('addDataForm');
title.textContent = `Add Data to ${tableName}`;
const table = database[tableName];
let html = '';
Object.entries(table.structure).forEach(([column, info]) => {
if (info.primaryKey) return; // Skip primary key
html += `<label><strong>${column} (${info.type}):</strong></label>`;
if (info.type === 'text' || info.type === 'url') {
html += `<input type="text" id="field_${column}" placeholder="Enter ${column}">`;
} else if (info.type === 'number') {
html += `<input type="number" id="field_${column}" placeholder="Enter ${column}">`;
} else if (info.type === 'date') {
html += `<input type="date" id="field_${column}">`;
} else if (info.type === 'boolean') {
html += `<select id="field_${column}"><option value="true">True</option><option value="false">False</option></select>`;
} else if (info.type === 'html') {
html += `<textarea id="field_${column}" rows="6" placeholder="Enter HTML content"></textarea>`;
}
});
form.innerHTML = html;
modal.classList.remove('hidden');
modal.setAttribute('data-table', tableName);
}
function hideAddDataModal() {
document.getElementById('addDataModal').classList.add('hidden');
}
function addRecord() {
const modal = document.getElementById('addDataModal');
const tableName = modal.getAttribute('data-table');
if (!database[tableName]) return;
const table = database[tableName];
const newRecord = {};
// Get form data
Object.entries(table.structure).forEach(([column, info]) => {
if (info.primaryKey) return;
const field = document.getElementById(`field_${column}`);
if (field) {
let value = field.value;
if (info.type === 'number') {
value = parseFloat(value) || 0;
} else if (info.type === 'boolean') {
value = value === 'true';
} else if (info.type === 'date' && column === 'created_at' && !value) {
value = new Date().toISOString().split('T')[0];
}
newRecord[column] = value;
}
});
// Add created_at if it exists and wasn't filled
if (table.structure.created_at && !newRecord.created_at) {
newRecord.created_at = new Date().toISOString().split('T')[0];
}
if (addRecordToDatabase(tableName, newRecord)) {
hideAddDataModal();
loadTables(); // Reload to update hologram colors
} else {
alert('Failed to add record');
}
}
function showHtmlPageModal(tableName) {
if (!database[tableName] || !database[tableName].structure.html_content) return;
const modal = document.getElementById('htmlPageModal');
const title = document.getElementById('htmlPageTitle');
const content = document.getElementById('htmlPageContent');
title.textContent = `🎬 ${tableName} - HTML Pages`;
const table = database[tableName];
let html = '<div class="table-data">';
if (table.data.length > 0) {
html += '<strong>Available Pages:</strong><br>';
table.data.forEach((row, index) => {
if (row.html_content) {
html += `<div class="html-page-item" onclick="loadHtmlOnScreen('${tableName}', ${index})">`;
html += `<strong>${row.title || 'Untitled'}</strong><br>`;
html += `<small>${row.url || 'No URL'}</small><br>`;
html += `<em>${row.description || 'No description'}</em>`;
html += '</div>';
}
});
} else {
html += '<p><em>No HTML pages in this table yet. Go to the Data Area to add some!</em></p>';
}
html += '</div>';
content.innerHTML = html;
modal.classList.remove('hidden');
}
function hideHtmlPageModal() {
document.getElementById('htmlPageModal').classList.add('hidden');
}
function loadHtmlOnScreen(tableName, index) {
const table = database[tableName];
if (!table || !table.data[index] || !table.data[index].html_content) return;
const htmlContent = table.data[index].html_content;
// Create canvas for rendering HTML preview
const canvas = document.createElement('canvas');
canvas.width = 1920;
canvas.height = 1080;
const ctx = canvas.getContext('2d');
// Create a visual representation of the HTML page
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Add a border
ctx.strokeStyle = '#cccccc';
ctx.lineWidth = 4;
ctx.strokeRect(0, 0, canvas.width, canvas.height);
const title = table.data[index].title || 'Untitled';
const url = table.data[index].url || '';
// Header area (browser-like)
ctx.fillStyle = '#2c3e50';
ctx.fillRect(0, 0, canvas.width, 120);
// Browser header
ctx.fillStyle = '#34495e';
ctx.fillRect(0, 0, canvas.width, 60);
// Browser buttons
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.arc(30, 30, 12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#f39c12';
ctx.beginPath();
ctx.arc(60, 30, 12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#27ae60';
ctx.beginPath();
ctx.arc(90, 30, 12, 0, Math.PI * 2);
ctx.fill();
// URL bar
ctx.fillStyle = '#ecf0f1';
ctx.fillRect(140, 15, canvas.width - 280, 30);
ctx.fillStyle = '#7f8c8d';
ctx.font = '20px Arial';
ctx.fillText(url || 'https://example.com', 155, 35);
// Page title
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 32px Arial';
ctx.fillText(title, 40, 100);
// Parse HTML and create visual representation
const htmlDoc = new DOMParser().parseFromString(htmlContent, 'text/html');
let contentY = 160;
// Determine background based on HTML content
if (htmlContent.includes('gradient') || htmlContent.includes('#667eea')) {
const gradient = ctx.createLinearGradient(0, 120, canvas.width, canvas.height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(0, 120, canvas.width, canvas.height - 120);
} else if (htmlContent.includes('#1a1a2e') || htmlContent.includes('dark')) {
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 120, canvas.width, canvas.height - 120);
} else {
ctx.fillStyle = '#f8f9fa';
ctx.fillRect(0, 120, canvas.width, canvas.height - 120);
}
// Extract and display content
const headings = htmlDoc.querySelectorAll('h1, h2, h3');
const paragraphs = htmlDoc.querySelectorAll('p');
ctx.fillStyle = htmlContent.includes('dark') || htmlContent.includes('#1a1a2e') ? '#ffffff' : '#2c3e50';
// Display headings
headings.forEach((heading, i) => {
if (i < 3 && contentY < canvas.height - 100) {
const size = heading.tagName === 'H1' ? '48px' : heading.tagName === 'H2' ? '36px' : '28px';
ctx.font = `bold ${size} Arial`;
const text = heading.textContent.substring(0, 80);
ctx.fillText(text, 60, contentY);
contentY += parseInt(size) + 20;
}
});
// Display paragraphs
ctx.font = '24px Arial';
paragraphs.forEach((p, i) => {
if (i < 5 && contentY < canvas.height - 60) {
const text = p.textContent.substring(0, 120);
const words = text.split(' ');
let line = '';
for (let j = 0; j < words.length; j++) {
const testLine = line + words[j] + ' ';
const metrics = ctx.measureText(testLine);
if (metrics.width > canvas.width - 120 && j > 0) {
ctx.fillText(line, 60, contentY);
line = words[j] + ' ';
contentY += 30;
if (contentY > canvas.height - 60) break;
} else {
line = testLine;
}
}
ctx.fillText(line, 60, contentY);
contentY += 40;
}
});
// Add buttons if mentioned in HTML
if (htmlContent.includes('button') || htmlContent.includes('btn')) {
ctx.fillStyle = '#3498db';
ctx.fillRect(60, contentY + 20, 200, 50);
ctx.fillRect(280, contentY + 20, 200, 50);
ctx.fillStyle = '#ffffff';
ctx.font = '20px Arial';
ctx.fillText('Get Started', 85, contentY + 50);
ctx.fillText('Learn More', 305, contentY + 50);
}
// Update screen texture
const texture = new THREE.CanvasTexture(canvas);
driveInScreen.material.map = texture;
driveInScreen.material.color.setHex(0xffffff);
driveInScreen.material.needsUpdate = true;
hideHtmlPageModal();
}
function teleportToDataArea() {
penguin.position.set(80, 1, 80);
}
function teleportToDriveIn() {
penguin.position.set(-80, 1, -60);
}
function teleportToMainArea() {
penguin.position.set(0, 1, 0);
}
function animate() {
requestAnimationFrame(animate);
updateMovement();
// Animate table holograms
tables.forEach(table => {
const holo = table.children.find(child => child.material && child.material.transparent);
if (holo) {
holo.rotation.y += 0.01;
holo.material.opacity = 0.4 + Math.sin(Date.now() * 0.003) * 0.2;
}
});
renderer.render(scene, camera);
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start the game
init();
</script>
</body>
</html>