Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no"> | |
| <meta name="description" content="JARVIS real 8th Wall WebAR runtime for OMEGA"> | |
| <meta name="theme-color" content="#05070b"> | |
| <meta name="mobile-web-app-capable" content="yes"> | |
| <meta name="apple-mobile-web-app-capable" content="yes"> | |
| <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> | |
| <title>JARVIS Real AR</title> | |
| <link rel="manifest" href="./assets/manifest-8qualO3q.json"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <!-- | |
| BUG 33 FIX — Explicit compositor layer z-order applied before any JS runs. | |
| camerafeed (XR8/fallback camera canvas) → z-index: 0 (base layer) | |
| css3d-layer (CSS3D AR overlays) → z-index: 1 | |
| hand-overlay (MediaPipe hand canvas) → z-index: 2 | |
| world-label-layer → z-index: 3 | |
| ui-layer (AR panels, HUD) → z-index: 10 | |
| loading-screen → z-index: 100 | |
| error-host / banner-host → z-index: 200 | |
| --> | |
| <style id="jarvis-compositor-layer-order"> | |
| #camerafeed { | |
| position: fixed; | |
| inset: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| z-index: 0; | |
| display: block; | |
| background: var(--bg-darker); | |
| /* XR8 will resize the canvas pixel dimensions on xrloaded. | |
| The CSS dimensions are locked here so no flash of 0x0. */ | |
| } | |
| #css3d-layer { | |
| position: fixed; | |
| inset: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| z-index: 1; | |
| pointer-events: none; | |
| } | |
| #hand-overlay { | |
| position: fixed; | |
| inset: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| z-index: 2; | |
| pointer-events: none; | |
| } | |
| #world-label-layer { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 3; | |
| pointer-events: none; | |
| } | |
| #ui-layer { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| #ui-layer > * { pointer-events: auto; } | |
| #hud-toolbar { z-index: 11; } | |
| #status-panel { z-index: 11; } | |
| #jarvis-bubble { z-index: 11; } | |
| #loading-screen { z-index: 100; } | |
| /* Layout lives in hud.css (#notice-stack) — these two hosts are laid out as one | |
| column there so notices can never overlap. Only the z-index and pointer-events | |
| policy stay here, alongside the rest of the compositor's layer order. */ | |
| #notice-stack { z-index: 200; pointer-events: none; } | |
| #error-host > *, #banner-host > * { pointer-events: auto; } | |
| </style> | |
| <!-- | |
| This product includes the XR Engine software developed by | |
| Niantic Spatial, Inc. | |
| Copyright © 2026 Niantic Spatial, Inc. All rights reserved. | |
| License: https://github.com/8thwall/engine/blob/main/LICENSE | |
| 8th Wall transitioned to open source (MIT framework, closed SLAM binary) on | |
| Feb 28 2026. The hosted CDN was retired. The binary is self-hosted under | |
| external/xr/xr.js — download from https://8th.io/xrjs and place at that path. | |
| --> | |
| <!-- | |
| BUG 31 FIX — A-Frame JS loaded here so window.AFRAME is defined before | |
| ar-init.js runs and <a-scene> can initialise normally. | |
| Pinned at v1.6.0; update by changing the version in both src and the integrity hash. | |
| --> | |
| <!-- | |
| A-Frame is deliberately NOT loaded. History, so this isn't "fixed" back: | |
| The tag used to point at aframe@1.6.0/dist/aframe.min.js, which 404s on | |
| jsDelivr (the real filename is aframe-master.min.js). So `window.AFRAME` has | |
| never been defined in production, and every page load paid for a failed | |
| request. Its only consumer was the physics tick in ar-physics.js, which | |
| silently no-op'd behind `if (window.AFRAME)` — physics never stepped once. | |
| Correcting the URL was tried and measured in a real browser: A-Frame ships its | |
| own copy of three.js, which produced "WARNING: Multiple instances of Three.js | |
| being imported" plus a CONTEXT_LOST_WEBGL against the app's own renderer. | |
| A-Frame is not the renderer here — <a-scene> below is display:none and the live | |
| context comes from XR8 or the fallback THREE canvas (utils/scene-db.js | |
| getSceneContext/ensureFallbackThree) — so loading it buys nothing and costs a | |
| duplicate WebGL/three stack. | |
| The physics tick now runs on the app's own managed RAF loop instead, so nothing | |
| depends on A-Frame. The <a-scene> block below is inert without it, which is | |
| exactly how production has always behaved. | |
| --> | |
| <!-- | |
| BUG 32 FIX — XR binary loaded statically so XR8 is always attempted even | |
| when the page is served without a ?xr= URL parameter. | |
| Since the 8th Wall hosted CDN was retired (Feb 2026) the binary must be | |
| self-hosted. Place the extracted xr-standalone.zip contents at: | |
| phone/eightwall_ar/external/xr/xr.js | |
| Download: https://8th.io/xrjs | |
| The onerror handler fires when the file is absent and falls through | |
| gracefully — ar-init.js will try the ?xr= param next, then WebXR fallback, | |
| then basic camera. No hard failure. | |
| --> | |
| <script id="jarvis-xr-static" | |
| src="./external/xr/xr.js" | |
| async | |
| data-preload-chunks="slam" | |
| data-jarvis-xr-static="true" | |
| onerror="document.getElementById('jarvis-xr-static').dataset.loadFailed='true'"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script> | |
| <script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js" crossorigin="anonymous"></script> | |
| <script type="module" crossorigin src="./assets/index-CyALIax6.js"></script> | |
| <link rel="modulepreload" crossorigin href="./assets/mediapipe-Ck-u_eH7.js"> | |
| <link rel="stylesheet" crossorigin href="./assets/index-hHPIeHhK.css"> | |
| </head> | |
| <body> | |
| <canvas id="camerafeed" aria-label="Live rear camera AR feed"></canvas> | |
| <a-scene id="ar-scene" | |
| xr-mode-ui="enabled: false" | |
| renderer="alpha: true; antialias: true; colorManagement: true; physicallyCorrectLights: true; logarithmicDepthBuffer: true" | |
| background="color: transparent" | |
| embedded | |
| vr-mode-ui="enabled: false" | |
| style="display:none !important"> | |
| <a-entity id="camera-rig"> | |
| <a-camera | |
| id="main-camera" | |
| position="0 0 0" | |
| near="0.01" | |
| far="1000" | |
| fov="60" | |
| look-controls="enabled: false" | |
| wasd-controls="enabled: false"></a-camera> | |
| </a-entity> | |
| <a-light type="ambient" color="#ffffff" intensity="0.6"></a-light> | |
| <a-light type="directional" color="#ffffff" intensity="0.8" position="1 2 1" cast-shadow="true"></a-light> | |
| <a-assets id="ar-assets" timeout="30000"></a-assets> | |
| </a-scene> | |
| <div id="css3d-layer" aria-hidden="true"></div> | |
| <canvas id="hand-overlay" aria-label="Hand tracking overlay"></canvas> | |
| <div id="world-label-layer" aria-live="polite"></div> | |
| <div id="ui-layer" aria-live="polite"></div> | |
| <div id="loading-screen" class="loading-screen"> | |
| <div class="loading-card glass-panel"> | |
| <div class="loading-ring" aria-hidden="true"></div> | |
| <p class="caption">JARVIS REAL AR</p> | |
| <h1>Spatial Runtime</h1> | |
| <p id="loading-text">Preparing secure AR runtime...</p> | |
| <div class="progress-track"><span id="loading-progress"></span></div> | |
| <!-- Stated before the browser's permission prompt appears, not after. An unexplained | |
| camera request is the highest-drop-off moment in any WebAR product, and this one is | |
| asking a subscriber to point a camera around their home. --> | |
| <div class="gate-consent"> | |
| <svg class="gate-consent-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.8 7.8h11.4a2 2 0 0 1 2 2v6.4a2 2 0 0 1-2 2H2.8a2 2 0 0 1-2-2V9.8a2 2 0 0 1 2-2z"/><path d="m16.2 12.2 5-3v7.6l-5-3z"/></svg> | |
| <p>Next you'll be asked for camera access. Video is processed on your device and is never uploaded or stored.</p> | |
| </div> | |
| <button id="start-ar" class="btn-primary" disabled>PREPARING...</button> | |
| <p class="gate-foot">Requires a rear camera · works best standing</p> | |
| </div> | |
| </div> | |
| <!-- | |
| Both notice hosts share ONE stacking column so they can never overlap. | |
| They used to be two independently positioned fixed elements at hardcoded | |
| top offsets (78px and 152px). That only holds while an error card stays | |
| under 74px tall — and a real one measured 112px in a live session, so the | |
| fallback-tracking error and the wake-word banner drew on top of each other | |
| and neither was fully readable. The offsets stay separate elements for the | |
| two distinct aria-live politeness levels; only the layout is shared. | |
| --> | |
| <div id="notice-stack"> | |
| <aside id="error-host" aria-live="assertive"></aside> | |
| <aside id="banner-host" aria-live="polite"></aside> | |
| </div> | |
| <nav id="hud-toolbar" class="hud-toolbar glass-panel" aria-label="AR controls"> | |
| <!-- Mode selector, per the Claude Design `hud/mode-toolbar` component. These six were bare | |
| characters (M S G B VR J) whose only real label lived in title/aria-label. A phone held at | |
| arm's length has no hover, so a tooltip-only label is one the user can never read — the | |
| word is now permanent under each icon, and the glyph carries the meaning. The decorative | |
| svg is aria-hidden so a screen reader announces each mode once, not twice. --> | |
| <button class="btn-symbol selected" data-action="models" title="Models" aria-label="Models" aria-pressed="true"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.6 21 7v10l-9 4.4L3 17V7z"/><path d="M3 7l9 4.4L21 7M12 11.4V21.4"/></svg></span> | |
| <span class="btn-label">Models</span> | |
| </button> | |
| <button class="btn-symbol" data-action="scan" title="Scan room" aria-label="Scan room" aria-pressed="false"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8V5.2A2.2 2.2 0 0 1 5.2 3H8M16 3h2.8A2.2 2.2 0 0 1 21 5.2V8M21 16v2.8a2.2 2.2 0 0 1-2.2 2.2H16M8 21H5.2A2.2 2.2 0 0 1 3 18.8V16"/><path d="M3 12h18" opacity=".9"/></svg></span> | |
| <span class="btn-label">Scan</span> | |
| </button> | |
| <button class="btn-symbol" data-action="games" title="Games" aria-label="Games" aria-pressed="false"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2.4" y="7.4" width="19.2" height="9.2" rx="4.4"/><path d="M7 10.6v2.8M8.4 12H5.6M15.6 11.4h.01M17.9 13.2h.01"/></svg></span> | |
| <span class="btn-label">Games</span> | |
| </button> | |
| <button class="btn-symbol" data-action="builder" title="Builder" aria-label="Builder" aria-pressed="false"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a3.6 3.6 0 0 0 4.8 4.7l2 2-4.5 4.5-2-2a3.6 3.6 0 0 0-4.7-4.8z"/><path d="M9.4 14.6 3.6 20.4"/></svg></span> | |
| <span class="btn-label">Builder</span> | |
| </button> | |
| <button class="btn-symbol" data-action="vr" title="VR screen" aria-label="VR screen" aria-pressed="false"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2.2" y="7.6" width="19.6" height="8.8" rx="3"/><path d="M9.6 16.4c-.5-1.7-1-2.5-2.2-2.5s-2 .9-2 2.2M14.4 16.4c.5-1.7 1-2.5 2.2-2.5s2 .9 2 2.2"/></svg></span> | |
| <span class="btn-label">VR</span> | |
| </button> | |
| <button class="btn-symbol" data-action="media" title="Media" aria-label="Local media viewer" aria-pressed="false"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2.4" y="4.6" width="19.2" height="13" rx="2.4"/><path d="M10.2 9.2 14.6 11.1 10.2 13z"/><path d="M7 21h10"/></svg></span> | |
| <span class="btn-label">Media</span> | |
| </button> | |
| <button class="btn-symbol" data-action="jarvis" title="JARVIS" aria-label="JARVIS" aria-pressed="false"> | |
| <span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.1"/><circle cx="12" cy="12" r="8.4" opacity=".55"/><path d="M12 1.2v2.4M12 20.4v2.4M22.8 12h-2.4M3.6 12H1.2"/></svg></span> | |
| <span class="btn-label">JARVIS</span> | |
| </button> | |
| </nav> | |
| <section id="status-panel" class="status-panel glass-panel"> | |
| <div> | |
| <span>Camera</span> | |
| <strong id="status-camera">standby</strong> | |
| </div> | |
| <div> | |
| <span>Tracking</span> | |
| <strong id="status-tracking">waiting</strong> | |
| </div> | |
| <div> | |
| <span>OMEGA</span> | |
| <strong id="status-omega">offline</strong> | |
| </div> | |
| </section> | |
| <!-- Part 38 Claude Design pass: webar/assistant-bubble.html. | |
| data-state is driven from jarvis/assistant-state.js, which is itself driven by the | |
| REAL SpeechSynthesis utterance lifecycle — so the indicator cannot outlive the thing | |
| it indicates. The glyph is four <i> bars restyled per state (meter / orbit / wave / | |
| still), not an emoji: emoji render differently on every device and a screen reader | |
| announces whatever that device's font happens to call the codepoint. --> | |
| <section id="jarvis-bubble" class="jarvis-bubble glass-panel hidden" aria-live="polite" data-state="idle"> | |
| <button type="button" id="jarvis-bubble-close" class="bubble-x" aria-label="Dismiss">×</button> | |
| <span><span class="state-glyph" aria-hidden="true"><i></i><i></i><i></i><i></i></span><span id="jarvis-bubble-label">JARVIS</span></span> | |
| <p id="jarvis-bubble-text"></p> | |
| </section> | |
| <template id="panel-template"> | |
| <section class="ar-panel glass-panel"> | |
| <!-- Part 41 Claude Design pass: webar/info-model-panel.html. This button was EMPTY — | |
| a 52px solid red circle with nothing in it, reading as a warning dot rather than a | |
| control, and sitting top-left while minimise sat top-right. It now carries a real | |
| glyph and moves into the header beside minimise. --> | |
| <button class="panel-close" aria-label="Close panel"><span aria-hidden="true">×</span></button> | |
| <div class="panel-drag-handle" aria-hidden="true"></div> | |
| <header> | |
| <h2></h2> | |
| <button class="panel-minimize btn-symbol" aria-label="Minimize"><span class="icon">-</span></button> | |
| </header> | |
| <div class="panel-body"></div> | |
| <div class="ornament"></div> | |
| <div class="panel-resize-handle" aria-hidden="true"></div> | |
| </section> | |
| </template> | |
| <!-- S4: access gate loads first; it imports ar-init.js only after the | |
| cloud access key is validated server-side. Keeps the Space public | |
| while locking the app behind a real backend-enforced password. --> | |
| <script> | |
| if ('serviceWorker' in navigator) { | |
| window.addEventListener('load', () => { | |
| const scope = new URL('.', window.location.href).pathname | |
| navigator.serviceWorker.register('service-worker.js', { scope }) | |
| .catch(error => { | |
| window.dispatchEvent(new CustomEvent('jarvis-ar-service-worker-error', { | |
| detail: { message: error.message || 'Service worker registration failed' }, | |
| })) | |
| }) | |
| }) | |
| } | |
| </script> | |
| </body> | |
| </html> | |