Compare commits
1 Commits
main
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d759416fc6 |
60
index.html
60
index.html
@@ -22,13 +22,56 @@
|
||||
position: fixed; inset: 0; z-index: 100;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: #000;
|
||||
color: #00ff41; font-size: 14px; letter-spacing: 4px;
|
||||
text-shadow: 0 0 12px #00ff41;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
#loading-screen.hidden { display: none; }
|
||||
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
||||
#loading-screen span { animation: blink 1.2s ease-in-out infinite; }
|
||||
|
||||
.loading-content {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 18px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* ASCII logo */
|
||||
#ascii-logo {
|
||||
white-space: pre;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: clamp(6px, 1.1vw, 13px);
|
||||
line-height: 1.25;
|
||||
color: #002200;
|
||||
text-shadow: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* Lit characters glow green */
|
||||
#ascii-logo .ac { color: #002200; transition: color 0.15s, text-shadow 0.15s; }
|
||||
#ascii-logo .ac.lit {
|
||||
color: #00ff41;
|
||||
text-shadow: 0 0 8px #00ff41, 0 0 20px #00cc33;
|
||||
}
|
||||
|
||||
/* Progress bar */
|
||||
#loading-bar-track {
|
||||
width: clamp(220px, 50vw, 500px);
|
||||
height: 3px;
|
||||
background: #001800;
|
||||
border: 1px solid #003300;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#loading-bar-fill {
|
||||
height: 100%; width: 0%;
|
||||
background: linear-gradient(90deg, #00aa22, #00ff41);
|
||||
box-shadow: 0 0 10px #00ff41;
|
||||
transition: width 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Percent + status */
|
||||
#loading-percent {
|
||||
color: #00ff41; font-size: 11px; letter-spacing: 3px;
|
||||
text-shadow: 0 0 8px #00ff41;
|
||||
}
|
||||
#loading-msg {
|
||||
color: #007722; font-size: 10px; letter-spacing: 4px;
|
||||
}
|
||||
|
||||
#ui-overlay {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
@@ -162,7 +205,14 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading-screen"><span>INITIALIZING...</span></div>
|
||||
<div id="loading-screen">
|
||||
<div class="loading-content">
|
||||
<pre id="ascii-logo"></pre>
|
||||
<div id="loading-bar-track"><div id="loading-bar-fill"></div></div>
|
||||
<div id="loading-percent">0%</div>
|
||||
<div id="loading-msg">INITIALIZING...</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- WebGL context loss overlay (iPad PWA, GPU resets) -->
|
||||
<div id="webgl-recovery-overlay" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.9);color:#00ff41;font-family:monospace;align-items:center;justify-content:center;flex-direction:column">
|
||||
<p style="font-size:1.4rem">RECOVERING WebGL CONTEXT…</p>
|
||||
|
||||
86
js/loading.js
Normal file
86
js/loading.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Loading screen — ASCII art Timmy logo with progressive character reveal.
|
||||
*
|
||||
* Usage:
|
||||
* import { initLoadingArt, setLoadingProgress } from './loading.js';
|
||||
* initLoadingArt(); // call once on page load
|
||||
* setLoadingProgress(50, 'MSG'); // 0–100, optional status message
|
||||
*/
|
||||
|
||||
const TIMMY_ASCII = `
|
||||
████████╗██╗███╗ ███╗███╗ ███╗██╗ ██╗
|
||||
╚══██╔══╝██║████╗ ████║████╗ ████║╚██╗ ██╔╝
|
||||
██║ ██║██╔████╔██║██╔████╔██║ ╚████╔╝
|
||||
██║ ██║██║╚██╔╝██║██║╚██╔╝██║ ╚██╔╝
|
||||
██║ ██║██║ ╚═╝ ██║██║ ╚═╝ ██║ ██║
|
||||
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
|
||||
|
||||
┌───────────────────────────────┐
|
||||
│ SOVEREIGN AI · SOUL ON BTC │
|
||||
└───────────────────────────────┘
|
||||
`.trimStart();
|
||||
|
||||
/** All non-whitespace char indices in the ASCII art, randomised for scatter reveal */
|
||||
let _charSpans = [];
|
||||
let _totalChars = 0;
|
||||
|
||||
/**
|
||||
* Initialise the loading screen: build character spans for the ASCII art.
|
||||
* Call once before the first setLoadingProgress().
|
||||
*/
|
||||
export function initLoadingArt() {
|
||||
const logoEl = document.getElementById('ascii-logo');
|
||||
if (!logoEl) return;
|
||||
|
||||
// Build span-per-char markup — whitespace stays as plain text nodes for layout.
|
||||
const chars = [...TIMMY_ASCII];
|
||||
let html = '';
|
||||
let idx = 0;
|
||||
for (const ch of chars) {
|
||||
if (ch === '\n') {
|
||||
html += '\n';
|
||||
} else if (ch === ' ') {
|
||||
html += ' ';
|
||||
} else {
|
||||
html += `<span class="ac" data-i="${idx}">${ch}</span>`;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
_totalChars = idx;
|
||||
logoEl.innerHTML = html;
|
||||
|
||||
// Cache span elements for fast access
|
||||
_charSpans = Array.from(logoEl.querySelectorAll('.ac'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update loading progress.
|
||||
* @param {number} percent 0–100
|
||||
* @param {string} [msg] Optional status line text
|
||||
*/
|
||||
export function setLoadingProgress(percent, msg) {
|
||||
const pct = Math.max(0, Math.min(100, percent));
|
||||
|
||||
// How many chars should be "lit" at this progress level
|
||||
const litCount = Math.round((_totalChars * pct) / 100);
|
||||
|
||||
for (let i = 0; i < _charSpans.length; i++) {
|
||||
if (i < litCount) {
|
||||
_charSpans[i].classList.add('lit');
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bar fill
|
||||
const bar = document.getElementById('loading-bar-fill');
|
||||
if (bar) bar.style.width = `${pct}%`;
|
||||
|
||||
// Percentage label
|
||||
const pctEl = document.getElementById('loading-percent');
|
||||
if (pctEl) pctEl.textContent = `${Math.round(pct)}%`;
|
||||
|
||||
// Status message
|
||||
if (msg) {
|
||||
const msgEl = document.getElementById('loading-msg');
|
||||
if (msgEl) msgEl.textContent = msg;
|
||||
}
|
||||
}
|
||||
22
js/main.js
22
js/main.js
@@ -8,6 +8,7 @@ import { initUI, updateUI } from './ui.js';
|
||||
import { initInteraction, updateControls, disposeInteraction } from './interaction.js';
|
||||
import { initWebSocket, getConnectionState, getJobCount } from './websocket.js';
|
||||
import { initVisitor } from './visitor.js';
|
||||
import { initLoadingArt, setLoadingProgress } from './loading.js';
|
||||
|
||||
let running = false;
|
||||
let canvas = null;
|
||||
@@ -22,26 +23,40 @@ let canvas = null;
|
||||
* Agent state map captured just before teardown; reapplied after initAgents.
|
||||
*/
|
||||
function buildWorld(firstInit, stateSnapshot) {
|
||||
if (firstInit) setLoadingProgress(10, 'RENDERING ENGINE...');
|
||||
const { scene, camera, renderer } = initWorld(canvas);
|
||||
canvas = renderer.domElement;
|
||||
|
||||
if (firstInit) setLoadingProgress(30, 'EFFECTS SYSTEM...');
|
||||
initEffects(scene);
|
||||
|
||||
if (firstInit) setLoadingProgress(50, 'SPAWNING AGENTS...');
|
||||
initAgents(scene);
|
||||
|
||||
if (stateSnapshot) {
|
||||
applyAgentStates(stateSnapshot);
|
||||
}
|
||||
|
||||
if (firstInit) setLoadingProgress(70, 'ARMING CONTROLS...');
|
||||
initInteraction(camera, renderer);
|
||||
|
||||
if (firstInit) {
|
||||
setLoadingProgress(80, 'LOADING INTERFACE...');
|
||||
initUI();
|
||||
|
||||
setLoadingProgress(90, 'CONNECTING TO GRID...');
|
||||
initWebSocket(scene);
|
||||
|
||||
setLoadingProgress(95, 'INITIALIZING VISITOR...');
|
||||
initVisitor();
|
||||
|
||||
// Dismiss loading screen
|
||||
setLoadingProgress(100, 'WELCOME TO TOWER WORLD');
|
||||
|
||||
// Dismiss loading screen after a brief moment so 100% is visible
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
if (loadingScreen) loadingScreen.classList.add('hidden');
|
||||
if (loadingScreen) {
|
||||
setTimeout(() => loadingScreen.classList.add('hidden'), 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce resize to 1 call per frame
|
||||
@@ -117,6 +132,9 @@ function teardown({ scene, renderer, ac }) {
|
||||
function main() {
|
||||
const $overlay = document.getElementById('webgl-recovery-overlay');
|
||||
|
||||
initLoadingArt();
|
||||
setLoadingProgress(0, 'BOOTING MATRIX...');
|
||||
|
||||
let handle = buildWorld(true, null);
|
||||
|
||||
// WebGL context loss recovery (iPad PWA, GPU driver reset, etc.)
|
||||
|
||||
Reference in New Issue
Block a user