Skip to content

Add: 3D Virtual robotics maze#12

Open
Lakshya-2440 wants to merge 2 commits intoalphaonelabs:mainfrom
Lakshya-2440:3dmaze
Open

Add: 3D Virtual robotics maze#12
Lakshya-2440 wants to merge 2 commits intoalphaonelabs:mainfrom
Lakshya-2440:3dmaze

Conversation

@Lakshya-2440
Copy link
Copy Markdown
Contributor

The 3D Maze app is an interactive web application that allows users to explore and navigate a virtual maze rendered in three dimensions. Users can move through the maze, experience immersive graphics, and challenge themselves to find the exit. The app is designed for both entertainment and educational purposes, showcasing 3D rendering, user controls, and real-time interaction within a browser environment.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Walkthrough

This PR introduces a complete browser-based robotics playground featuring two interactive experiences: a gesture-controlled 3D maze simulator and a robot assembly simulator. Both support optional MediaPipe hand-gesture control with keyboard fallback, canvas-based rendering, physics simulation, and responsive UI overlays with tutorials.

Changes

Cohort / File(s) Summary
Maze Experience
3D/app.js, 3D/index.html, 3D/style.css
Complete maze simulator with deterministic PRNG-based level generation, first-person raycasting renderer, hand gesture and keyboard controls, collision-aware robot movement, object pickup/drop, human NPC agents, lidar-like detection panel, and maze completion detection. Includes MediaPipe hand-tracking initialization with graceful fallback to keyboard mode.
Assembly Experience
3D/assemble-app.js, 3D/assemble.html, 3D/assemble-style.css
Standalone 2D assembly simulator with robot module selection via gesture-controlled toolbox, rotating robot preview reflecting equipped parts and held items, camera frame mirroring, hand landmark detection and gesture classification with stability filtering, and lidar scanning when equipped. Supports keyboard W/A/S/D movement in manual mode.
Navigation & Documentation
index.html, home.html, README.md
Updated landing page branding and navigation buttons linking to new 3D experiences; added toolbox grid links to assembly and maze modes; rewrote README with new section structure describing experiences, technical highlights, and project layout.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as Maze App
    participant Camera as Camera/Vision
    participant Canvas as Canvas Renderer
    participant Sim as Simulation

    User->>App: Load page / Grant camera access
    App->>Camera: Request MediaPipe setup & camera stream
    alt Gesture Mode
        Camera-->>App: Hand landmarks detected
        App->>App: Classify gestures + filter stability
    else Keyboard Mode
        User->>App: Press W/A/S/D or Space
    end
    
    loop Animation Frame
        App->>Sim: Update robot position based on gesture/input
        Sim->>Sim: Check collisions, move NPCs, update state
        App->>Canvas: Raycast walls for first-person view
        App->>Canvas: Render objects/humans/exit with depth
        Canvas->>Canvas: Draw HUD, toolbox, lidar panel
        Canvas-->>User: Display interactive maze
    end
    
    User->>App: Reach exit marker
    App->>Sim: Detect proximity, trigger reset
    Sim->>App: Generate new maze layout
Loading
sequenceDiagram
    participant User
    participant App as Assembly App
    participant Camera as Camera/Vision
    participant Canvas as Canvas Renderer
    participant Sim as Simulation

    User->>App: Load page / Grant camera access
    App->>Camera: Load MediaPipe & request camera stream
    alt Gesture Mode
        Camera->>App: Mirror video feed
        Camera-->>App: Hand landmarks per frame
        App->>App: Classify gestures, update cursor
    else Keyboard Mode
        User->>App: Press W/A/S/D, hover toolbox, pinch
    end
    
    loop Animation Frame
        alt Cursor not interacting with toolbox
            App->>Sim: Update robot position/angle from gesture
        end
        App->>Sim: Check for stable fist gesture or Space key
        Sim->>Sim: Trigger pickup/drop or rotate equipped module
        App->>Canvas: Draw ground, grid, objects, humans
        App->>Canvas: Render robot model with equipped parts
        App->>Canvas: Draw toolbox UI + camera overlay panel
        Canvas-->>User: Display assembly interface
    end
    
    User->>App: Select toolbox part via pinch
    App->>Sim: Equip selected module on robot
    Sim->>Canvas: Update preview to show equipped part
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main addition: a new 3D virtual robotics maze experience with interactive navigation and gesture controls.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@3D/app.js`:
- Around line 179-193: Rename the confusing bgr helper to a clearer name (e.g.,
bgrToRgb or colorFromBgr) and update all usages and related helpers to match
(rename bgrAlpha to bgrToRgba or colorFromBgrAlpha if desired) so the function
intent — converting a BGR array to an RGB(A) CSS string — is explicit; modify
the function declaration for bgr (and bgrAlpha) to the new names and update any
references in the codebase, keeping the colorCache key logic and return format
unchanged.
- Around line 55-68: The MEDIAPIPE_SOURCES array includes an unpinned CDN entry
using "@latest" which risks unexpected breaks; update the fallback entry in
MEDIAPIPE_SOURCES to a specific version (e.g., match "0.10.21" or another chosen
semver like "@0.10" if supported) for both moduleUrl and wasmRoot so all sources
are version-pinned; edit the object that currently contains moduleUrl
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/vision_bundle.mjs"
and its wasmRoot to use the chosen pinned version string, keeping the other two
entries unchanged.
- Around line 2411-2413: The calls resetMazeWorld(), drawBootScreen(), and
init() run immediately at module load and can fail if DOM elements are not yet
available; wrap these calls inside a DOMContentLoaded listener (e.g.,
document.addEventListener('DOMContentLoaded', ...)) so that resetMazeWorld,
drawBootScreen, and init execute only after the DOM is ready, preserving the
current call order and behavior.
- Around line 2106-2122: The final error message in loadVisionModule currently
only shows the first collected error; update the thrown Error to include all
entries from the errors array (e.g., join them with a separator and include a
count) so callers see every CDN failure from MEDIAPIPE_SOURCES; keep existing
behavior of setting activeWasmRoot and rethrowing a descriptive Error that
combines a short summary with the joined errors for easier debugging.
- Around line 74-76: The code assumes DOM elements exist; add null checks after
calling document.getElementById for "simCanvas" and "cameraVideo" to avoid
TypeError when accessing canvas.getContext or using video; for example, verify
that const canvas and const video are non-null, and if either is null call a
clear failure path (throw an Error or log via console.error and return/disable
initialization) and only then call canvas.getContext("2d") to set ctx; reference
the existing symbols canvas, ctx, video and document.getElementById in your fix.
- Line 2060: The empty function drawModeBanner is dead code but still invoked
with a ctx argument elsewhere; either remove the drawModeBanner declaration and
its call site so there's no unused function/call, or if it's intended for future
use, add a clear TODO comment inside drawModeBanner and update its signature to
accept the ctx parameter (or actually use ctx) to match the call site; ensure
you update or remove the caller that currently passes ctx to drawModeBanner
accordingly.
- Around line 35-42: Remove the unused HAND_CONNECTIONS constant declaration
from the file (the const HAND_CONNECTIONS = [...] block) since it is not
referenced anywhere; delete that block and run the linter/tests to confirm
nothing else depends on HAND_CONNECTIONS (search for the symbol to be sure) and
then commit the change.

In `@3D/assemble-app.js`:
- Around line 1666-1684: The keydown handler must ignore auto-repeated events
for one-shot actions (pickup/drop and toolbox cycling): check event.repeat (or
maintain a pressed-key set) and return early for repeats when handling Space or
number keys; specifically, in the window.addEventListener("keydown") handler,
guard the branches that call tryPickup/tryDrop (when checking heldObject and
event.code === "Space") and the toolbox cycling branch that calls
cycleSelectedPart(TOOLBOX_CATEGORIES[...]) (when controlMode === "manual" and
event.key between "1" and "5") so they only run on the initial keydown
(non-repeat). Use event.repeat or a small map keyed by event.code to suppress
repeated firings while still allowing manualKeys tracking to behave correctly.
- Around line 46-60: MEDIAPIPE_SOURCES mixes pinned and mutable versions which
can load an incompatible bundle; update every moduleUrl and wasmRoot entry in
the MEDIAPIPE_SOURCES array to the same explicit version (e.g., replace any
"@latest" entries with "@0.10.21") and also change HAND_LANDMARKER_MODEL_URL to
point to the matching pinned model version rather than "latest" so the bundle
and model remain version-consistent across all CDNs.

In `@3D/assemble-style.css`:
- Around line 58-156: The tutorial CSS is duplicated; extract the shared rules
(.tutorial-pop, .tutorial-card, .tutorial-kicker, .tutorial-card h2,
.tutorial-card p, .tutorial-card ul, .tutorial-card li + li, .tutorial-close,
.tutorial-action and their :hover states) into a single common stylesheet (e.g.,
shared-tutorial.css) and import that from both 3D/style.css and
3D/assemble-style.css, leaving only page-specific overrides (colors, layout
widths, paddings or unique selectors) in each file so future fixes apply once.

In `@3D/assemble.html`:
- Around line 19-41: The dialog with id="assembly-tutorial" declares aria-modal
but doesn't move or trap focus; update the assemblyTutorial initialization to
save document.activeElement before opening, programmatically focus the first
focusable control inside the dialog (e.g., element ids "assembly-tutorial-close"
or "assembly-tutorial-dismiss"), install keyboard handlers to trap Tab/Shift+Tab
within the dialog and to close on Escape by calling closeAssemblyTutorial, and
when closing restore focus to the previously focused element; additionally, make
background content non-tabbable while open (e.g., set inert/aria-hidden or
disable tabbable elements outside the assemblyTutorial) so the "Move to Maze"
link (class "mode-jump") and other controls cannot be focused while the modal is
open.

In `@3D/index.html`:
- Around line 20-48: The dialog with id "maze-tutorial" needs real modal focus
management: when opened, programmatically set focus into the dialog (e.g., to
the button with id "maze-tutorial-dismiss" or the dialog container), save the
previously focused element, add a keydown handler to trap Tab/Shift+Tab within
the dialog (using the dialog's focusable elements) and ignore outside focus
movement, and on close (handlers for "maze-tutorial-close" and
"maze-tutorial-dismiss") restore focus to the saved element and remove the
keydown trap. Also mark the background content as inert/unfocusable while open
(e.g., toggle aria-hidden or inert on the nav.route-nav and main content) so
keyboard/screen‑reader users cannot reach elements behind the modal; ensure
these attributes are removed when the dialog is dismissed. Ensure all changes
target the DOM nodes referenced by mazeTutorial, "maze-tutorial-close", and
"maze-tutorial-dismiss".

In `@index.html`:
- Around line 593-626: The onClick handlers on the anchor elements (the tech-btn
anchors that call document.getElementById('page-transition') and set
window.location.href) unconditionally call preventDefault(), breaking modified
clicks and assistive navigation; modify each onClick to only call
e.preventDefault() when it's an unmodified primary click (e.button === 0 and
!e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey), then run the overlay
activation and timed navigation; also add brief accessibility attributes to the
anchors (e.g., aria-label describing the transition and aria-describedby or
aria-controls pointing to the 'page-transition' overlay) so screen readers
understand the enhanced behavior.

In `@README.md`:
- Around line 141-152: In the "Option 1: Open directly" README section, replace
the incorrect clone and cd commands referencing
alphaonelabs-virtual-robotics-playground with the correct repository (git clone
https://github.com/alphaonelabs/botlab.git and cd botlab) and update the
numbered step "2. Open `index.html`..." to "1. Open `index.html`..." so the
ordered list is sequential (this fixes the wrong repository reference and the
MD029 lint issue); ensure these changes are applied inside the existing code
block in that section.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: eaee2086-071f-41b2-989e-f1855ca8e5aa

📥 Commits

Reviewing files that changed from the base of the PR and between 022f952 and 82d84c4.

⛔ Files ignored due to path filters (1)
  • .DS_Store is excluded by !**/.DS_Store
📒 Files selected for processing (9)
  • 3D/app.js
  • 3D/assemble-app.js
  • 3D/assemble-style.css
  • 3D/assemble.html
  • 3D/index.html
  • 3D/style.css
  • README.md
  • home.html
  • index.html

Comment on lines +35 to +42
const HAND_CONNECTIONS = [
[0, 1], [1, 2], [2, 3], [3, 4],
[0, 5], [5, 6], [6, 7], [7, 8],
[0, 9], [9, 10], [10, 11], [11, 12],
[0, 13], [13, 14], [14, 15], [15, 16],
[0, 17], [17, 18], [18, 19], [19, 20],
[5, 9], [9, 13], [13, 17]
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for HAND_CONNECTIONS usage
rg -n "HAND_CONNECTIONS" 3D/app.js

Repository: alphaonelabs/botlab

Length of output: 92


🏁 Script executed:

rg "HAND_CONNECTIONS" --type js --type ts

Repository: alphaonelabs/botlab

Length of output: 145


Remove the unused HAND_CONNECTIONS constant.

This constant defines MediaPipe hand skeleton connections but isn't referenced anywhere in the codebase. It appears to be leftover from earlier development—perhaps intended for a hand skeleton overlay feature that wasn't completed. Removing it will keep the code cleaner and easier to maintain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/app.js` around lines 35 - 42, Remove the unused HAND_CONNECTIONS constant
declaration from the file (the const HAND_CONNECTIONS = [...] block) since it is
not referenced anywhere; delete that block and run the linter/tests to confirm
nothing else depends on HAND_CONNECTIONS (search for the symbol to be sure) and
then commit the change.

Comment on lines +55 to +68
const MEDIAPIPE_SOURCES = [
{
moduleUrl: "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.21/vision_bundle.mjs",
wasmRoot: "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.21/wasm"
},
{
moduleUrl: "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/vision_bundle.mjs",
wasmRoot: "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
},
{
moduleUrl: "https://unpkg.com/@mediapipe/tasks-vision@0.10.21/vision_bundle.mjs",
wasmRoot: "https://unpkg.com/@mediapipe/tasks-vision@0.10.21/wasm"
}
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider pinning all MediaPipe CDN versions for reproducibility.

Using @latest (lines 61-62) as a fallback CDN source creates a risk of unexpected breakage if the MediaPipe team releases a version with breaking changes. While I understand this provides a fallback if the pinned version becomes unavailable, it may be better to pin to a specific version across all sources, or at least use a semver range like @0.10 if the CDN supports it.

This is a trade-off between availability and stability—just something to be aware of for production use!

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/app.js` around lines 55 - 68, The MEDIAPIPE_SOURCES array includes an
unpinned CDN entry using "@latest" which risks unexpected breaks; update the
fallback entry in MEDIAPIPE_SOURCES to a specific version (e.g., match "0.10.21"
or another chosen semver like "@0.10" if supported) for both moduleUrl and
wasmRoot so all sources are version-pinned; edit the object that currently
contains moduleUrl
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/vision_bundle.mjs"
and its wasmRoot to use the chosen pinned version string, keeping the other two
entries unchanged.

Comment on lines +74 to +76
const canvas = document.getElementById("simCanvas");
const ctx = canvas.getContext("2d");
const video = document.getElementById("cameraVideo");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add null checks for DOM elements to prevent runtime errors.

If the expected DOM elements (#simCanvas, #cameraVideo) are missing or misspelled in the HTML, these lines will cause a TypeError when attempting to call methods on null. Adding defensive checks here provides a clearer error message and prevents cascading failures.

🛡️ Suggested defensive approach
-const canvas = document.getElementById("simCanvas");
-const ctx = canvas.getContext("2d");
-const video = document.getElementById("cameraVideo");
+const canvas = document.getElementById("simCanvas");
+if (!canvas) {
+  throw new Error("Required element `#simCanvas` not found in the DOM");
+}
+const ctx = canvas.getContext("2d");
+if (!ctx) {
+  throw new Error("Could not get 2D rendering context from canvas");
+}
+const video = document.getElementById("cameraVideo");
+if (!video) {
+  throw new Error("Required element `#cameraVideo` not found in the DOM");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const canvas = document.getElementById("simCanvas");
const ctx = canvas.getContext("2d");
const video = document.getElementById("cameraVideo");
const canvas = document.getElementById("simCanvas");
if (!canvas) {
throw new Error("Required element `#simCanvas` not found in the DOM");
}
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("Could not get 2D rendering context from canvas");
}
const video = document.getElementById("cameraVideo");
if (!video) {
throw new Error("Required element `#cameraVideo` not found in the DOM");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/app.js` around lines 74 - 76, The code assumes DOM elements exist; add
null checks after calling document.getElementById for "simCanvas" and
"cameraVideo" to avoid TypeError when accessing canvas.getContext or using
video; for example, verify that const canvas and const video are non-null, and
if either is null call a clear failure path (throw an Error or log via
console.error and return/disable initialization) and only then call
canvas.getContext("2d") to set ctx; reference the existing symbols canvas, ctx,
video and document.getElementById in your fix.

Comment on lines +179 to +193
function bgr(color) {
const key = color.join(",");
if (!colorCache.has(key)) {
colorCache.set(key, `rgb(${color[2]}, ${color[1]}, ${color[0]})`);
}
return colorCache.get(key);
}

function bgrAlpha(color, alpha) {
return `rgba(${color[2]}, ${color[1]}, ${color[0]}, ${alpha})`;
}

function rgb(r, g, b, alpha = 1) {
return alpha === 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor naming clarity: bgr() function outputs RGB.

The function bgr() accepts a color array in BGR order (common in OpenCV/image processing contexts) and converts it to an RGB CSS string—that's the correct behavior! However, the name might confuse readers who expect it to output BGR. A name like bgrToRgb() or colorFromBgr() would make the transformation explicit.

That said, if this naming convention is established elsewhere in the project (e.g., from MediaPipe or OpenCV integration), keeping it consistent is totally fine!

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/app.js` around lines 179 - 193, Rename the confusing bgr helper to a
clearer name (e.g., bgrToRgb or colorFromBgr) and update all usages and related
helpers to match (rename bgrAlpha to bgrToRgba or colorFromBgrAlpha if desired)
so the function intent — converting a BGR array to an RGB(A) CSS string — is
explicit; modify the function declaration for bgr (and bgrAlpha) to the new
names and update any references in the codebase, keeping the colorCache key
logic and return format unchanged.

fillText(ctx, "FROZEN", SIM_W / 2, 35, 36, bgr([0, 80, 255]), "center", 700);
}

function drawModeBanner() {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Empty function drawModeBanner appears to be dead code.

This function has an empty body but is still called on line 2263 with ctx as an argument (which the function doesn't accept). If this was intended functionality that's no longer needed, consider removing both the function and its call site. If it's planned for future use, a // TODO comment explaining the intent would help future maintainers.

🧹 Option 1: Remove if not needed
-function drawModeBanner() {}

And remove the call on line 2263:

-  drawModeBanner(ctx);
📝 Option 2: Add TODO if planned
-function drawModeBanner() {}
+// TODO: Implement mode banner overlay (e.g., "GESTURE MODE" / "MANUAL MODE")
+function drawModeBanner(context) {
+  // Placeholder for future implementation
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function drawModeBanner() {}
// TODO: Implement mode banner overlay (e.g., "GESTURE MODE" / "MANUAL MODE")
function drawModeBanner(context) {
// Placeholder for future implementation
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/app.js` at line 2060, The empty function drawModeBanner is dead code but
still invoked with a ctx argument elsewhere; either remove the drawModeBanner
declaration and its call site so there's no unused function/call, or if it's
intended for future use, add a clear TODO comment inside drawModeBanner and
update its signature to accept the ctx parameter (or actually use ctx) to match
the call site; ensure you update or remove the caller that currently passes ctx
to drawModeBanner accordingly.

Comment on lines +58 to +156
.tutorial-pop {
position: fixed;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(0, 0, 0, 0.48);
backdrop-filter: blur(4px);
}

.tutorial-pop.is-hidden {
display: none;
}

.tutorial-card {
position: relative;
width: min(460px, 100%);
padding: 22px 22px 18px;
border: 1px solid rgba(96, 165, 250, 0.55);
border-radius: 20px;
background: rgba(6, 12, 20, 0.96);
color: #e5eef8;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
}

.tutorial-kicker {
margin: 0 0 8px;
color: #93c5fd;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
}

.tutorial-card h2 {
margin: 0 0 10px;
color: #fff;
font-size: 28px;
line-height: 1.05;
}

.tutorial-card p {
margin: 0 0 12px;
color: #cbd5e1;
font-size: 14px;
line-height: 1.5;
}

.tutorial-card ul {
margin: 0 0 16px;
padding-left: 18px;
color: #dbe7f5;
font-size: 14px;
line-height: 1.5;
}

.tutorial-card li + li {
margin-top: 8px;
}

.tutorial-close {
position: absolute;
top: 12px;
right: 12px;
width: 34px;
height: 34px;
border: 0;
border-radius: 999px;
background: rgba(30, 41, 59, 0.95);
color: #e2e8f0;
font-size: 20px;
cursor: pointer;
}

.tutorial-close:hover {
background: rgba(51, 65, 85, 0.98);
}

.tutorial-action {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 16px;
border: 1px solid rgba(34, 197, 94, 0.65);
border-radius: 999px;
background: rgba(20, 83, 45, 0.95);
color: #f0fdf4;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
cursor: pointer;
}

.tutorial-action:hover {
background: rgba(22, 101, 52, 1);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

The tutorial styling is already duplicated across both 3D entry pages.

This block is almost the same as 3D/style.css, so any future fix to the modal or button states will have to be made twice. Consider extracting the shared tutorial/card/button rules into one common stylesheet and leaving only page-specific colors/layout here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/assemble-style.css` around lines 58 - 156, The tutorial CSS is duplicated;
extract the shared rules (.tutorial-pop, .tutorial-card, .tutorial-kicker,
.tutorial-card h2, .tutorial-card p, .tutorial-card ul, .tutorial-card li + li,
.tutorial-close, .tutorial-action and their :hover states) into a single common
stylesheet (e.g., shared-tutorial.css) and import that from both 3D/style.css
and 3D/assemble-style.css, leaving only page-specific overrides (colors, layout
widths, paddings or unique selectors) in each file so future fixes apply once.

Comment on lines +19 to +41
<div class="tutorial-pop" id="assembly-tutorial" role="dialog" aria-modal="true" aria-labelledby="assembly-tutorial-title">
<div class="tutorial-card">
<button class="tutorial-close" id="assembly-tutorial-close" aria-label="Close tutorial">×</button>
<div class="tutorial-kicker">Quick Start</div>
<h2 id="assembly-tutorial-title">3D Assembly</h2>
<p>Use your right hand to move the robot:</p>
<ul>
<li><strong>1 finger</strong> = move forward</li>
<li><strong>2 fingers</strong> = move backward</li>
<li><strong>3 fingers</strong> = move right</li>
<li><strong>4 fingers</strong> = move left</li>
</ul>
<button class="tutorial-action" id="assembly-tutorial-dismiss">Got It</button>
</div>
</div>
<a href="./index.html" class="mode-jump" aria-label="Move to 3D maze page">Move to Maze</a>
<canvas id="simCanvas" width="1920" height="1080"></canvas>
<video id="cameraVideo" autoplay playsinline muted></video>
<script>
const assemblyTutorial = document.getElementById("assembly-tutorial");
const closeAssemblyTutorial = () => assemblyTutorial.classList.add("is-hidden");
document.getElementById("assembly-tutorial-close").addEventListener("click", closeAssemblyTutorial);
document.getElementById("assembly-tutorial-dismiss").addEventListener("click", closeAssemblyTutorial);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The tutorial is marked modal, but it does not behave like one.

aria-modal="true" is set, but focus is never moved into the dialog and the Move to Maze link remains tabbable behind it. Keyboard users can navigate outside the overlay, so the modal semantics are currently misleading. Please move initial focus into the dialog and trap/restore focus when it closes; Escape-to-dismiss would help too.

As per coding guidelines, **/*.html: Review HTML templates for accessibility (ARIA attributes, semantic elements), XSS risks from unescaped user content, and proper use of template inheritance.

🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 21-21: The type attribute must be present on elements.

(button-type-require)


[warning] 31-31: The type attribute must be present on

elements.

(button-type-require)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/assemble.html` around lines 19 - 41, The dialog with
id="assembly-tutorial" declares aria-modal but doesn't move or trap focus;
update the assemblyTutorial initialization to save document.activeElement before
opening, programmatically focus the first focusable control inside the dialog
(e.g., element ids "assembly-tutorial-close" or "assembly-tutorial-dismiss"),
install keyboard handlers to trap Tab/Shift+Tab within the dialog and to close
on Escape by calling closeAssemblyTutorial, and when closing restore focus to
the previously focused element; additionally, make background content
non-tabbable while open (e.g., set inert/aria-hidden or disable tabbable
elements outside the assemblyTutorial) so the "Move to Maze" link (class
"mode-jump") and other controls cannot be focused while the modal is open.

Comment on lines +20 to +48
<div class="tutorial-pop" id="maze-tutorial" role="dialog" aria-modal="true" aria-labelledby="maze-tutorial-title">
<div class="tutorial-card">
<button class="tutorial-close" id="maze-tutorial-close" aria-label="Close tutorial">×</button>
<div class="tutorial-kicker">Quick Start</div>
<h2 id="maze-tutorial-title">3D Maze</h2>
<p>Use your right hand to move the robot:</p>
<ul>
<li><strong>1 finger</strong> = move forward</li>
<li><strong>2 fingers</strong> = move backward</li>
<li><strong>3 fingers</strong> = move right</li>
<li><strong>4 fingers</strong> = move left</li>
<li><strong>M</strong> = new maze</li>
</ul>
<button class="tutorial-action" id="maze-tutorial-dismiss">Got It</button>
</div>
</div>
<nav class="route-nav" aria-label="Playground pages">
<a href="../index.html">Main</a>
<a href="../home.html">2D Robotics</a>
<a href="./assemble.html">3D Assembly</a>
<a href="./index.html" class="is-active" aria-current="page">3D Maze</a>
</nav>
<canvas id="simCanvas" width="1920" height="1080"></canvas>
<video id="cameraVideo" autoplay playsinline muted></video>
<script>
const mazeTutorial = document.getElementById("maze-tutorial");
const closeMazeTutorial = () => mazeTutorial.classList.add("is-hidden");
document.getElementById("maze-tutorial-close").addEventListener("click", closeMazeTutorial);
document.getElementById("maze-tutorial-dismiss").addEventListener("click", closeMazeTutorial);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This dialog also needs real modal focus management.

While the tutorial is open, the route nav behind it is still reachable by keyboard. That makes aria-modal="true" incorrect in practice and creates a confusing first-run experience for keyboard and screen-reader users. Please move focus into the dialog on open and keep it there until the user dismisses it.

As per coding guidelines, **/*.html: Review HTML templates for accessibility (ARIA attributes, semantic elements), XSS risks from unescaped user content, and proper use of template inheritance.

🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 22-22: The type attribute must be present on elements.

(button-type-require)


[warning] 33-33: The type attribute must be present on

elements.

(button-type-require)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@3D/index.html` around lines 20 - 48, The dialog with id "maze-tutorial" needs
real modal focus management: when opened, programmatically set focus into the
dialog (e.g., to the button with id "maze-tutorial-dismiss" or the dialog
container), save the previously focused element, add a keydown handler to trap
Tab/Shift+Tab within the dialog (using the dialog's focusable elements) and
ignore outside focus movement, and on close (handlers for "maze-tutorial-close"
and "maze-tutorial-dismiss") restore focus to the saved element and remove the
keydown trap. Also mark the background content as inert/unfocusable while open
(e.g., toggle aria-hidden or inert on the nav.route-nav and main content) so
keyboard/screen‑reader users cannot reach elements behind the modal; ensure
these attributes are removed when the dialog is dismissed. Ensure all changes
target the DOM nodes referenced by mazeTutorial, "maze-tutorial-close", and
"maze-tutorial-dismiss".

Comment on lines +593 to +626
<a href="3D/assemble.html" className="tech-btn" style={{ letterSpacing: '0.14em' }} onClick={(e) => {
e.preventDefault();
const overlay = document.getElementById('page-transition');
overlay.classList.add('active');
setTimeout(() => { window.location.href = '3D/assemble.html'; }, 1200);
}}>
<span className="btn-corner tl"></span>
<span className="btn-corner tr"></span>
<span className="btn-corner bl"></span>
<span className="btn-corner br"></span>
<span className="btn-pulse"></span>
<span className="btn-status"></span>
<span>Assemble a robot in 3D</span>
<svg className="btn-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M20 7.5L12 3 4 7.5m16 0v9L12 21m8-13.5L12 12m0 9-8-4.5v-9M12 21v-9m0 0L4 7.5m8 4.5 8-4.5" />
</svg>
</a>
<a href="3D/index.html" className="tech-btn" style={{ letterSpacing: '0.14em' }} onClick={(e) => {
e.preventDefault();
const overlay = document.getElementById('page-transition');
overlay.classList.add('active');
setTimeout(() => { window.location.href = '3D/index.html'; }, 1200);
}}>
<span className="btn-corner tl"></span>
<span className="btn-corner tr"></span>
<span className="btn-corner bl"></span>
<span className="btn-corner br"></span>
<span className="btn-pulse"></span>
<span className="btn-status"></span>
<span>Run the 3D maze</span>
<svg className="btn-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 7h16M4 12h10M4 17h16" />
</svg>
</a>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the index.html file
find . -name "index.html" -type f | head -20

Repository: alphaonelabs/botlab

Length of output: 91


🏁 Script executed:

# Get file size to understand what we're dealing with
find . -name "index.html" -type f -exec wc -l {} \;

Repository: alphaonelabs/botlab

Length of output: 98


🏁 Script executed:

# Read the specific lines mentioned in the review
sed -n '593,626p' ./index.html

Repository: alphaonelabs/botlab

Length of output: 2152


🏁 Script executed:

# Get context around these lines to understand the structure
sed -n '580,640p' ./index.html

Repository: alphaonelabs/botlab

Length of output: 3803


Preserve native link behavior for the new transition CTAs and add accessibility attributes.

These handlers call preventDefault() unconditionally, which blocks Cmd/Ctrl+Click (open in new tab), Shift+Click (open in new window), and middle-click navigation—affecting power users and users relying on keyboard navigation. Additionally, screen readers may not properly understand the enhanced link behavior.

Please guard the prevention to only intercept unmodified primary clicks, and add ARIA attributes to clarify the enhanced interaction:

Suggested improvements
-             <a href="3D/assemble.html" className="tech-btn" style={{ letterSpacing: '0.14em' }} onClick={(e) => {
+             <a href="3D/assemble.html" className="tech-btn" style={{ letterSpacing: '0.14em' }} role="link" aria-label="Assemble a robot in 3D with animated transition" onClick={(e) => {
+              if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
+                return;
+              }
               e.preventDefault();
               const overlay = document.getElementById('page-transition');
               overlay.classList.add('active');
               setTimeout(() => { window.location.href = '3D/assemble.html'; }, 1200);
             }}>
              
-             <a href="3D/index.html" className="tech-btn" style={{ letterSpacing: '0.14em' }} onClick={(e) => {
+             <a href="3D/index.html" className="tech-btn" style={{ letterSpacing: '0.14em' }} role="link" aria-label="Run the 3D maze with animated transition" onClick={(e) => {
+              if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
+                return;
+              }
               e.preventDefault();
               const overlay = document.getElementById('page-transition');
               overlay.classList.add('active');
               setTimeout(() => { window.location.href = '3D/index.html'; }, 1200);
             }}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@index.html` around lines 593 - 626, The onClick handlers on the anchor
elements (the tech-btn anchors that call
document.getElementById('page-transition') and set window.location.href)
unconditionally call preventDefault(), breaking modified clicks and assistive
navigation; modify each onClick to only call e.preventDefault() when it's an
unmodified primary click (e.button === 0 and !e.metaKey && !e.ctrlKey &&
!e.shiftKey && !e.altKey), then run the overlay activation and timed navigation;
also add brief accessibility attributes to the anchors (e.g., aria-label
describing the transition and aria-describedby or aria-controls pointing to the
'page-transition' overlay) so screen readers understand the enhanced behavior.

Comment on lines +141 to +152
### Option 1: Open directly

You can open the project directly in a browser:

1. Clone the repository:

```bash
git clone https://github.com/alphaonelabs/alphaonelabs-virtual-robotics-playground.git
cd alphaonelabs-virtual-robotics-playground
```

- The **shape** of the detected object — `Square`, `Circle`, or `Triangle`
- The **color** of the detected object — e.g. `Red`, `Orange`, `Green`, `Blue`
2. Open `index.html` in a modern browser.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the Getting Started commands in this block.

This PR is on alphaonelabs/botlab, so the current clone URL and cd target point to a different repository. The same list also trips MD029 if markdownlint is enforced.

Suggested doc fix
  1. Clone the repository:
  
  ```bash
-git clone https://github.com/alphaonelabs/alphaonelabs-virtual-robotics-playground.git
-cd alphaonelabs-virtual-robotics-playground
+git clone https://github.com/alphaonelabs/botlab.git
+cd botlab

@@
-2. Open index.html in a modern browser.
+1. Open index.html in a modern browser.

</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.0)</summary>

[warning] 152-152: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1

(MD029, ol-prefix)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @README.md around lines 141 - 152, In the "Option 1: Open directly" README
section, replace the incorrect clone and cd commands referencing
alphaonelabs-virtual-robotics-playground with the correct repository (git clone
https://github.com/alphaonelabs/botlab.git and cd botlab) and update the
numbered step "2. Open index.html..." to "1. Open index.html..." so the
ordered list is sequential (this fixes the wrong repository reference and the
MD029 lint issue); ensure these changes are applied inside the existing code
block in that section.


</details>

<!-- fingerprinting:phantom:medusa:grasshopper:5bcef4a9-f051-44e8-a0cd-a44f25634038 -->

<!-- This is an auto-generated comment by CodeRabbit -->

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant