StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 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 | <meta name="description" content="OxideUI WebAssembly Demo - A Rust UI Framework for the Web"> |
| 7 | <title>OxideUI WASM Demo</title> |
| 8 | <style> |
| 9 | * { |
| 10 | margin: 0; |
| 11 | padding: 0; |
| 12 | box-sizing: border-box; |
| 13 | } |
| 14 | |
| 15 | body { |
| 16 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; |
| 17 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| 18 | min-height: 100vh; |
| 19 | display: flex; |
| 20 | flex-direction: column; |
| 21 | color: #ffffff; |
| 22 | } |
| 23 | |
| 24 | header { |
| 25 | padding: 20px; |
| 26 | background: rgba(0, 0, 0, 0.2); |
| 27 | backdrop-filter: blur(10px); |
| 28 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
| 29 | } |
| 30 | |
| 31 | header h1 { |
| 32 | font-size: 24px; |
| 33 | font-weight: 600; |
| 34 | } |
| 35 | |
| 36 | header p { |
| 37 | font-size: 14px; |
| 38 | opacity: 0.8; |
| 39 | margin-top: 5px; |
| 40 | } |
| 41 | |
| 42 | main { |
| 43 | flex: 1; |
| 44 | display: flex; |
| 45 | justify-content: center; |
| 46 | align-items: center; |
| 47 | padding: 40px 20px; |
| 48 | } |
| 49 | |
| 50 | #app-container { |
| 51 | background: rgba(255, 255, 255, 0.1); |
| 52 | backdrop-filter: blur(20px); |
| 53 | border-radius: 20px; |
| 54 | padding: 40px; |
| 55 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); |
| 56 | max-width: 600px; |
| 57 | width: 100%; |
| 58 | } |
| 59 | |
| 60 | #loading { |
| 61 | text-align: center; |
| 62 | padding: 60px 20px; |
| 63 | } |
| 64 | |
| 65 | #loading h2 { |
| 66 | font-size: 28px; |
| 67 | margin-bottom: 20px; |
| 68 | } |
| 69 | |
| 70 | .spinner { |
| 71 | width: 50px; |
| 72 | height: 50px; |
| 73 | margin: 20px auto; |
| 74 | border: 4px solid rgba(255, 255, 255, 0.3); |
| 75 | border-top-color: #ffffff; |
| 76 | border-radius: 50%; |
| 77 | animation: spin 1s linear infinite; |
| 78 | } |
| 79 | |
| 80 | @keyframes spin { |
| 81 | to { transform: rotate(360deg); } |
| 82 | } |
| 83 | |
| 84 | #oxide-canvas { |
| 85 | width: 100%; |
| 86 | height: 400px; |
| 87 | border-radius: 10px; |
| 88 | background: #1a1a2e; |
| 89 | display: none; |
| 90 | } |
| 91 | |
| 92 | #counter-display { |
| 93 | text-align: center; |
| 94 | margin: 30px 0; |
| 95 | } |
| 96 | |
| 97 | #counter-value { |
| 98 | font-size: 72px; |
| 99 | font-weight: bold; |
| 100 | color: #4ecdc4; |
| 101 | text-shadow: 0 2px 10px rgba(78, 205, 196, 0.5); |
| 102 | } |
| 103 | |
| 104 | #counter-label { |
| 105 | font-size: 18px; |
| 106 | opacity: 0.8; |
| 107 | margin-top: 10px; |
| 108 | } |
| 109 | |
| 110 | .button-group { |
| 111 | display: flex; |
| 112 | gap: 15px; |
| 113 | justify-content: center; |
| 114 | margin: 30px 0; |
| 115 | } |
| 116 | |
| 117 | button { |
| 118 | padding: 12px 30px; |
| 119 | font-size: 16px; |
| 120 | font-weight: 600; |
| 121 | border: none; |
| 122 | border-radius: 8px; |
| 123 | cursor: pointer; |
| 124 | transition: all 0.3s ease; |
| 125 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); |
| 126 | } |
| 127 | |
| 128 | button:hover { |
| 129 | transform: translateY(-2px); |
| 130 | box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); |
| 131 | } |
| 132 | |
| 133 | button:active { |
| 134 | transform: translateY(0); |
| 135 | } |
| 136 | |
| 137 | .btn-primary { |
| 138 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| 139 | color: white; |
| 140 | } |
| 141 | |
| 142 | .btn-secondary { |
| 143 | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| 144 | color: white; |
| 145 | } |
| 146 | |
| 147 | .btn-danger { |
| 148 | background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); |
| 149 | color: white; |
| 150 | } |
| 151 | |
| 152 | #metrics { |
| 153 | margin-top: 30px; |
| 154 | padding: 20px; |
| 155 | background: rgba(0, 0, 0, 0.2); |
| 156 | border-radius: 10px; |
| 157 | font-size: 14px; |
| 158 | font-family: 'Courier New', monospace; |
| 159 | } |
| 160 | |
| 161 | #metrics h3 { |
| 162 | margin-bottom: 10px; |
| 163 | font-size: 16px; |
| 164 | } |
| 165 | |
| 166 | #metrics p { |
| 167 | margin: 5px 0; |
| 168 | opacity: 0.9; |
| 169 | } |
| 170 | |
| 171 | footer { |
| 172 | padding: 20px; |
| 173 | text-align: center; |
| 174 | background: rgba(0, 0, 0, 0.2); |
| 175 | backdrop-filter: blur(10px); |
| 176 | } |
| 177 | |
| 178 | footer a { |
| 179 | color: #4ecdc4; |
| 180 | text-decoration: none; |
| 181 | font-weight: 600; |
| 182 | } |
| 183 | |
| 184 | footer a:hover { |
| 185 | text-decoration: underline; |
| 186 | } |
| 187 | |
| 188 | .hidden { |
| 189 | display: none !important; |
| 190 | } |
| 191 | |
| 192 | .error { |
| 193 | background: rgba(255, 0, 0, 0.2); |
| 194 | border: 2px solid rgba(255, 0, 0, 0.5); |
| 195 | padding: 20px; |
| 196 | border-radius: 10px; |
| 197 | text-align: center; |
| 198 | } |
| 199 | |
| 200 | .error h2 { |
| 201 | color: #ff6b6b; |
| 202 | margin-bottom: 10px; |
| 203 | } |
| 204 | |
| 205 | @media (max-width: 600px) { |
| 206 | #app-container { |
| 207 | padding: 20px; |
| 208 | } |
| 209 | |
| 210 | #counter-value { |
| 211 | font-size: 48px; |
| 212 | } |
| 213 | |
| 214 | .button-group { |
| 215 | flex-direction: column; |
| 216 | } |
| 217 | |
| 218 | button { |
| 219 | width: 100%; |
| 220 | } |
| 221 | } |
| 222 | </style> |
| 223 | </head> |
| 224 | <body> |
| 225 | <header> |
| 226 | <h1>🦀 OxideUI WebAssembly Demo</h1> |
| 227 | <p>A high-performance Rust UI framework compiled to WebAssembly</p> |
| 228 | </header> |
| 229 | |
| 230 | <main> |
| 231 | <div id="app-container"> |
| 232 | <div id="loading"> |
| 233 | <h2>Loading OxideUI...</h2> |
| 234 | <div class="spinner"></div> |
| 235 | <p>Initializing WebAssembly module</p> |
| 236 | </div> |
| 237 | |
| 238 | <div id="app-content" class="hidden"> |
| 239 | <canvas id="oxide-canvas"></canvas> |
| 240 | |
| 241 | <div id="counter-display"> |
| 242 | <div id="counter-value">0</div> |
| 243 | <div id="counter-label">Counter Value</div> |
| 244 | </div> |
| 245 | |
| 246 | <div class="button-group"> |
| 247 | <button id="btn-decrement" class="btn-secondary">➖ Decrement</button> |
| 248 | <button id="btn-reset" class="btn-danger">🔄 Reset</button> |
| 249 | <button id="btn-increment" class="btn-primary">➕ Increment</button> |
| 250 | </div> |
| 251 | |
| 252 | <div id="metrics"> |
| 253 | <h3>📊 Metrics</h3> |
| 254 | <p>Counter: <span id="metric-counter">0</span></p> |
| 255 | <p>Uptime: <span id="metric-uptime">0</span>ms</p> |
| 256 | <p>Browser: <span id="metric-browser">Loading...</span></p> |
| 257 | </div> |
| 258 | </div> |
| 259 | |
| 260 | <div id="error" class="hidden error"> |
| 261 | <h2>⚠️ Error Loading Application</h2> |
| 262 | <p id="error-message"></p> |
| 263 | </div> |
| 264 | </div> |
| 265 | </main> |
| 266 | |
| 267 | <footer> |
| 268 | <p> |
| 269 | Built with ❤️ using <a href="https://github.com/oxideui/oxide-ui" target="_blank">OxideUI</a> | |
| 270 | <a href="https://www.rust-lang.org/" target="_blank">Rust</a> + |
| 271 | <a href="https://webassembly.org/" target="_blank">WebAssembly</a> |
| 272 | </p> |
| 273 | </footer> |
| 274 | |
| 275 | <script type="module"> |
| 276 | import init, { WasmApp, get_browser_info, log_message } from './pkg/oxide_wasm_demo.js'; |
| 277 | |
| 278 | let app = null; |
| 279 | |
| 280 | async function run() { |
| 281 | try { |
| 282 | log_message('Starting OxideUI WASM application...'); |
| 283 | |
| 284 | // Initialize WASM module |
| 285 | await init(); |
| 286 | log_message('WASM module initialized'); |
| 287 | |
| 288 | // Create app instance |
| 289 | app = new WasmApp('oxide-canvas'); |
| 290 | log_message('App instance created'); |
| 291 | |
| 292 | // Hide loading, show app |
| 293 | document.getElementById('loading').classList.add('hidden'); |
| 294 | document.getElementById('app-content').classList.remove('hidden'); |
| 295 | |
| 296 | // Set up event listeners |
| 297 | setupEventListeners(); |
| 298 | |
| 299 | // Get browser info |
| 300 | const browserInfo = get_browser_info(); |
| 301 | document.getElementById('metric-browser').textContent = browserInfo.userAgent.split(' ')[0]; |
| 302 | |
| 303 | // Start render loop |
| 304 | app.start_render_loop(); |
| 305 | |
| 306 | // Update metrics periodically |
| 307 | setInterval(updateMetrics, 1000); |
| 308 | |
| 309 | log_message('Application started successfully'); |
| 310 | |
| 311 | } catch (error) { |
| 312 | console.error('Failed to start app:', error); |
| 313 | showError(error.message || 'Unknown error occurred'); |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | function setupEventListeners() { |
| 318 | document.getElementById('btn-increment').addEventListener('click', () => { |
| 319 | app.increment(); |
| 320 | updateCounter(); |
| 321 | }); |
| 322 | |
| 323 | document.getElementById('btn-decrement').addEventListener('click', () => { |
| 324 | app.decrement(); |
| 325 | updateCounter(); |
| 326 | }); |
| 327 | |
| 328 | document.getElementById('btn-reset').addEventListener('click', () => { |
| 329 | app.reset(); |
| 330 | updateCounter(); |
| 331 | }); |
| 332 | } |
| 333 | |
| 334 | function updateCounter() { |
| 335 | const value = app.counter; |
| 336 | document.getElementById('counter-value').textContent = value; |
| 337 | document.getElementById('metric-counter').textContent = value; |
| 338 | } |
| 339 | |
| 340 | function updateMetrics() { |
| 341 | if (!app) return; |
| 342 | |
| 343 | try { |
| 344 | const metrics = app.get_metrics(); |
| 345 | document.getElementById('metric-counter').textContent = metrics.counter; |
| 346 | document.getElementById('metric-uptime').textContent = Math.round(metrics.uptime_ms); |
| 347 | } catch (error) { |
| 348 | console.error('Failed to update metrics:', error); |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | function showError(message) { |
| 353 | document.getElementById('loading').classList.add('hidden'); |
| 354 | document.getElementById('error').classList.remove('hidden'); |
| 355 | document.getElementById('error-message').textContent = message; |
| 356 | } |
| 357 | |
| 358 | // Start the application |
| 359 | run(); |
| 360 | </script> |
| 361 | </body> |
| 362 | </html> |
| 363 |