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) <noreply@anthropic.com>main
parent
e4076c790b
commit
a5da08ce3a
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.card}>
|
||||
<h2 className={`${styles.title} font-title`}>TIPS</h2>
|
||||
<ol className={styles.tips}>
|
||||
<li>Bring your foot fully back to center after each step</li>
|
||||
<li>Try to stay in one spot</li>
|
||||
<li>Face the camera with your body</li>
|
||||
<li>
|
||||
Stand so you're fully visible — your body should be above the red
|
||||
line, and keep your head in frame too
|
||||
</li>
|
||||
</ol>
|
||||
<button
|
||||
className={`${styles.dismissBtn} font-title`}
|
||||
onClick={() => {
|
||||
playSound(SFX_BUTTON_DEFAULT)
|
||||
onDismiss()
|
||||
}}
|
||||
>
|
||||
GOT IT
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue