From 79832ed8181eab2b18985a536667abc5c81bf90c Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Thu, 28 May 2026 23:06:48 -0500 Subject: [PATCH] Add help modal and back button to score entry pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HelpModal component shows game setup and scoring rules in a scrollable overlay, triggered by a ? button top-right on Player Setup and Enter Scores. Enter Scores also gets a ← back button top-left that returns to the scoresheet without saving. PageFrame gains a headerActionLeft slot to support the left-side button. Co-Authored-By: Claude Sonnet 4.6 --- src/App.tsx | 2 +- src/components/HelpModal.module.css | 31 +++++++++++++++++++++++++++++ src/components/HelpModal.tsx | 28 ++++++++++++++++++++++++++ src/components/PageFrame.module.css | 8 +++++++- src/components/PageFrame.tsx | 4 +++- src/pages/EnterScores.tsx | 9 ++++++++- src/pages/PlayerSetup.tsx | 5 +++++ src/styles/shared.module.css | 12 +++++++++++ 8 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/components/HelpModal.module.css create mode 100644 src/components/HelpModal.tsx diff --git a/src/App.tsx b/src/App.tsx index b7de2d5..0f5fc57 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -59,5 +59,5 @@ export default function App() { if (page === 'title') return if (page === 'setup') return if (page === 'scoresheet') return setPage('enterscores')} onNewGame={handleNewGame} onEditRound={handleEditRound} /> - if (page === 'enterscores') return + if (page === 'enterscores') return { setEditingRound(null); setPage('scoresheet') }} initialValues={editingRound !== null ? game!.rounds[editingRound] : undefined} /> } diff --git a/src/components/HelpModal.module.css b/src/components/HelpModal.module.css new file mode 100644 index 0000000..141c5a9 --- /dev/null +++ b/src/components/HelpModal.module.css @@ -0,0 +1,31 @@ +.box { + display: flex; + flex-direction: column; + background: var(--color-parchment); + border: 2px solid black; + border-radius: 1.5rem; + width: calc(100% - 4rem); + max-width: 22rem; + max-height: 80vh; + overflow: hidden; +} + +.body { + flex: 1; + overflow-y: auto; + padding: 1.5rem 1.5rem 1rem; + font-size: 0.95rem; + line-height: 1.5; +} + +.dismiss { + margin: 0 1.5rem 1.5rem; + padding: 0.6rem 1rem; + background: var(--color-blue); + color: black; + border: none; + border-radius: 0.75rem; + font-size: 1rem; + font-weight: 600; + flex-shrink: 0; +} diff --git a/src/components/HelpModal.tsx b/src/components/HelpModal.tsx new file mode 100644 index 0000000..08ea5be --- /dev/null +++ b/src/components/HelpModal.tsx @@ -0,0 +1,28 @@ +import styles from './HelpModal.module.css' + +interface Props { + onClose: () => void +} + +export default function HelpModal({ onClose }: Props) { + return ( +
+
+

Setup

+
    +
  • 3 players, remove all 1s and 2s
  • +
  • 4-6 players, use everything
  • +
  • 7-8 players, remove non-yellow 2s
  • +
+

Scoring

+
    +
  • Gold good! Each gold card is +5 points
  • +
  • Red bad! Each bad card -10 points
  • +
  • Black seven very bad: -50 points each
  • +
  • Last trick very good: +50 points
  • +
+
+ +
+ ) +} diff --git a/src/components/PageFrame.module.css b/src/components/PageFrame.module.css index 415ddf8..ce2d7f3 100644 --- a/src/components/PageFrame.module.css +++ b/src/components/PageFrame.module.css @@ -11,10 +11,16 @@ .headerAction { position: absolute; - top: 0.75rem; + top: 1rem; right: 1rem; } +.headerActionLeft { + position: absolute; + top: 1rem; + left: 1rem; +} + .title { margin: 0; font-size: 1.5rem; diff --git a/src/components/PageFrame.tsx b/src/components/PageFrame.tsx index 2c6cfc2..0faa9a5 100644 --- a/src/components/PageFrame.tsx +++ b/src/components/PageFrame.tsx @@ -5,16 +5,18 @@ interface Props { children: React.ReactNode footer?: React.ReactNode headerAction?: React.ReactNode + headerActionLeft?: React.ReactNode overlay?: React.ReactNode } -export default function PageFrame({ title, children, footer, headerAction, overlay }: Props) { +export default function PageFrame({ title, children, footer, headerAction, headerActionLeft, overlay }: Props) { return (

{title}

{headerAction &&
{headerAction}
} + {headerActionLeft &&
{headerActionLeft}
}
{children} diff --git a/src/pages/EnterScores.tsx b/src/pages/EnterScores.tsx index 98980c9..b7f5f56 100644 --- a/src/pages/EnterScores.tsx +++ b/src/pages/EnterScores.tsx @@ -1,20 +1,24 @@ import { useState } from 'react' import PageFrame from '../components/PageFrame' import PrimaryButton from '../components/PrimaryButton' +import HelpModal from '../components/HelpModal' import { isIndividualScoreValid, isRoundScoreValid } from '../game/scoring' import { type GameState } from '../types' import styles from './EnterScores.module.css' +import sharedStyles from '../styles/shared.module.css' interface Props { game: GameState onSubmit: (scores: number[]) => void + onBack: () => void initialValues?: number[] } -export default function EnterScores({ game, onSubmit, initialValues }: Props) { +export default function EnterScores({ game, onSubmit, onBack, initialValues }: Props) { const [values, setValues] = useState(() => game.players.map((_, i) => initialValues ? String(initialValues[i]) : '') ) + const [showHelp, setShowHelp] = useState(false) function updateValue(i: number, v: string) { setValues(vs => vs.map((val, idx) => (idx === i ? v : val))) @@ -38,6 +42,9 @@ export default function EnterScores({ game, onSubmit, initialValues }: Props) { return ( ←} + headerAction={} + overlay={showHelp ? setShowHelp(false)} /> : undefined} footer={ onSubmit(parsed)} disabled={!canSubmit}> Submit diff --git a/src/pages/PlayerSetup.tsx b/src/pages/PlayerSetup.tsx index a1550fe..d5f23f9 100644 --- a/src/pages/PlayerSetup.tsx +++ b/src/pages/PlayerSetup.tsx @@ -1,7 +1,9 @@ import { useState } from 'react' import PageFrame from '../components/PageFrame' import PrimaryButton from '../components/PrimaryButton' +import HelpModal from '../components/HelpModal' import styles from './PlayerSetup.module.css' +import sharedStyles from '../styles/shared.module.css' interface Props { onStart: (players: string[]) => void @@ -14,6 +16,7 @@ function normalize(s: string) { export default function PlayerSetup({ onStart, initialNames }: Props) { const [names, setNames] = useState(initialNames ?? ['', '', '']) + const [showHelp, setShowHelp] = useState(false) function updateName(i: number, value: string) { setNames(n => n.map((v, idx) => (idx === i ? value : v))) @@ -36,6 +39,8 @@ export default function PlayerSetup({ onStart, initialNames }: Props) { return ( setShowHelp(true)}>?} + overlay={showHelp ? setShowHelp(false)} /> : undefined} footer={ onStart(names.map(n => n.trim()))} disabled={!isValid}> Start diff --git a/src/styles/shared.module.css b/src/styles/shared.module.css index 62b5c6b..fc4c22f 100644 --- a/src/styles/shared.module.css +++ b/src/styles/shared.module.css @@ -1,3 +1,15 @@ +.helpBtn { + background: none; + border: none; + padding: 0; + min-height: unset; + font-size: 1.25rem; + font-weight: 700; + color: #aaa; + line-height: 1; + box-shadow: none; +} + .card { position: fixed; inset: 1rem;