| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>Flippd Pinball Classifier</title> |
| <style> |
| :root { color-scheme: light dark; font-family: system-ui, sans-serif; } |
| body { max-width: 72rem; margin: 0 auto; padding: 2rem 1rem; line-height: 1.5; } |
| h1, h2 { line-height: 1.2; } |
| .controls { display: flex; flex-wrap: wrap; gap: 1rem; align-items: end; margin-block: 1.5rem; } |
| label { display: grid; gap: .35rem; font-weight: 600; } |
| input, button { font: inherit; padding: .55rem .7rem; } |
| button { cursor: pointer; } |
| button:disabled { cursor: wait; opacity: .65; } |
| #preview { display: none; width: min(100%, 32rem); max-height: 28rem; object-fit: contain; margin-block: 1rem; background: #111; } |
| #status { min-height: 1.5rem; } |
| #error { display: none; padding: .8rem 1rem; border: 1px solid #b42318; color: #b42318; background: color-mix(in srgb, #b42318 10%, transparent); white-space: pre-wrap; } |
| #results { display: none; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1.25rem; margin-top: 1.5rem; } |
| table { width: 100%; border-collapse: collapse; } |
| th, td { padding: .45rem .55rem; border-bottom: 1px solid #8886; text-align: left; } |
| th:last-child, td:last-child { text-align: right; font-variant-numeric: tabular-nums; } |
| </style> |
| </head> |
| <body> |
| <main> |
| <h1>Pinball Classifier</h1> |
| <p>Select a local image to run the browser FP16 model. The model and metadata are loaded from this server.</p> |
| <p>Results use canonical <a href="https://opdb.org">Open Pinball Database (OPDB)</a> identifiers; the exact head may also return the non-OPDB <code>__unknown__</code> sentinel.</p> |
| <div class="controls"> |
| <label>Image<input id="image-file" type="file" accept="image/*"></label> |
| <label>Results per head<input id="top-k" type="number" min="1" max="20" step="1" value="5"></label> |
| <button id="run" type="button">Run classifier</button> |
| </div> |
| <img id="preview" alt="Selected image preview"> |
| <p id="status" role="status" aria-live="polite">Provider: not initialized</p> |
| <div id="error" role="alert" aria-live="assertive"></div> |
| <section id="results" aria-label="Classification results"> |
| <div><h2>Group</h2><table><thead><tr><th>OPDB ID</th><th>Confidence</th></tr></thead><tbody id="group-results"></tbody></table></div> |
| <div><h2>Machine</h2><table><thead><tr><th>OPDB ID</th><th>Confidence</th></tr></thead><tbody id="machine-results"></tbody></table></div> |
| <div><h2>Exact</h2><table><thead><tr><th>OPDB ID / sentinel</th><th>Confidence</th></tr></thead><tbody id="exact-results"></tbody></table></div> |
| </section> |
| </main> |
| <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.webgpu.min.js"></script> |
| <script src="./main.js"></script> |
| </body> |
| </html> |
|
|