From e0a6aed4cf661637d6c769cfa7303749103baeb0 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Wed, 27 May 2026 00:23:36 -0500 Subject: [PATCH] Centralize duplicated card shell, press animation, and color tokens Extract the rounded-card shell and button press animation into a shared CSS module composed by PageFrame, TitleScreen, and all buttons, add color tokens for repeated values, and load the saved game once on mount. Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 6 ++-- src/components/PageFrame.module.css | 16 +++------- src/components/PrimaryButton.module.css | 10 ++---- src/index.css | 8 ++++- src/pages/PlayerSetup.module.css | 41 ++++++++----------------- src/pages/TitleScreen.module.css | 8 +---- src/styles/shared.module.css | 19 ++++++++++++ 7 files changed, 49 insertions(+), 59 deletions(-) create mode 100644 src/styles/shared.module.css diff --git a/src/App.tsx b/src/App.tsx index b501358..8a37b54 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,10 +21,8 @@ function clearGame() { } export default function App() { - const [page, setPage] = useState(() => - loadGame() ? 'scoresheet' : 'title' - ) - const [game, setGame] = useState(() => loadGame()) + const [game, setGame] = useState(loadGame) + const [page, setPage] = useState(() => (game ? 'scoresheet' : 'title')) function handleNewGame() { clearGame() diff --git a/src/components/PageFrame.module.css b/src/components/PageFrame.module.css index ef8c652..e94aaf7 100644 --- a/src/components/PageFrame.module.css +++ b/src/components/PageFrame.module.css @@ -1,12 +1,6 @@ .frame { - position: fixed; - inset: 1rem; - border: 2px solid black; - border-radius: 2rem; - background: #f5f0e4; - display: flex; - flex-direction: column; - overflow: hidden; + composes: card from '../styles/shared.module.css'; + background: var(--color-parchment); } .header { @@ -23,7 +17,7 @@ .rule { position: relative; border: none; - border-top: 1px solid #c8b99a; + border-top: 1px solid var(--color-parchment-border); margin: 0.75rem 0 0; } @@ -33,10 +27,10 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - background: #f5f0e4; + background: var(--color-parchment); padding: 0 0.35em; font-size: 0.55rem; - color: #c8b99a; + color: var(--color-parchment-border); line-height: 1; } diff --git a/src/components/PrimaryButton.module.css b/src/components/PrimaryButton.module.css index 67b6e59..bd5f899 100644 --- a/src/components/PrimaryButton.module.css +++ b/src/components/PrimaryButton.module.css @@ -1,19 +1,13 @@ .button { + composes: pressable from '../styles/shared.module.css'; width: 100%; padding: 1rem; - background: #a8d5e8; + background: var(--color-blue); border: none; border-radius: 1rem; font-size: 1.25rem; font-weight: 600; cursor: pointer; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); - transition: transform 80ms ease, box-shadow 80ms ease; -} - -.button:active:not(:disabled) { - transform: translateY(2px); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } .button:disabled { diff --git a/src/index.css b/src/index.css index c3e1bc4..7fad4e9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,8 +1,14 @@ +:root { + --color-blue: #a8d5e8; + --color-parchment: #f5f0e4; + --color-parchment-border: #c8b99a; +} + *, *::before, *::after { box-sizing: border-box; } button { min-height: 2.75rem; font-size: 1rem; font-family: inherit; cursor: pointer; } html, body, #root { margin: 0; height: 100%; } body { font-family: sans-serif; - background: linear-gradient(to bottom, #a8d5e8, #4a9e6a); + background: linear-gradient(to bottom, var(--color-blue), #4a9e6a); min-height: 100vh; } diff --git a/src/pages/PlayerSetup.module.css b/src/pages/PlayerSetup.module.css index b39c2aa..ebdb925 100644 --- a/src/pages/PlayerSetup.module.css +++ b/src/pages/PlayerSetup.module.css @@ -10,7 +10,7 @@ padding: 0.85rem 1rem; font-size: 1.1rem; font-family: inherit; - border: 1.5px solid #c8b99a; + border: 1.5px solid var(--color-parchment-border); border-radius: 0.75rem; background: #fdfaf4; } @@ -20,27 +20,25 @@ background: #fff5f5; } -.removeBtn { - flex-shrink: 0; - width: 2.5rem; - height: 2.5rem; +.circleBtn { + composes: pressable from '../styles/shared.module.css'; border-radius: 50%; border: none; - background: #d94f4f; - color: white; - font-size: 1.25rem; line-height: 1; display: flex; align-items: center; justify-content: center; cursor: pointer; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); - transition: transform 80ms ease, box-shadow 80ms ease; } -.removeBtn:active { - transform: translateY(2px); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); +.removeBtn { + composes: circleBtn; + flex-shrink: 0; + width: 2.5rem; + height: 2.5rem; + background: #d94f4f; + color: white; + font-size: 1.25rem; } .addRow { @@ -50,22 +48,9 @@ } .addBtn { + composes: circleBtn; width: 3rem; height: 3rem; - border-radius: 50%; - border: none; - background: #a8d5e8; + background: var(--color-blue); font-size: 1.75rem; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); - transition: transform 80ms ease, box-shadow 80ms ease; -} - -.addBtn:active { - transform: translateY(2px); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } diff --git a/src/pages/TitleScreen.module.css b/src/pages/TitleScreen.module.css index 170f8f0..a4f42d3 100644 --- a/src/pages/TitleScreen.module.css +++ b/src/pages/TitleScreen.module.css @@ -1,14 +1,8 @@ .frame { - position: fixed; - inset: 1rem; - border: 2px solid black; - border-radius: 2rem; + composes: card from '../styles/shared.module.css'; background: transparent; - display: flex; - flex-direction: column; align-items: center; justify-content: center; - overflow: hidden; } .title { diff --git a/src/styles/shared.module.css b/src/styles/shared.module.css new file mode 100644 index 0000000..9b3319f --- /dev/null +++ b/src/styles/shared.module.css @@ -0,0 +1,19 @@ +.card { + position: fixed; + inset: 1rem; + border: 2px solid black; + border-radius: 2rem; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.pressable { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + transition: transform 80ms ease, box-shadow 80ms ease; +} + +.pressable:active:not(:disabled) { + transform: translateY(2px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); +}