Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,101 +1,248 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
#
|
| 3 |
-
import numpy as np
|
| 4 |
import time
|
| 5 |
-
import threading
|
| 6 |
import os
|
| 7 |
-
from flask import Flask
|
| 8 |
|
| 9 |
PORT = int(os.getenv('PORT', 7860))
|
| 10 |
-
WIDTH, HEIGHT = 400, 300
|
| 11 |
-
|
| 12 |
-
print(f"🚀 Starting animation on port {PORT}")
|
| 13 |
-
|
| 14 |
-
# Shared state
|
| 15 |
-
shared = {
|
| 16 |
-
"pixels": np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8),
|
| 17 |
-
"frame_count": 0,
|
| 18 |
-
"time": 0.0,
|
| 19 |
-
"lock": threading.Lock()
|
| 20 |
-
}
|
| 21 |
|
| 22 |
app = Flask(__name__)
|
| 23 |
|
| 24 |
HTML = '''<!DOCTYPE html>
|
| 25 |
<html>
|
| 26 |
<head>
|
| 27 |
-
<title>Simple Animation</title>
|
| 28 |
<style>
|
| 29 |
body { margin: 0; padding: 20px; background: #111; color: white; font-family: Arial; text-align: center; }
|
| 30 |
canvas { border: 3px solid #0af; background: black; display: block; margin: 20px auto; }
|
| 31 |
-
.
|
| 32 |
-
|
| 33 |
-
|
| 34 |
</style>
|
| 35 |
</head>
|
| 36 |
<body>
|
| 37 |
-
<h1>
|
| 38 |
-
<canvas id="canvas" width="400" height="300"></canvas>
|
| 39 |
|
| 40 |
-
<
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
</div>
|
| 45 |
|
| 46 |
<script>
|
| 47 |
const canvas = document.getElementById('canvas');
|
| 48 |
const ctx = canvas.getContext('2d');
|
| 49 |
-
let
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
.
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
|
|
|
| 75 |
|
| 76 |
-
|
| 77 |
-
document.getElementById('frameCount').textContent = framesReceived;
|
| 78 |
-
document.getElementById('status').textContent = 'Streaming ✓';
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
|
|
|
|
|
|
|
| 99 |
</script>
|
| 100 |
</body>
|
| 101 |
</html>'''
|
|
@@ -104,114 +251,13 @@ HTML = '''<!DOCTYPE html>
|
|
| 104 |
def index():
|
| 105 |
return HTML
|
| 106 |
|
| 107 |
-
@app.route('/stats')
|
| 108 |
-
def stats():
|
| 109 |
-
with shared['lock']:
|
| 110 |
-
return {
|
| 111 |
-
'frame_count': shared['frame_count'],
|
| 112 |
-
'time': shared['time']
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
@app.route('/frame')
|
| 116 |
-
def get_frame():
|
| 117 |
-
"""Generate a simple moving pattern"""
|
| 118 |
-
with shared['lock']:
|
| 119 |
-
# Update time
|
| 120 |
-
shared['time'] += 1/30
|
| 121 |
-
t = shared['time']
|
| 122 |
-
|
| 123 |
-
# Create coordinate grids
|
| 124 |
-
y, x = np.mgrid[:HEIGHT, :WIDTH]
|
| 125 |
-
|
| 126 |
-
# Clear to dark blue
|
| 127 |
-
pixels = shared['pixels']
|
| 128 |
-
pixels[:, :, 0] = 20 # R
|
| 129 |
-
pixels[:, :, 1] = 20 # G
|
| 130 |
-
pixels[:, :, 2] = 40 # B
|
| 131 |
-
|
| 132 |
-
# ===== ANIMATION 1: Moving rainbow circle =====
|
| 133 |
-
center_x = 200 + 100 * np.sin(t)
|
| 134 |
-
center_y = 150 + 80 * np.cos(t * 0.7)
|
| 135 |
-
radius = 30 + 10 * np.sin(t * 2)
|
| 136 |
-
|
| 137 |
-
distance = np.sqrt((x - center_x)**2 + (y - center_y)**2)
|
| 138 |
-
circle_mask = distance < radius
|
| 139 |
-
|
| 140 |
-
# Rainbow colors based on position and time
|
| 141 |
-
pixels[circle_mask, 0] = (128 + 127 * np.sin(t + x[circle_mask]/100)).astype(np.uint8)
|
| 142 |
-
pixels[circle_mask, 1] = (128 + 127 * np.sin(t * 1.3 + y[circle_mask]/100)).astype(np.uint8)
|
| 143 |
-
pixels[circle_mask, 2] = (128 + 127 * np.sin(t * 1.7 + (x[circle_mask]+y[circle_mask])/200)).astype(np.uint8)
|
| 144 |
-
|
| 145 |
-
# ===== ANIMATION 2: Pulsing grid =====
|
| 146 |
-
grid_size = 20 + 10 * np.sin(t)
|
| 147 |
-
grid_x = (x // grid_size).astype(int)
|
| 148 |
-
grid_y = (y // grid_size).astype(int)
|
| 149 |
-
|
| 150 |
-
# Pulsing checkerboard
|
| 151 |
-
pulse = (np.sin(t * 3) + 1) * 0.5
|
| 152 |
-
checker_mask = ((grid_x + grid_y) % 2 == 0)
|
| 153 |
-
|
| 154 |
-
# Add pulsing grid lines
|
| 155 |
-
grid_line_mask = ((x % grid_size < 2) | (y % grid_size < 2))
|
| 156 |
-
pixels[grid_line_mask & checker_mask, 0] = 100 + int(155 * pulse)
|
| 157 |
-
pixels[grid_line_mask & checker_mask, 1] = 200
|
| 158 |
-
pixels[grid_line_mask & checker_mask, 2] = 100 + int(155 * pulse)
|
| 159 |
-
|
| 160 |
-
# ===== ANIMATION 3: Moving wave =====
|
| 161 |
-
wave = np.sin(x/20 + t * 2) * np.sin(y/30 + t * 1.5)
|
| 162 |
-
wave_mask = wave > 0.5
|
| 163 |
-
|
| 164 |
-
wave_color = np.zeros_like(pixels[wave_mask])
|
| 165 |
-
wave_color[:, 0] = 200 # R
|
| 166 |
-
wave_color[:, 1] = 100 # G
|
| 167 |
-
wave_color[:, 2] = 200 # B
|
| 168 |
-
pixels[wave_mask] = wave_color
|
| 169 |
-
|
| 170 |
-
# ===== Add frame counter =====
|
| 171 |
-
frame_text = f"Frame: {shared['frame_count']}"
|
| 172 |
-
|
| 173 |
-
# Simple text using pixels
|
| 174 |
-
if shared['frame_count'] < 1000:
|
| 175 |
-
# Draw "FRAME" text
|
| 176 |
-
text_pixels = [
|
| 177 |
-
# F
|
| 178 |
-
[(10, 10), (10, 25), (15, 10), (15, 15)],
|
| 179 |
-
# R
|
| 180 |
-
[(20, 10), (20, 25), (25, 10), (25, 15), (20, 15), (25, 25)],
|
| 181 |
-
# A
|
| 182 |
-
[(30, 25), (32, 10), (34, 10), (36, 25), (32, 17), (34, 17)],
|
| 183 |
-
# M
|
| 184 |
-
[(40, 25), (40, 10), (43, 20), (46, 10), (46, 25)],
|
| 185 |
-
# E
|
| 186 |
-
[(50, 10), (50, 25), (55, 10), (55, 17), (50, 17), (55, 25)]
|
| 187 |
-
]
|
| 188 |
-
|
| 189 |
-
for char_pixels in text_pixels:
|
| 190 |
-
for px, py in char_pixels:
|
| 191 |
-
if 0 <= px < WIDTH and 0 <= py < HEIGHT:
|
| 192 |
-
pixels[py, px] = [255, 255, 100]
|
| 193 |
-
|
| 194 |
-
# Update frame count
|
| 195 |
-
shared['frame_count'] += 1
|
| 196 |
-
|
| 197 |
-
# Convert to bytes
|
| 198 |
-
pixel_bytes = pixels.tobytes()
|
| 199 |
-
|
| 200 |
-
print(f"📦 Frame {shared['frame_count']}, Time: {t:.2f}s")
|
| 201 |
-
|
| 202 |
-
return Response(
|
| 203 |
-
pixel_bytes,
|
| 204 |
-
mimetype='application/octet-stream',
|
| 205 |
-
headers={'Cache-Control': 'no-cache'}
|
| 206 |
-
)
|
| 207 |
-
|
| 208 |
def main():
|
| 209 |
print("="*60)
|
| 210 |
-
print("
|
| 211 |
print("="*60)
|
| 212 |
print(f"📡 Port: {PORT}")
|
| 213 |
-
print(
|
| 214 |
-
print("🎬
|
| 215 |
print("="*60)
|
| 216 |
|
| 217 |
app.run(host='0.0.0.0', port=PORT, threaded=True, use_reloader=False)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
# simple_canvas_animation.py - HTML5 Canvas + Minimal Flask
|
|
|
|
| 3 |
import time
|
|
|
|
| 4 |
import os
|
| 5 |
+
from flask import Flask
|
| 6 |
|
| 7 |
PORT = int(os.getenv('PORT', 7860))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
|
| 11 |
HTML = '''<!DOCTYPE html>
|
| 12 |
<html>
|
| 13 |
<head>
|
| 14 |
+
<title>Simple Canvas Animation</title>
|
| 15 |
<style>
|
| 16 |
body { margin: 0; padding: 20px; background: #111; color: white; font-family: Arial; text-align: center; }
|
| 17 |
canvas { border: 3px solid #0af; background: black; display: block; margin: 20px auto; }
|
| 18 |
+
.controls { margin: 20px; }
|
| 19 |
+
button { background: #0af; color: white; border: none; padding: 10px 20px; margin: 5px; border-radius: 5px; cursor: pointer; }
|
| 20 |
+
button:hover { background: #08c; }
|
| 21 |
</style>
|
| 22 |
</head>
|
| 23 |
<body>
|
| 24 |
+
<h1>🎨 HTML5 Canvas Animation</h1>
|
|
|
|
| 25 |
|
| 26 |
+
<canvas id="canvas" width="800" height="600"></canvas>
|
| 27 |
+
|
| 28 |
+
<div class="controls">
|
| 29 |
+
<button onclick="startAnimation()">Start</button>
|
| 30 |
+
<button onclick="stopAnimation()">Stop</button>
|
| 31 |
+
<button onclick="changePattern()">Change Pattern</button>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<div style="color: #888; margin-top: 20px;">
|
| 35 |
+
<p>Pure HTML5 Canvas + JavaScript - No Flask data transfer needed</p>
|
| 36 |
+
<p>Everything runs in your browser!</p>
|
| 37 |
</div>
|
| 38 |
|
| 39 |
<script>
|
| 40 |
const canvas = document.getElementById('canvas');
|
| 41 |
const ctx = canvas.getContext('2d');
|
| 42 |
+
let animationId = null;
|
| 43 |
+
let pattern = 1;
|
| 44 |
+
let time = 0;
|
| 45 |
|
| 46 |
+
// Bouncing balls
|
| 47 |
+
const balls[];
|
| 48 |
+
for (let i = 0; i < 10; i++) {
|
| 49 |
+
balls.push({
|
| 50 |
+
x: Math.random() * canvas.width,
|
| 51 |
+
y: Math.random() * canvas.height,
|
| 52 |
+
dx: (Math.random() - 0.5) * 8,
|
| 53 |
+
dy: (Math.random() - 0.5) * 8,
|
| 54 |
+
radius: 10 + Math.random() * 20,
|
| 55 |
+
color: `hsl(${Math.random() * 360}, 100%, 60%)`,
|
| 56 |
+
trail: []
|
| 57 |
+
});
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
function drawPattern1() {
|
| 61 |
+
// Clear with gradient
|
| 62 |
+
const gradient = ctx.createRadialGradient(
|
| 63 |
+
canvas.width/2, canvas.height/2, 0,
|
| 64 |
+
canvas.width/2, canvas.height/2, Math.max(canvas.width, canvas.height)/2
|
| 65 |
+
);
|
| 66 |
+
gradient.addColorStop(0, 'rgba(20, 20, 40, 0.8)');
|
| 67 |
+
gradient.addColorStop(1, 'rgba(0, 0, 20, 1)');
|
| 68 |
+
ctx.fillStyle = gradient;
|
| 69 |
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 70 |
+
|
| 71 |
+
// Update and draw balls
|
| 72 |
+
balls.forEach(ball => {
|
| 73 |
+
// Update position
|
| 74 |
+
ball.x += ball.dx;
|
| 75 |
+
ball.y += ball.dy;
|
| 76 |
+
|
| 77 |
+
// Bounce off walls
|
| 78 |
+
if (ball.x - ball.radius < 0 || ball.x + ball.radius > canvas.width) {
|
| 79 |
+
ball.dx = -ball.dx;
|
| 80 |
+
}
|
| 81 |
+
if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) {
|
| 82 |
+
ball.dy = -ball.dy;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Add to trail
|
| 86 |
+
ball.trail.push({x: ball.x, y: ball.y});
|
| 87 |
+
if (ball.trail.length > 20) ball.trail.shift();
|
| 88 |
+
|
| 89 |
+
// Draw trail
|
| 90 |
+
ball.trail.forEach((pos, i) => {
|
| 91 |
+
const alpha = i / ball.trail.length;
|
| 92 |
+
ctx.beginPath();
|
| 93 |
+
ctx.arc(pos.x, pos.y, ball.radius * alpha, 0, Math.PI * 2);
|
| 94 |
+
ctx.fillStyle = ball.color.replace(')', `, ${alpha})`).replace('hsl', 'hsla');
|
| 95 |
+
ctx.fill();
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
// Draw ball
|
| 99 |
+
ctx.beginPath();
|
| 100 |
+
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
|
| 101 |
+
ctx.fillStyle = ball.color;
|
| 102 |
+
ctx.fill();
|
| 103 |
+
ctx.strokeStyle = 'white';
|
| 104 |
+
ctx.lineWidth = 2;
|
| 105 |
+
ctx.stroke();
|
| 106 |
+
});
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
function drawPattern2() {
|
| 110 |
+
// Clear
|
| 111 |
+
ctx.fillStyle = 'rgba(0, 10, 20, 0.1)';
|
| 112 |
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 113 |
+
|
| 114 |
+
time += 0.05;
|
| 115 |
+
|
| 116 |
+
// Draw rotating pattern
|
| 117 |
+
const centerX = canvas.width / 2;
|
| 118 |
+
const centerY = canvas.height / 2;
|
| 119 |
+
|
| 120 |
+
for (let i = 0; i < 20; i++) {
|
| 121 |
+
const angle = time + i * Math.PI / 10;
|
| 122 |
+
const radius = 50 + i * 15;
|
| 123 |
+
const x = centerX + Math.cos(angle) * radius;
|
| 124 |
+
const y = centerY + Math.sin(angle) * radius;
|
| 125 |
+
const size = 10 + Math.sin(time * 2 + i) * 5;
|
| 126 |
+
|
| 127 |
+
// Gradient color
|
| 128 |
+
const hue = (time * 20 + i * 18) % 360;
|
| 129 |
+
ctx.fillStyle = `hsl(${hue}, 100%, 60%)`;
|
| 130 |
+
|
| 131 |
+
ctx.beginPath();
|
| 132 |
+
ctx.arc(x, y, size, 0, Math.PI * 2);
|
| 133 |
+
ctx.fill();
|
| 134 |
+
|
| 135 |
+
// Connecting lines
|
| 136 |
+
if (i > 0) {
|
| 137 |
+
const prevAngle = time + (i - 1) * Math.PI / 10;
|
| 138 |
+
const prevX = centerX + Math.cos(prevAngle) * radius;
|
| 139 |
+
const prevY = centerY + Math.sin(prevAngle) * radius;
|
| 140 |
|
| 141 |
+
ctx.beginPath();
|
| 142 |
+
ctx.moveTo(prevX, prevY);
|
| 143 |
+
ctx.lineTo(x, y);
|
| 144 |
+
ctx.strokeStyle = `hsla(${hue}, 100%, 60%, 0.5)`;
|
| 145 |
+
ctx.lineWidth = 2;
|
| 146 |
+
ctx.stroke();
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
// Draw center circle
|
| 151 |
+
ctx.beginPath();
|
| 152 |
+
ctx.arc(centerX, centerY, 30, 0, Math.PI * 2);
|
| 153 |
+
ctx.fillStyle = `hsl(${time * 50 % 360}, 100%, 60%)`;
|
| 154 |
+
ctx.fill();
|
| 155 |
+
ctx.strokeStyle = 'white';
|
| 156 |
+
ctx.lineWidth = 3;
|
| 157 |
+
ctx.stroke();
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
function drawPattern3() {
|
| 161 |
+
// Clear with subtle fade
|
| 162 |
+
ctx.fillStyle = 'rgba(10, 5, 20, 0.2)';
|
| 163 |
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 164 |
+
|
| 165 |
+
time += 0.03;
|
| 166 |
+
|
| 167 |
+
// Draw wave pattern
|
| 168 |
+
for (let x = 0; x < canvas.width; x += 20) {
|
| 169 |
+
for (let y = 0; y < canvas.height; y += 20) {
|
| 170 |
+
const wave = Math.sin(x * 0.02 + time) * Math.cos(y * 0.02 + time);
|
| 171 |
+
const size = 5 + wave * 10;
|
| 172 |
|
| 173 |
+
const hue = (x + y + time * 100) % 360;
|
| 174 |
+
const saturation = 70 + wave * 30;
|
| 175 |
+
const lightness = 40 + wave * 20;
|
| 176 |
|
| 177 |
+
ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
|
|
|
|
|
| 178 |
|
| 179 |
+
ctx.beginPath();
|
| 180 |
+
ctx.arc(x, y, size, 0, Math.PI * 2);
|
| 181 |
+
ctx.fill();
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// Draw moving text
|
| 186 |
+
ctx.font = 'bold 48px Arial';
|
| 187 |
+
ctx.textAlign = 'center';
|
| 188 |
+
ctx.textBaseline = 'middle';
|
| 189 |
+
|
| 190 |
+
const text = 'HTML5 CANVAS';
|
| 191 |
+
const textX = canvas.width / 2;
|
| 192 |
+
const textY = canvas.height / 2;
|
| 193 |
+
|
| 194 |
+
// Text shadow/glow
|
| 195 |
+
for (let i = 0; i < 10; i++) {
|
| 196 |
+
const offset = Math.sin(time + i) * 5;
|
| 197 |
+
ctx.fillStyle = `hsla(${i * 36}, 100%, 60%, 0.2)`;
|
| 198 |
+
ctx.fillText(text, textX + offset, textY + offset);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
// Main text
|
| 202 |
+
ctx.fillStyle = `hsl(${time * 50 % 360}, 100%, 60%)`;
|
| 203 |
+
ctx.fillText(text, textX, textY);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
function animate() {
|
| 207 |
+
switch(pattern) {
|
| 208 |
+
case 1: drawPattern1(); break;
|
| 209 |
+
case 2: drawPattern2(); break;
|
| 210 |
+
case 3: drawPattern3(); break;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// Add FPS counter
|
| 214 |
+
ctx.font = '16px monospace';
|
| 215 |
+
ctx.fillStyle = 'white';
|
| 216 |
+
ctx.textAlign = 'left';
|
| 217 |
+
ctx.fillText(`Pattern: ${pattern}`, 10, 20);
|
| 218 |
+
ctx.fillText(`Time: ${time.toFixed(1)}s`, 10, 40);
|
| 219 |
+
|
| 220 |
+
animationId = requestAnimationFrame(animate);
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
function startAnimation() {
|
| 224 |
+
if (!animationId) {
|
| 225 |
+
animate();
|
| 226 |
+
console.log('Animation started');
|
| 227 |
+
}
|
| 228 |
}
|
| 229 |
|
| 230 |
+
function stopAnimation() {
|
| 231 |
+
if (animationId) {
|
| 232 |
+
cancelAnimationFrame(animationId);
|
| 233 |
+
animationId = null;
|
| 234 |
+
console.log('Animation stopped');
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
function changePattern() {
|
| 239 |
+
pattern = pattern % 3 + 1;
|
| 240 |
+
console.log(`Changed to pattern ${pattern}`);
|
| 241 |
+
}
|
| 242 |
|
| 243 |
+
// Initialize with pattern 1
|
| 244 |
+
drawPattern1();
|
| 245 |
+
console.log('Ready! Click Start to animate');
|
| 246 |
</script>
|
| 247 |
</body>
|
| 248 |
</html>'''
|
|
|
|
| 251 |
def index():
|
| 252 |
return HTML
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
def main():
|
| 255 |
print("="*60)
|
| 256 |
+
print("🎨 HTML5 Canvas Animation Server")
|
| 257 |
print("="*60)
|
| 258 |
print(f"📡 Port: {PORT}")
|
| 259 |
+
print("✨ Everything runs in browser - no data transfer needed!")
|
| 260 |
+
print("🎬 3 different animation patterns")
|
| 261 |
print("="*60)
|
| 262 |
|
| 263 |
app.run(host='0.0.0.0', port=PORT, threaded=True, use_reloader=False)
|