From a5da08ce3a9088b270231262d73eabdb65ff7e98 Mon Sep 17 00:00:00 2001 From: name Date: Sun, 12 Apr 2026 08:59:30 -0700 Subject: [PATCH 1/3] add tutorial overlay component with tips for new players Shows 4 gameplay tips (return foot to center, stay in one spot, face camera, stay fully visible). Stores dismissal in localStorage. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/Tutorial.module.css | 105 +++++++++++++++++++++++++++++ src/components/Tutorial.tsx | 44 ++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/components/Tutorial.module.css create mode 100644 src/components/Tutorial.tsx diff --git a/src/components/Tutorial.module.css b/src/components/Tutorial.module.css new file mode 100644 index 0000000..48e6451 --- /dev/null +++ b/src/components/Tutorial.module.css @@ -0,0 +1,105 @@ +.overlay { + position: fixed; + inset: 0; + z-index: 600; + background: rgba(0, 0, 0, 0.92); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; +} + +.card { + max-width: 400px; + width: 100%; + padding: 32px 28px; + border-radius: 16px; + background: rgba(217, 70, 239, 0.08); + border: 2px solid rgba(217, 70, 239, 0.3); +} + +.title { + font-size: 36px; + color: #d946ef; + text-align: center; + margin: 0 0 24px; + text-shadow: 0 0 30px rgba(217, 70, 239, 0.5); +} + +.tips { + list-style: none; + counter-reset: tip; + padding: 0; + margin: 0 0 28px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.tips li { + counter-increment: tip; + position: relative; + padding-left: 32px; + color: rgba(255, 255, 255, 0.9); + font-size: 16px; + line-height: 1.5; +} + +.tips li::before { + content: counter(tip); + position: absolute; + left: 0; + top: 0; + width: 22px; + height: 22px; + border-radius: 50%; + background: rgba(217, 70, 239, 0.25); + color: #d946ef; + font-size: 13px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.dismissBtn { + display: block; + width: 100%; + padding: 14px 0; + border: 2px solid #d946ef; + border-radius: 8px; + background: rgba(217, 70, 239, 0.2); + color: #fff; + font-size: 20px; + cursor: pointer; + transition: background 0.15s, transform 0.1s; +} + +.dismissBtn:hover { + background: rgba(217, 70, 239, 0.4); +} + +.dismissBtn:active { + transform: scale(0.97); +} + +@media (max-width: 480px) { + .card { + padding: 24px 20px; + } + + .title { + font-size: 28px; + margin-bottom: 20px; + } + + .tips li { + font-size: 15px; + } + + .tips { + gap: 14px; + margin-bottom: 24px; + } +} diff --git a/src/components/Tutorial.tsx b/src/components/Tutorial.tsx new file mode 100644 index 0000000..52d59e1 --- /dev/null +++ b/src/components/Tutorial.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react' +import { playSound, SFX_BUTTON_DEFAULT } from '../lib/sounds' +import styles from './Tutorial.module.css' + +const TUTORIAL_SEEN_KEY = 'tutorialSeen' + +export function useTutorialSeen() { + const [seen, setSeen] = useState( + () => localStorage.getItem(TUTORIAL_SEEN_KEY) === 'true', + ) + const markSeen = () => { + localStorage.setItem(TUTORIAL_SEEN_KEY, 'true') + setSeen(true) + } + return { seen, markSeen } +} + +export function Tutorial({ onDismiss }: { onDismiss: () => void }) { + return ( +
+
+

TIPS

+
    +
  1. Bring your foot fully back to center after each step
  2. +
  3. Try to stay in one spot
  4. +
  5. Face the camera with your body
  6. +
  7. + Stand so you're fully visible — your body should be above the red + line, and keep your head in frame too +
  8. +
+ +
+
+ ) +} From a36b3f8e3e7093cc0fc77a00ec08d289006c0cce Mon Sep 17 00:00:00 2001 From: name Date: Sun, 12 Apr 2026 08:59:36 -0700 Subject: [PATCH 2/3] show tutorial before first play, update escape menu - Show tutorial overlay on first /game visit (localStorage flag) - Add TUTORIAL button to escape menu to re-show tips - Move LOG and fullscreen into escape menu corners (subtle placement) - Change menu button from II to three dots for better mobile UX - Hide AppLayout fullscreen button on /game (now in escape menu) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/AppLayout.tsx | 7 ++-- src/pages/GamePage.module.css | 48 +++++++++++++++++---------- src/pages/GamePage.tsx | 62 +++++++++++++++++++++++++++-------- 3 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/app/AppLayout.tsx b/src/app/AppLayout.tsx index 8956c57..9dfb1da 100644 --- a/src/app/AppLayout.tsx +++ b/src/app/AppLayout.tsx @@ -1,9 +1,12 @@ import { useCallback, useEffect, useState } from 'react' -import { Outlet } from 'react-router-dom' +import { Outlet, useLocation } from 'react-router-dom' +import { ROUTE_PATHS } from './route-paths' import { supportsFullscreen } from '../lib/platform' import styles from './AppLayout.module.css' export function AppLayout() { + const location = useLocation() + const isGamePage = location.pathname === ROUTE_PATHS.game const canFullscreen = supportsFullscreen() const [isFullscreen, setIsFullscreen] = useState(!!document.fullscreenElement) @@ -23,7 +26,7 @@ export function AppLayout() {
- {canFullscreen && !isFullscreen && ( + {canFullscreen && !isFullscreen && !isGamePage && ( - {paused && (
+ + {canFullscreen && !isFullscreen && ( + + )}

PAUSED

+
)} + + {showTutorial && ( + { + markTutorialSeen() + setShowTutorial(false) + }} + /> + )} ) } From 50d873c4217f13857e5072df8eba4b5ea79e0b44 Mon Sep 17 00:00:00 2001 From: name Date: Sun, 12 Apr 2026 09:09:42 -0700 Subject: [PATCH 3/3] add tutorial test page at /test/tutorial Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/AppRoutes.tsx | 2 ++ src/pages/TutorialTestPage.tsx | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/pages/TutorialTestPage.tsx diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index a191e47..f182355 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -10,6 +10,7 @@ const SongSelectionPage = lazy(() => import('../pages/SongSelectionPage').then(m const EndOfGamePage = lazy(() => import('../pages/EndOfGamePage').then(m => ({ default: m.EndOfGamePage }))) const LeaderboardPage = lazy(() => import('../pages/LeaderboardPage').then(m => ({ default: m.LeaderboardPage }))) const CreditsPage = lazy(() => import('../pages/CreditsPage').then(m => ({ default: m.CreditsPage }))) +const TutorialTestPage = lazy(() => import('../pages/TutorialTestPage').then(m => ({ default: m.TutorialTestPage }))) export function AppRoutes() { return ( @@ -23,6 +24,7 @@ export function AppRoutes() { } /> } /> } /> + } /> } /> diff --git a/src/pages/TutorialTestPage.tsx b/src/pages/TutorialTestPage.tsx new file mode 100644 index 0000000..6076153 --- /dev/null +++ b/src/pages/TutorialTestPage.tsx @@ -0,0 +1,10 @@ +import { Tutorial } from '../components/Tutorial' + +export function TutorialTestPage() { + return ( +
+

Debug: tutorial test

+ alert('dismissed')} /> +
+ ) +}