|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | + <title>PythonExtra Demo: Cyberspace</title> |
| 7 | + <style> |
| 8 | + body { |
| 9 | + background-color: #222; |
| 10 | + display: flex; |
| 11 | + justify-content: center; |
| 12 | + align-items: center; |
| 13 | + height: 100vh; |
| 14 | + margin: 0; |
| 15 | + font-family: monospace; |
| 16 | + color: #888; |
| 17 | + } |
| 18 | + #calculator-bezel { |
| 19 | + background-color: #e0e0e0; |
| 20 | + padding: 20px; |
| 21 | + border-radius: 15px; |
| 22 | + box-shadow: 0 10px 30px rgba(0,0,0,0.5); |
| 23 | + display: flex; |
| 24 | + flex-direction: column; |
| 25 | + align-items: center; |
| 26 | + } |
| 27 | + canvas { |
| 28 | + background-color: #000; |
| 29 | + border: 2px solid #999; |
| 30 | + /* Ensures crisp scaling on high-DPI screens */ |
| 31 | + image-rendering: pixelated; |
| 32 | + image-rendering: crisp-edges; |
| 33 | + width: 320px; |
| 34 | + height: 528px; |
| 35 | + box-shadow: inset 0 0 10px rgba(0,0,0,0.8); |
| 36 | + } |
| 37 | + .controls { |
| 38 | + margin-top: 10px; |
| 39 | + font-size: 12px; |
| 40 | + } |
| 41 | + </style> |
| 42 | +</head> |
| 43 | +<body> |
| 44 | + |
| 45 | + <div id="calculator-bezel"> |
| 46 | + <!-- Canvas size matches calculator resolution exactly --> |
| 47 | + <canvas id="screen" width="320" height="528"></canvas> |
| 48 | + <div class="controls">PythonExtra JS Simulator • 320x528</div> |
| 49 | + </div> |
| 50 | + |
| 51 | + <script> |
| 52 | + /** |
| 53 | + * GINT / PYTHONEXTRA SHIM FOR JS CANVAS |
| 54 | + * Mimics the environment of the calculator |
| 55 | + */ |
| 56 | + const canvas = document.getElementById('screen'); |
| 57 | + const ctx = canvas.getContext('2d', { alpha: false }); // Optimize for no transparency |
| 58 | + const DWIDTH = 320; |
| 59 | + const DHEIGHT = 528; |
| 60 | + |
| 61 | + // Configuration from Python script |
| 62 | + const WIDTH = DWIDTH; |
| 63 | + const HEIGHT = DHEIGHT; |
| 64 | + const CX = Math.floor(WIDTH / 2); |
| 65 | + const CY = Math.floor(HEIGHT / 2); |
| 66 | + |
| 67 | + // Disable smoothing for pixel-perfect rendering |
| 68 | + ctx.imageSmoothingEnabled = false; |
| 69 | + |
| 70 | + // Color helper: Maps 0-31 range (RGB555) to 0-255 CSS RGB |
| 71 | + function C_RGB(r, g, b) { |
| 72 | + const R = Math.floor(r * 255 / 31); |
| 73 | + const G = Math.floor(g * 255 / 31); |
| 74 | + const B = Math.floor(b * 255 / 31); |
| 75 | + return `rgb(${R}, ${G}, ${B})`; |
| 76 | + } |
| 77 | + |
| 78 | + const C_WHITE = C_RGB(31, 31, 31); |
| 79 | + const C_BG = "#000000"; |
| 80 | + const C_NONE = null; |
| 81 | + |
| 82 | + // Drawing Primitives - Integers only for pixel alignment |
| 83 | + function dclear(color) { |
| 84 | + ctx.fillStyle = color; |
| 85 | + ctx.fillRect(0, 0, WIDTH, HEIGHT); |
| 86 | + } |
| 87 | + |
| 88 | + function dline(x1, y1, x2, y2, color) { |
| 89 | + if (!color) return; |
| 90 | + ctx.strokeStyle = color; |
| 91 | + ctx.lineWidth = 1; // 1px lines for precision |
| 92 | + ctx.beginPath(); |
| 93 | + // Math.floor ensures we hit exact pixels |
| 94 | + ctx.moveTo(Math.floor(x1) + 0.5, Math.floor(y1) + 0.5); |
| 95 | + ctx.lineTo(Math.floor(x2) + 0.5, Math.floor(y2) + 0.5); |
| 96 | + ctx.stroke(); |
| 97 | + } |
| 98 | + |
| 99 | + function drect(x1, y1, x2, y2, color) { |
| 100 | + if (!color) return; |
| 101 | + ctx.fillStyle = color; |
| 102 | + const x = Math.min(x1, x2) | 0; |
| 103 | + const y = Math.min(y1, y2) | 0; |
| 104 | + const w = (Math.abs(x2 - x1) + 1) | 0; |
| 105 | + const h = (Math.abs(y2 - y1) + 1) | 0; |
| 106 | + ctx.fillRect(x, y, w, h); |
| 107 | + } |
| 108 | + |
| 109 | + // Text handling |
| 110 | + const DTEXT_LEFT = 'left'; |
| 111 | + const DTEXT_CENTER = 'center'; |
| 112 | + const DTEXT_RIGHT = 'right'; |
| 113 | + const DTEXT_TOP = 'top'; |
| 114 | + const DTEXT_BOTTOM = 'bottom'; |
| 115 | + |
| 116 | + function dtext(x, y, color, text) { |
| 117 | + if (!color) return; |
| 118 | + ctx.fillStyle = color; |
| 119 | + ctx.font = '16px "Courier New", monospace'; |
| 120 | + ctx.textAlign = 'left'; |
| 121 | + ctx.textBaseline = 'top'; |
| 122 | + ctx.fillText(text, Math.floor(x), Math.floor(y)); |
| 123 | + } |
| 124 | + |
| 125 | + function dtext_opt(x, y, fg, bg, halign, valign, text) { |
| 126 | + ctx.font = '16px "Courier New", monospace'; |
| 127 | + ctx.textAlign = halign; |
| 128 | + |
| 129 | + if (valign === DTEXT_TOP) ctx.textBaseline = 'top'; |
| 130 | + else if (valign === DTEXT_BOTTOM) ctx.textBaseline = 'bottom'; |
| 131 | + else ctx.textBaseline = 'middle'; |
| 132 | + |
| 133 | + ctx.fillStyle = fg; |
| 134 | + ctx.fillText(text, Math.floor(x), Math.floor(y)); |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * MAIN DEMO LOGIC |
| 139 | + */ |
| 140 | + |
| 141 | + // 3D Cube Data |
| 142 | + const VERTICES = [ |
| 143 | + [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1], |
| 144 | + [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1] |
| 145 | + ]; |
| 146 | + |
| 147 | + const EDGES = [ |
| 148 | + [0,1], [1,2], [2,3], [3,0], |
| 149 | + [4,5], [5,6], [6,7], [7,4], |
| 150 | + [0,4], [1,5], [2,6], [3,7] |
| 151 | + ]; |
| 152 | + |
| 153 | + // State variables |
| 154 | + let angle_x = 0.0; |
| 155 | + let angle_y = 0.0; |
| 156 | + let angle_z = 0.0; |
| 157 | + const scale_base = 80; |
| 158 | + |
| 159 | + // Scroller |
| 160 | + const msg = " WELCOME TO PYTHONEXTRA ... DEMO SCENE EFFECT ... GINT GRAPHICS LIBRARY ... 3D CUBE RENDERING ... "; |
| 161 | + let msg_x = WIDTH; |
| 162 | + const text_width = msg.length * 10; |
| 163 | + |
| 164 | + function rotate_point(x, y, z, ax, ay, az) { |
| 165 | + let s, c; |
| 166 | + // Rotate X |
| 167 | + if (ax !== 0) { |
| 168 | + s = Math.sin(ax); c = Math.cos(ax); |
| 169 | + let ny = y * c - z * s; |
| 170 | + let nz = y * s + z * c; |
| 171 | + y = ny; z = nz; |
| 172 | + } |
| 173 | + // Rotate Y |
| 174 | + if (ay !== 0) { |
| 175 | + s = Math.sin(ay); c = Math.cos(ay); |
| 176 | + let nx = x * c - z * s; |
| 177 | + let nz = x * s + z * c; |
| 178 | + x = nx; z = nz; |
| 179 | + } |
| 180 | + // Rotate Z |
| 181 | + if (az !== 0) { |
| 182 | + s = Math.sin(az); c = Math.cos(az); |
| 183 | + let nx = x * c - y * s; |
| 184 | + let ny = x * s + y * c; |
| 185 | + x = nx; y = ny; |
| 186 | + } |
| 187 | + return {x, y, z}; |
| 188 | + } |
| 189 | + |
| 190 | + // Animation Loop Control |
| 191 | + const FPS = 40; |
| 192 | + const FRAME_INTERVAL = 1000 / FPS; |
| 193 | + let lastFrameTime = 0; |
| 194 | + const startTime = Date.now(); |
| 195 | + |
| 196 | + function loop(currentTime) { |
| 197 | + requestAnimationFrame(loop); |
| 198 | + |
| 199 | + // FPS Limiter |
| 200 | + const elapsed = currentTime - lastFrameTime; |
| 201 | + if (elapsed < FRAME_INTERVAL) return; |
| 202 | + |
| 203 | + // Adjust lastFrameTime to target interval (avoids drift) |
| 204 | + lastFrameTime = currentTime - (elapsed % FRAME_INTERVAL); |
| 205 | + |
| 206 | + // Logic time relative to start |
| 207 | + const t = (Date.now() - startTime) / 1000; |
| 208 | + |
| 209 | + // Update Physics |
| 210 | + angle_x += 0.04; |
| 211 | + angle_y += 0.02; |
| 212 | + angle_z += 0.01; |
| 213 | + |
| 214 | + const y_offset = Math.sin(t * 2.5) * 60; |
| 215 | + |
| 216 | + // Colors |
| 217 | + const r = Math.floor((Math.sin(t * 3) + 1) * 15.5); |
| 218 | + const g = Math.floor((Math.sin(t * 3 + 2) + 1) * 15.5); |
| 219 | + const b = Math.floor((Math.sin(t * 3 + 4) + 1) * 15.5); |
| 220 | + const col_wire = C_RGB(r, g, b); |
| 221 | + |
| 222 | + // Draw |
| 223 | + dclear(C_BG); |
| 224 | + |
| 225 | + // Background Grid |
| 226 | + const grid_col = C_RGB(6, 6, 6); |
| 227 | + const grid_speed = (t * 50) % 40; |
| 228 | + |
| 229 | + for (let i = 0; i < HEIGHT / 2; i += 40) { |
| 230 | + const y_pos = HEIGHT - i + grid_speed; |
| 231 | + if (y_pos < HEIGHT) { |
| 232 | + dline(0, y_pos, WIDTH, y_pos, grid_col); |
| 233 | + } |
| 234 | + } |
| 235 | + for (let i = -200; i < WIDTH + 200; i += 80) { |
| 236 | + dline(i, HEIGHT, CX, CY, grid_col); |
| 237 | + } |
| 238 | + |
| 239 | + // 3D Cube |
| 240 | + const projected_points = []; |
| 241 | + |
| 242 | + for (let v of VERTICES) { |
| 243 | + const p = rotate_point(v[0], v[1], v[2], angle_x, angle_y, angle_z); |
| 244 | + const dist = 4; |
| 245 | + const z_factor = 1 / (dist - p.z); |
| 246 | + |
| 247 | + // Floor coordinates for pixel perfection |
| 248 | + const px = Math.floor(p.x * scale_base * z_factor + CX); |
| 249 | + const py = Math.floor(p.y * scale_base * z_factor + CY + y_offset); |
| 250 | + |
| 251 | + projected_points.push({x: px, y: py}); |
| 252 | + } |
| 253 | + |
| 254 | + for (let edge of EDGES) { |
| 255 | + const p1 = projected_points[edge[0]]; |
| 256 | + const p2 = projected_points[edge[1]]; |
| 257 | + dline(p1.x, p1.y, p2.x, p2.y, col_wire); |
| 258 | + } |
| 259 | + |
| 260 | + // Scroller |
| 261 | + drect(0, HEIGHT - 30, WIDTH, HEIGHT, C_RGB(0, 0, 10)); |
| 262 | + dline(0, HEIGHT - 30, WIDTH, HEIGHT - 30, C_WHITE); |
| 263 | + dtext(Math.floor(msg_x), HEIGHT - 24, C_WHITE, msg); |
| 264 | + |
| 265 | + msg_x -= 3; |
| 266 | + if (msg_x < -text_width) { |
| 267 | + msg_x = WIDTH; |
| 268 | + } |
| 269 | + |
| 270 | + // HUD |
| 271 | + dtext_opt(CX, 10, C_WHITE, null, DTEXT_CENTER, DTEXT_TOP, "PythonExtra Demo"); |
| 272 | + dtext_opt(CX, 25, col_wire, null, DTEXT_CENTER, DTEXT_TOP, "[EXIT] to Quit"); |
| 273 | + } |
| 274 | + |
| 275 | + // Initialize loop |
| 276 | + loop(0); |
| 277 | + |
| 278 | + </script> |
| 279 | +</body> |
| 280 | +</html> |
0 commit comments