Mount two first-time tutorial overlays driven by the game state machine:
- MovementTutorial: visible during the intro reveal and the free-walk
step before the ebike mount, dismissed on the first Z/Q/S/D keydown.
- HandTrackingTutorial: visible during the early ebike repair steps
(fragmented, scanning, inspected), dismissed when MediaPipe detects
any hand on screen.
Both share a generic TutorialOverlay shell (transparent panel, dark
blue border, lucide-react Hand / inline ZQSD keycap icons, centered
text). The overlay sits at z-index 14, behind Subtitles (15) and
the talkie overlay (16), so dialogue/subtitle UI stays in front.
Dismissals stay persistent for the session: keyboard-triggered uses
event-handler setState; hand-detection uses a guarded effect-driven
setState (same pattern as PylonDownedPylon resync).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the live hand visualizer as a light-blue silhouette with a
crisp dark-blue outline, suitable as the primary hand UI (replacing
the buggy 3D glove for the default flow):
- Palm polygon (landmarks 0,1,5,9,13,17) and five finger tubes merged
via an SVG feMorphology filter, so the outline is a single
continuous ring with no internal seams.
- Q curves bow out to two synthetic wrist corners (perpendicular to
the palm centerline) for a rounded heel of palm.
- Straight L edges between MCPs along the top - the filter dilation
rounds the corners visually, no creux.
- Each finger path starts half a stroke inside the palm so the round
base cap is hidden under the palm fill.
- Whole silhouette shrunk to 65% of the tracked hand size around the
centroid, with 0.8 group opacity, and a faint MediaPipe skeleton
overlay (lines + dots) on top.
Update the static fallback silhouettes (HandTrackingFallback) to a
matching curved-path look in a 100x120 viewBox.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Smaller boxes (36x36 key + 36px-tall label) instead of the previous
oversized white pills.
- Dark translucent background (rgba(10, 12, 20, 0.55)) with a 1px
white outline (rgba(255, 255, 255, 0.7)), no border-radius and
white text so the prompt blends with the dark UI instead of being a
bright blob over the 3D scene.
- Key cube now has a 3D keyboard-key effect (inset top highlight +
inset bottom darkening + small bottom drop) so it reads as a
physical key.
- Key and label are visually separated (gap: 8px) but share the same
height for alignment.
- InteractPrompt no longer renders the label box when focused.label is
empty/whitespace, so callers can show the key prompt alone.
- Larger label box and key cube on white-translucent backgrounds
(rgba(255, 255, 255, 0.92)) with black Inter 900 text and rounded
12px corners + soft drop shadow.
- Move from bottom: 30% to bottom: 12% so the prompt sits closer to
the visual center of attention near focused world objects.
- Key cube grown 24x24 -> 64x64 / font 13 -> 32, label padding 0 ->
16x24 / font 13 -> 22, both bold instead of regular.
- Bump high to 30m chunk / HD<20m and ultra to HD<30m so HD models
persist further before swapping.
- Render the 5 graphics preset cards on a single row.
- Vegetation LOD selection now uses the distance to the nearest instance
in each chunk instead of the chunk centre, matching MapInstancingSystem
and avoiding premature LOD swaps when the camera enters a chunk.
The previous talkie-overlay refactor lost the rest/active behaviour and
left the radio-shake CSS animation running constantly. Restore the
intended polish:
- Position lerp between TALKIE_REST_Y (idle) and TALKIE_ACTIVE_Y
(raised when narrator is speaking) with a subtle floating bob.
- Subtle z-axis float at idle, faster shake when active.
- Gate the CSS radio-shake on the new --active modifier so the talkie
is calm when no narrator dialogue is playing.
- Keep the face-camera rotation [0.18, PI, -0.08] from the original
overlay version.
Visibility still kicks in at the reveal step (no regression on the
recent fix).
- index.css: add visible :focus-visible rings for .site-card-button
and .site-button so keyboard users can see where focus lives
- SiteCard: drop outline:none, add aria-pressed and aria-label so
screen readers announce selection state
- SiteButton: add the .site-button class for the shared focus ring
- SiteDisclaimerScreen: keyboard skip via Enter / Space / Escape, a
role="region" + aria-label wrapper and aria-live="polite" on the
message; honour prefers-reduced-motion on the fade
- IntroVideoPlayer: role="region" with a skip hint in aria-label,
preload="auto", and aria-hidden on the decorative caption span
- Fix all 63 ESLint errors across codebase
- Consolidate MaterialWithTextureSlots type in src/types/three/three.ts
- Add CSS custom properties for design tokens
- Extract ebike constants to src/data/ebike/ebikeConfig.ts
- Add proper TypeScript types for window extensions
- Fix React hooks violations (refs during render, setState in effects)
- Remove unused exports and redundant CSS
- Add type guards for Three.js material handling
- Clean up AI slop comments and legacy CSS patterns