|
.control-group input[type="checkbox"] { |
|
width: auto; |
|
margin-right: 5px; |
|
}<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>NGVT: Vortex Torus - Compatible Version</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
|
<style> |
|
body { |
|
margin: 0; |
|
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%); |
|
font-family: 'Arial', sans-serif; |
|
overflow: hidden; |
|
color: white; |
|
} |
|
|
|
#container { |
|
position: relative; |
|
width: 100vw; |
|
height: 100vh; |
|
} |
|
|
|
#info { |
|
position: absolute; |
|
top: 20px; |
|
left: 20px; |
|
z-index: 100; |
|
background: rgba(0, 0, 0, 0.9); |
|
padding: 20px; |
|
border-radius: 10px; |
|
border: 1px solid #00ffff; |
|
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); |
|
max-width: 280px; |
|
} |
|
|
|
#info h1 { |
|
margin: 0 0 10px 0; |
|
color: #00ffff; |
|
font-size: 20px; |
|
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); |
|
} |
|
|
|
#info h2 { |
|
margin: 15px 0 5px 0; |
|
color: #ff6b6b; |
|
font-size: 14px; |
|
} |
|
|
|
#info p { |
|
margin: 3px 0; |
|
font-size: 12px; |
|
color: #cccccc; |
|
} |
|
|
|
#controls { |
|
position: absolute; |
|
bottom: 20px; |
|
left: 20px; |
|
z-index: 100; |
|
background: rgba(0, 0, 0, 0.9); |
|
padding: 15px; |
|
border-radius: 10px; |
|
border: 1px solid #ff6b6b; |
|
} |
|
|
|
.control-group { |
|
margin: 10px 0; |
|
} |
|
|
|
.control-group label { |
|
display: block; |
|
color: #ff6b6b; |
|
font-size: 11px; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.control-group input { |
|
width: 120px; |
|
} |
|
|
|
#legend { |
|
position: absolute; |
|
bottom: 20px; |
|
right: 20px; |
|
z-index: 100; |
|
background: rgba(0, 0, 0, 0.9); |
|
padding: 15px; |
|
border-radius: 10px; |
|
border: 1px solid #4ecdc4; |
|
} |
|
|
|
.legend-item { |
|
display: flex; |
|
align-items: center; |
|
margin: 6px 0; |
|
} |
|
|
|
.legend-color { |
|
width: 16px; |
|
height: 16px; |
|
margin-right: 8px; |
|
border-radius: 3px; |
|
} |
|
|
|
.legend-text { |
|
font-size: 11px; |
|
color: #cccccc; |
|
} |
|
|
|
#error-message { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
background: rgba(255, 0, 0, 0.1); |
|
border: 1px solid #ff6b6b; |
|
padding: 20px; |
|
border-radius: 10px; |
|
color: #ff6b6b; |
|
display: none; |
|
text-align: center; |
|
} |
|
|
|
#fallback-canvas { |
|
display: none; |
|
border: 1px solid #4ecdc4; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="container"> |
|
<div id="error-message"> |
|
<h3>WebGL Not Available</h3> |
|
<p>Your browser doesn't support WebGL or it's disabled.</p> |
|
<p>Falling back to 2D visualization...</p> |
|
</div> |
|
|
|
<div id="info"> |
|
<h1>NGVT Vortex Torus</h1> |
|
<p><strong>Directional Flow Visualization</strong></p> |
|
|
|
<h2>Torus Parameters:</h2> |
|
<p>Rings: 1-5 concentric</p> |
|
<p>Particles: ON SAME PLANE</p> |
|
<p>Drag to orbit manually</p> |
|
|
|
<h2>Code Flow:</h2> |
|
<p>6 colored spiral bands</p> |
|
<p>Tight/loose spiral control</p> |
|
<p>Multi-ring vortex</p> |
|
|
|
<h2>Performance:</h2> |
|
<p>SWE-bench: 98.33%</p> |
|
<p>Speed: 7.4× faster</p> |
|
</div> |
|
|
|
<div id="controls"> |
|
<h3 style="color: #ff6b6b; margin-top: 0; font-size: 14px;">Flow Controls</h3> |
|
<div class="control-group"> |
|
<label>Flow Speed:</label> |
|
<input type="range" id="flowSpeed" min="0.1" max="2.0" step="0.1" value="0.8"> |
|
</div> |
|
<div class="control-group"> |
|
<label>Number of Rings:</label> |
|
<input type="range" id="numberOfRings" min="1" max="5" step="1" value="1"> |
|
</div> |
|
<div class="control-group"> |
|
<label>Spiral Pitch:</label> |
|
<input type="range" id="spiralPitch" min="0.5" max="4.0" step="0.1" value="2.0"> |
|
</div> |
|
<div class="control-group"> |
|
<label>Spiral Tightness:</label> |
|
<input type="range" id="spiralTightness" min="2" max="20" step="1" value="8"> |
|
</div> |
|
<div class="control-group"> |
|
<label>Ring Size:</label> |
|
<input type="range" id="ringSize" min="2.0" max="8.0" step="0.2" value="4.0"> |
|
</div> |
|
<div class="control-group"> |
|
<label>Zoom Level:</label> |
|
<input type="range" id="zoomLevel" min="5.0" max="30.0" step="1.0" value="15.0"> |
|
</div> |
|
<div class="control-group"> |
|
<label>Rotation:</label> |
|
<input type="checkbox" id="rotationToggle" checked> |
|
<span style="color: #ff6b6b; font-size: 11px; margin-left: 5px;">Auto Orbit</span> |
|
</div> |
|
<div class="control-group"> |
|
<label>Vortex Intensity:</label> |
|
<input type="range" id="vortexIntensity" min="0.1" max="1.5" step="0.1" value="0.8"> |
|
</div> |
|
</div> |
|
|
|
<div id="legend"> |
|
<h3 style="color: #4ecdc4; margin-top: 0; font-size: 14px;">Flow Elements</h3> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #00ffff; box-shadow: 0 0 8px #00ffff;"></div> |
|
<div class="legend-text">Functions</div> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #ff6b6b; box-shadow: 0 0 8px #ff6b6b;"></div> |
|
<div class="legend-text">Variables</div> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #4ecdc4; box-shadow: 0 0 8px #4ecdc4;"></div> |
|
<div class="legend-text">Keywords</div> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #ffd93d; box-shadow: 0 0 8px #ffd93d;"></div> |
|
<div class="legend-text">Operators</div> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #a8e6cf; box-shadow: 0 0 8px #a8e6cf;"></div> |
|
<div class="legend-text">Comments</div> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #dda0dd; box-shadow: 0 0 8px #dda0dd;"></div> |
|
<div class="legend-text">Strings</div> |
|
</div> |
|
</div> |
|
|
|
<canvas id="fallback-canvas" width="800" height="600"></canvas> |
|
</div> |
|
|
|
<script> |
|
|
|
let scene, camera, renderer; |
|
let torus, codeSegments = []; |
|
let animationId; |
|
|
|
|
|
let flowSpeed = 0.8; |
|
let spiralPitch = 2.0; |
|
let ringSize = 4.0; |
|
let vortexIntensity = 0.8; |
|
let zoomLevel = 15.0; |
|
let rotationEnabled = true; |
|
let numberOfRings = 1; |
|
let spiralTightness = 8; |
|
|
|
|
|
let isDragging = false; |
|
let previousMousePosition = { x: 0, y: 0 }; |
|
let cameraAngleX = 0; |
|
let cameraAngleY = 0; |
|
|
|
|
|
function isWebGLAvailable() { |
|
try { |
|
const canvas = document.createElement('canvas'); |
|
return !!(window.WebGLRenderingContext && ( |
|
canvas.getContext('webgl') || |
|
canvas.getContext('experimental-webgl') || |
|
canvas.getContext('webgl2') |
|
)); |
|
} catch (e) { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
function initThreeJS() { |
|
try { |
|
|
|
scene = new THREE.Scene(); |
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
|
|
|
|
const rendererOptions = { |
|
antialias: false, |
|
alpha: true, |
|
powerPreference: "default" |
|
}; |
|
|
|
renderer = new THREE.WebGLRenderer(rendererOptions); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
renderer.setClearColor(0x000000, 0); |
|
|
|
|
|
const testGeometry = new THREE.BoxGeometry(1, 1, 1); |
|
const testMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); |
|
const testMesh = new THREE.Mesh(testGeometry, testMaterial); |
|
scene.add(testMesh); |
|
renderer.render(scene, camera); |
|
scene.remove(testMesh); |
|
|
|
document.getElementById('container').appendChild(renderer.domElement); |
|
return true; |
|
|
|
} catch (error) { |
|
console.error('Three.js initialization failed:', error); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
function initFallbackCanvas() { |
|
const canvas = document.getElementById('fallback-canvas'); |
|
canvas.style.display = 'block'; |
|
canvas.style.position = 'absolute'; |
|
canvas.style.top = '50%'; |
|
canvas.style.left = '50%'; |
|
canvas.style.transform = 'translate(-50%, -50%)'; |
|
|
|
const ctx = canvas.getContext('2d'); |
|
let time = 0; |
|
|
|
function drawFallback() { |
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
const centerX = canvas.width / 2; |
|
const centerY = canvas.height / 2; |
|
const outerRadius = ringSize * 30; |
|
const innerRadius = outerRadius * 0.4; |
|
|
|
|
|
ctx.strokeStyle = '#4ecdc4'; |
|
ctx.lineWidth = 2; |
|
ctx.beginPath(); |
|
ctx.arc(centerX, centerY, outerRadius, 0, Math.PI * 2); |
|
ctx.stroke(); |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.arc(centerX, centerY, innerRadius, 0, Math.PI * 2); |
|
ctx.stroke(); |
|
|
|
|
|
const colors = ['#00ffff', '#ff6b6b', '#4ecdc4', '#ffd93d', '#a8e6cf', '#dda0dd']; |
|
|
|
for (let i = 0; i < 60; i++) { |
|
const angle = (i / 60) * Math.PI * 2 + time * flowSpeed * 0.02; |
|
const spiralAngle = angle * spiralPitch + time * flowSpeed * 0.05; |
|
|
|
const radius = innerRadius + (outerRadius - innerRadius) * |
|
(0.5 + 0.4 * Math.sin(spiralAngle)); |
|
|
|
const x = centerX + radius * Math.cos(angle); |
|
const y = centerY + radius * Math.sin(angle); |
|
|
|
const colorIndex = Math.floor(i / 10) % colors.length; |
|
const intensity = 0.5 + 0.5 * Math.sin(spiralAngle + time * 0.01); |
|
|
|
ctx.fillStyle = colors[colorIndex]; |
|
ctx.globalAlpha = intensity; |
|
ctx.beginPath(); |
|
ctx.arc(x, y, 3, 0, Math.PI * 2); |
|
ctx.fill(); |
|
} |
|
|
|
ctx.globalAlpha = 1; |
|
|
|
|
|
ctx.fillStyle = '#00ffff'; |
|
ctx.font = '16px Arial'; |
|
ctx.fillText('NGVT Vortex Torus (2D Fallback)', 20, 30); |
|
ctx.fillStyle = '#ff6b6b'; |
|
ctx.font = '12px Arial'; |
|
ctx.fillText('Spiral flow visualization', 20, 50); |
|
|
|
time++; |
|
requestAnimationFrame(drawFallback); |
|
} |
|
|
|
drawFallback(); |
|
} |
|
|
|
|
|
function createSimplifiedVisualization() { |
|
|
|
codeSegments.forEach(segment => scene.remove(segment)); |
|
codeSegments.length = 0; |
|
|
|
|
|
const torusesToRemove = []; |
|
scene.traverse((child) => { |
|
if (child.userData && child.userData.isTorusRing) { |
|
torusesToRemove.push(child); |
|
} |
|
}); |
|
torusesToRemove.forEach(torus => scene.remove(torus)); |
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x404040, 0.8); |
|
scene.add(ambientLight); |
|
|
|
const directionalLight = new THREE.DirectionalLight(0x00ffff, 0.5); |
|
directionalLight.position.set(0, 5, 5); |
|
scene.add(directionalLight); |
|
|
|
|
|
for (let ringIndex = 0; ringIndex < numberOfRings; ringIndex++) { |
|
const currentRingSize = ringSize - (ringIndex * 1.2); |
|
if (currentRingSize <= 1.0) break; |
|
|
|
const torusGeometry = new THREE.TorusGeometry(currentRingSize, 0.6, 8, 32); |
|
const ringHue = (ringIndex * 60) % 360; |
|
const torusMaterial = new THREE.MeshBasicMaterial({ |
|
color: new THREE.Color().setHSL(ringHue/360, 0.7, 0.5), |
|
transparent: true, |
|
opacity: 0.2 + (ringIndex * 0.1), |
|
wireframe: true |
|
}); |
|
|
|
const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial); |
|
torusMesh.userData.isTorusRing = true; |
|
torusMesh.userData.ringIndex = ringIndex; |
|
scene.add(torusMesh); |
|
|
|
if (ringIndex === 0) torus = torusMesh; |
|
} |
|
|
|
|
|
const colors = [0x00ffff, 0xff6b6b, 0x4ecdc4, 0xffd93d, 0xa8e6cf, 0xdda0dd]; |
|
const particlesPerRing = 200; |
|
|
|
for (let ringIndex = 0; ringIndex < numberOfRings; ringIndex++) { |
|
const currentRingSize = ringSize - (ringIndex * 1.2); |
|
if (currentRingSize <= 1.0) break; |
|
|
|
for (let i = 0; i < particlesPerRing; i++) { |
|
const particleGeometry = new THREE.SphereGeometry(0.03 + (ringIndex * 0.01), 6, 6); |
|
const particleMaterial = new THREE.MeshBasicMaterial({ |
|
color: colors[(i + ringIndex) % colors.length], |
|
transparent: true, |
|
opacity: 0.8 |
|
}); |
|
|
|
const particle = new THREE.Mesh(particleGeometry, particleMaterial); |
|
|
|
|
|
const spiralIndex = i % 6; |
|
const t = (i / particlesPerRing); |
|
const u = t * Math.PI * 2 * spiralTightness + (spiralIndex * Math.PI * 2 / 6); |
|
|
|
const v = (spiralIndex * Math.PI * 2 / 6) + (t * Math.PI * 2 * spiralPitch); |
|
|
|
const x = (currentRingSize + 0.6 * Math.cos(v)) * Math.cos(u); |
|
const y = (currentRingSize + 0.6 * Math.cos(v)) * Math.sin(u); |
|
const z = 0.6 * Math.sin(v); |
|
|
|
particle.position.set(x, y, z); |
|
particle.userData = { |
|
u, v, |
|
originalU: u, originalV: v, |
|
spiralIndex, t, |
|
ringIndex: ringIndex, |
|
ringSize: currentRingSize |
|
}; |
|
|
|
codeSegments.push(particle); |
|
scene.add(particle); |
|
} |
|
} |
|
|
|
|
|
camera.position.set(0, 8, zoomLevel); |
|
camera.lookAt(0, 0, 0); |
|
} |
|
|
|
|
|
function animate() { |
|
if (!renderer) return; |
|
|
|
animationId = requestAnimationFrame(animate); |
|
|
|
|
|
const time = Date.now() * 0.001; |
|
codeSegments.forEach((particle, index) => { |
|
const data = particle.userData; |
|
|
|
|
|
const ringSpeedMultiplier = 1.0 + (data.ringIndex * 0.3); |
|
data.t += flowSpeed * 0.008 * ringSpeedMultiplier; |
|
if (data.t > 1) data.t = 0; |
|
|
|
|
|
const u = (data.t * spiralTightness * Math.PI * 2) + (data.spiralIndex * Math.PI * 2 / 6); |
|
|
|
const baseV = (data.spiralIndex * Math.PI * 2 / 6); |
|
const vVariation = Math.sin(data.t * Math.PI * 2 * spiralPitch) * 0.3; |
|
const v = baseV + vVariation; |
|
|
|
|
|
const x = (data.ringSize + 0.6 * Math.cos(v)) * Math.cos(u); |
|
const y = (data.ringSize + 0.6 * Math.cos(v)) * Math.sin(u); |
|
const z = 0.6 * Math.sin(v); |
|
|
|
particle.position.set(x, y, z); |
|
|
|
|
|
const flowPosition = Math.abs(Math.cos(v)); |
|
particle.material.opacity = 0.6 + 0.4 * flowPosition; |
|
|
|
const distanceFromCenter = Math.sqrt(x*x + y*y); |
|
const scaleByDistance = 0.7 + 0.5 * (distanceFromCenter / data.ringSize); |
|
particle.scale.setScalar(scaleByDistance); |
|
}); |
|
|
|
|
|
scene.traverse((child) => { |
|
if (child.userData && child.userData.isTorusRing) { |
|
child.rotation.z += 0.001 * (child.userData.ringIndex + 1); |
|
} |
|
}); |
|
|
|
|
|
if (rotationEnabled) { |
|
const autoAngle = time * 0.05; |
|
cameraAngleY = autoAngle; |
|
cameraAngleX = 0.3; |
|
} |
|
|
|
|
|
const x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * zoomLevel; |
|
const y = Math.sin(cameraAngleX) * zoomLevel + 8; |
|
const z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * zoomLevel; |
|
|
|
camera.position.set(x, y, z); |
|
camera.lookAt(0, 0, 0); |
|
|
|
try { |
|
renderer.render(scene, camera); |
|
} catch (error) { |
|
console.error('Render error:', error); |
|
showError(); |
|
} |
|
} |
|
|
|
|
|
function showError() { |
|
document.getElementById('error-message').style.display = 'block'; |
|
if (animationId) { |
|
cancelAnimationFrame(animationId); |
|
} |
|
setTimeout(() => { |
|
document.getElementById('error-message').style.display = 'none'; |
|
initFallbackCanvas(); |
|
}, 3000); |
|
} |
|
|
|
|
|
function setupControls() { |
|
document.getElementById('flowSpeed').addEventListener('input', (e) => { |
|
flowSpeed = parseFloat(e.target.value); |
|
}); |
|
|
|
document.getElementById('numberOfRings').addEventListener('input', (e) => { |
|
numberOfRings = parseInt(e.target.value); |
|
createSimplifiedVisualization(); |
|
}); |
|
|
|
document.getElementById('spiralTightness').addEventListener('input', (e) => { |
|
spiralTightness = parseInt(e.target.value); |
|
}); |
|
|
|
document.getElementById('spiralPitch').addEventListener('input', (e) => { |
|
spiralPitch = parseFloat(e.target.value); |
|
}); |
|
|
|
document.getElementById('ringSize').addEventListener('input', (e) => { |
|
ringSize = parseFloat(e.target.value); |
|
createSimplifiedVisualization(); |
|
}); |
|
|
|
document.getElementById('zoomLevel').addEventListener('input', (e) => { |
|
zoomLevel = parseFloat(e.target.value); |
|
}); |
|
|
|
document.getElementById('rotationToggle').addEventListener('change', (e) => { |
|
rotationEnabled = e.target.checked; |
|
}); |
|
|
|
document.getElementById('vortexIntensity').addEventListener('input', (e) => { |
|
vortexIntensity = parseFloat(e.target.value); |
|
}); |
|
} |
|
|
|
|
|
function onMouseDown(event) { |
|
if (event.button === 0) { |
|
isDragging = true; |
|
previousMousePosition.x = event.clientX; |
|
previousMousePosition.y = event.clientY; |
|
rotationEnabled = false; |
|
document.getElementById('rotationToggle').checked = false; |
|
} |
|
} |
|
|
|
function onMouseMove(event) { |
|
if (isDragging) { |
|
const deltaX = event.clientX - previousMousePosition.x; |
|
const deltaY = event.clientY - previousMousePosition.y; |
|
|
|
|
|
cameraAngleY += deltaX * 0.01; |
|
cameraAngleX += deltaY * 0.01; |
|
|
|
|
|
cameraAngleX = Math.max(-Math.PI/2, Math.min(Math.PI/2, cameraAngleX)); |
|
|
|
previousMousePosition.x = event.clientX; |
|
previousMousePosition.y = event.clientY; |
|
} |
|
} |
|
|
|
function onMouseUp(event) { |
|
isDragging = false; |
|
} |
|
|
|
|
|
function onTouchStart(event) { |
|
if (event.touches.length === 1) { |
|
isDragging = true; |
|
previousMousePosition.x = event.touches[0].clientX; |
|
previousMousePosition.y = event.touches[0].clientY; |
|
rotationEnabled = false; |
|
document.getElementById('rotationToggle').checked = false; |
|
event.preventDefault(); |
|
} |
|
} |
|
|
|
function onTouchMove(event) { |
|
if (isDragging && event.touches.length === 1) { |
|
const deltaX = event.touches[0].clientX - previousMousePosition.x; |
|
const deltaY = event.touches[0].clientY - previousMousePosition.y; |
|
|
|
cameraAngleY += deltaX * 0.01; |
|
cameraAngleX += deltaY * 0.01; |
|
cameraAngleX = Math.max(-Math.PI/2, Math.min(Math.PI/2, cameraAngleX)); |
|
|
|
previousMousePosition.x = event.touches[0].clientX; |
|
previousMousePosition.y = event.touches[0].clientY; |
|
event.preventDefault(); |
|
} |
|
} |
|
|
|
function onTouchEnd(event) { |
|
isDragging = false; |
|
} |
|
|
|
|
|
function onMouseWheel(event) { |
|
event.preventDefault(); |
|
const zoomDelta = event.deltaY > 0 ? 1 : -1; |
|
zoomLevel = Math.max(5, Math.min(30, zoomLevel + zoomDelta)); |
|
|
|
|
|
const zoomSlider = document.getElementById('zoomLevel'); |
|
if (zoomSlider) { |
|
zoomSlider.value = zoomLevel; |
|
} |
|
} |
|
|
|
|
|
function onWindowResize() { |
|
if (camera && renderer) { |
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
} |
|
} |
|
|
|
|
|
function init() { |
|
setupControls(); |
|
|
|
if (!isWebGLAvailable()) { |
|
showError(); |
|
return; |
|
} |
|
|
|
if (initThreeJS()) { |
|
createSimplifiedVisualization(); |
|
animate(); |
|
window.addEventListener('resize', onWindowResize); |
|
window.addEventListener('wheel', onMouseWheel, { passive: false }); |
|
|
|
|
|
renderer.domElement.addEventListener('mousedown', onMouseDown); |
|
window.addEventListener('mousemove', onMouseMove); |
|
window.addEventListener('mouseup', onMouseUp); |
|
|
|
|
|
renderer.domElement.addEventListener('touchstart', onTouchStart); |
|
window.addEventListener('touchmove', onTouchMove); |
|
window.addEventListener('touchend', onTouchEnd); |
|
|
|
} else { |
|
showError(); |
|
} |
|
} |
|
|
|
|
|
window.addEventListener('load', init); |
|
|
|
|
|
window.addEventListener('error', (event) => { |
|
console.error('Global error:', event.error); |
|
showError(); |
|
}); |
|
</script> |
|
</body> |
|
</html> |