| | .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> |